Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions csharp/Platform.Data.Doublets.Tests/SequencesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using System;
using System.IO;
using System.Numerics;
using Platform.Data.Doublets.Decorators;
using Xunit;
using Platform.Memory;
using Platform.Data.Doublets.Memory.United.Generic;

namespace Platform.Data.Doublets.Tests
{
public static class SequencesTests
{
[Fact]
public static void SequencesWithoutCompactificationTest()
{
Using<uint>(links =>
{
using var sequences = new Sequences<uint>(links, enableAutomaticCompactification: false);

// Verify that automatic compactification is disabled
Assert.False(sequences.IsAutomaticCompactificationEnabled);

// Create some links
var link1 = links.Create();
var link2 = links.Create();
var link3 = links.GetOrCreate(link1, link2);

var countBefore = links.Count();

// Dispose should not perform compactification
sequences.Dispose();

var countAfter = links.Count();
Assert.Equal(countBefore, countAfter);
});
}

[Fact]
public static void SequencesWithCompactificationTest()
{
Using<uint>(links =>
{
using var sequences = new Sequences<uint>(links, enableAutomaticCompactification: true);

// Verify that automatic compactification is enabled
Assert.True(sequences.IsAutomaticCompactificationEnabled);

// Create some duplicate links
var link1 = links.Create();
var link2 = links.Create();

// Create the first link with specific source and target
var originalLink = links.GetOrCreate(link1, link2);

// Create another link with the same source and target (duplicate)
var duplicateLink = links.CreateAndUpdate(link1, link2);

// Verify we have duplicates
Assert.NotEqual(originalLink, duplicateLink);
Assert.Equal(links.GetSource(originalLink), links.GetSource(duplicateLink));
Assert.Equal(links.GetTarget(originalLink), links.GetTarget(duplicateLink));

var countBefore = links.Count();

// Dispose should perform compactification and remove duplicates
sequences.Dispose();

var countAfter = links.Count();

// Should have fewer links after compactification
Assert.True(countAfter < countBefore, $"Expected count to decrease from {countBefore} to {countAfter}");
});
}

[Fact]
public static void ManualCompactificationTest()
{
Using<uint>(links =>
{
var sequences = new Sequences<uint>(links, enableAutomaticCompactification: false);

// Create some duplicate links
var link1 = links.Create();
var link2 = links.Create();

// Create the first link with specific source and target
var originalLink = links.GetOrCreate(link1, link2);

// Create another link with the same source and target (duplicate)
var duplicateLink = links.CreateAndUpdate(link1, link2);

// Verify we have duplicates
Assert.NotEqual(originalLink, duplicateLink);

var countBefore = links.Count();

// Manually trigger compactification
sequences.Compact();

var countAfter = links.Count();

// Should have fewer links after compactification
Assert.True(countAfter < countBefore, $"Expected count to decrease from {countBefore} to {countAfter}");

sequences.Dispose();
});
}

[Fact]
public static void CompactificationSkipsPointLinksTest()
{
Using<uint>(links =>
{
var sequences = new Sequences<uint>(links, enableAutomaticCompactification: true);

// Create point links (links that reference themselves)
var pointLink1 = links.CreatePoint();
var pointLink2 = links.CreatePoint();

// Create regular duplicate links
var link1 = links.Create();
var link2 = links.Create();
var originalLink = links.GetOrCreate(link1, link2);
var duplicateLink = links.CreateAndUpdate(link1, link2);

var countBefore = links.Count();
var pointLinksCountBefore = CountPointLinks(links);

// Compactification should skip point links
sequences.Compact();

var countAfter = links.Count();
var pointLinksCountAfter = CountPointLinks(links);

// Point links should remain unchanged
Assert.Equal(pointLinksCountBefore, pointLinksCountAfter);

// But duplicates should be removed
Assert.True(countAfter < countBefore, $"Expected count to decrease from {countBefore} to {countAfter}");

sequences.Dispose();
});
}

[Fact]
public static void MultipleDisposeCallsTest()
{
Using<uint>(links =>
{
var sequences = new Sequences<uint>(links, enableAutomaticCompactification: true);

// Create some links
var link1 = links.Create();
var link2 = links.Create();
links.GetOrCreate(link1, link2);

// Multiple dispose calls should be safe
sequences.Dispose();
sequences.Dispose(); // This should not throw or cause issues

// Verify links are still accessible
Assert.True(links.Count() > 0);
});
}

private static uint CountPointLinks<TLinkAddress>(ILinks<TLinkAddress> links)
where TLinkAddress : IUnsignedNumber<TLinkAddress>
{
uint count = 0;
var constants = links.Constants;
var query = new Link<TLinkAddress>(constants.Any, constants.Any, constants.Any);

links.Each(link =>
{
var linkAddress = links.GetIndex(link);
var source = links.GetSource(link);
var target = links.GetTarget(link);

if (source == linkAddress && target == linkAddress)
{
count++;
}

return constants.Continue;
}, query);

return count;
}

private static void Using<TLinkAddress>(Action<ILinks<TLinkAddress>> action)
where TLinkAddress : IUnsignedNumber<TLinkAddress>, IShiftOperators<TLinkAddress, int, TLinkAddress>,
IBitwiseOperators<TLinkAddress, TLinkAddress, TLinkAddress>, IMinMaxValue<TLinkAddress>,
IComparisonOperators<TLinkAddress, TLinkAddress, bool>
{
var unitedMemoryLinks = new UnitedMemoryLinks<TLinkAddress>(new HeapResizableDirectMemory());
try
{
action(unitedMemoryLinks);
}
finally
{
unitedMemoryLinks?.Dispose();
}
}
}
}
163 changes: 163 additions & 0 deletions csharp/Platform.Data.Doublets/Sequences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using Platform.Data.Doublets.Decorators;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace Platform.Data.Doublets;

/// <summary>
/// <para>
/// Represents a sequences manager that provides automatic compactification (deduplication) functionality.
/// </para>
/// <para>
/// This class decorates an underlying ILinks instance and optionally performs automatic
/// compactification when disposed, merging duplicate links that have the same source and target.
/// </para>
/// <para>
/// Compactification helps reduce storage overhead by eliminating redundant links while
/// preserving the semantics of the data structure.
/// </para>
/// </summary>
/// <typeparam name="TLinkAddress">The type of the link address.</typeparam>
/// <seealso cref="LinksDisposableDecoratorBase{TLinkAddress}" />
public class Sequences<TLinkAddress> : LinksDisposableDecoratorBase<TLinkAddress> where TLinkAddress : IUnsignedNumber<TLinkAddress>
{
private readonly bool _enableAutomaticCompactification;

/// <summary>
/// <para>
/// Initializes a new <see cref="Sequences" /> instance.
/// </para>
/// <para></para>
/// </summary>
/// <param name="links">
/// <para>The underlying links storage.</para>
/// <para></para>
/// </param>
/// <param name="enableAutomaticCompactification">
/// <para>Enables automatic compactification (deduplication) of all sequences on dispose.</para>
/// <para></para>
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Sequences(ILinks<TLinkAddress> links, bool enableAutomaticCompactification = false) : base(links)
{
_enableAutomaticCompactification = enableAutomaticCompactification;
}

/// <summary>
/// <para>
/// Gets a value indicating whether automatic compactification is enabled.
/// </para>
/// <para></para>
/// </summary>
public bool IsAutomaticCompactificationEnabled
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _enableAutomaticCompactification;
}

/// <summary>
/// <para>
/// Performs compactification (deduplication) of all sequences by merging duplicate links.
/// </para>
/// <para></para>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Compact()
{
var duplicatePairs = FindDuplicateLinks();
foreach (var (duplicate, original) in duplicatePairs)
{
_links.MergeAndDelete(duplicate, original);
}
}

/// <summary>
/// <para>
/// Finds duplicate links that have the same source and target.
/// </para>
/// <para></para>
/// </summary>
/// <returns>
/// <para>A collection of duplicate pairs where each pair contains (duplicate, original).</para>
/// <para></para>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private System.Collections.Generic.List<(TLinkAddress duplicate, TLinkAddress original)> FindDuplicateLinks()
{
var duplicatePairs = new System.Collections.Generic.List<(TLinkAddress duplicate, TLinkAddress original)>();
var seenLinks = new System.Collections.Generic.Dictionary<(TLinkAddress source, TLinkAddress target), TLinkAddress>();

var constants = _links.Constants;
var query = new Link<TLinkAddress>(constants.Any, constants.Any, constants.Any);

_links.Each(link =>
{
if (link == null)
{
return constants.Continue;
}

var linkAddress = _links.GetIndex(link);
var source = _links.GetSource(link);
var target = _links.GetTarget(link);

// Skip point links (where source == target == linkAddress)
if (source.Equals(linkAddress) && target.Equals(linkAddress))
{
return constants.Continue;
}

var key = (source, target);
if (seenLinks.TryGetValue(key, out var originalLink))
{
// Found a duplicate - the current link is a duplicate of the original
duplicatePairs.Add((linkAddress, originalLink));
}
else
{
// First time seeing this source-target combination
seenLinks[key] = linkAddress;
}

return constants.Continue;
}, query);

return duplicatePairs;
}

/// <summary>
/// <para>
/// Disposes the sequences manager and optionally performs automatic compactification.
/// </para>
/// <para></para>
/// </summary>
/// <param name="manual">
/// <para>The manual disposal flag.</para>
/// <para></para>
/// </param>
/// <param name="wasDisposed">
/// <para>The was disposed flag.</para>
/// <para></para>
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void Dispose(bool manual, bool wasDisposed)
{
if (!wasDisposed && _enableAutomaticCompactification)
{
try
{
Compact();
}
catch
{
// Ignore any errors during compactification
// The underlying links storage might be in an inconsistent state
}
}

// Don't dispose the underlying links - let the caller handle that
// base.Dispose(manual, wasDisposed);
}
}
Loading