Skip to content

Commit

Permalink
Implement IComparable
Browse files Browse the repository at this point in the history
  • Loading branch information
rickardp committed Feb 16, 2023
1 parent 25be990 commit 3cd42d9
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 1 deletion.
42 changes: 42 additions & 0 deletions src/Combination.StringPools.Tests/Test_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public void Test_String_Conversion()
Assert.Equal("Hello", pool.Add("Hello").ToString());
Assert.Equal("Hello", (string)pool.Add("Hello"));
}

[Fact]
public void Test_Compare_Empty()
{
Expand Down Expand Up @@ -160,4 +161,45 @@ public void Test_Inequality_Different_Pools_Deduplicated()
Assert.True(str1 != str2);
Assert.NotEqual(str1.GetHashCode(), str2.GetHashCode());
}

[Theory]
[InlineData("a", "b")]
[InlineData("b", "a")]
[InlineData("a", "a")]
[InlineData("", "")]
[InlineData(" ", " ")]
[InlineData("", "b")]
[InlineData("a", "")]
[InlineData("a", "å")]
[InlineData("åå", "å")]
[InlineData("åaaaå", "åaaaå")]
public void Test_Compare_Different_Pools(string a, string b)
{
using var pool1 = StringPool.DeduplicatedUtf8(4096, 1);
using var pool2 = StringPool.DeduplicatedUtf8(4096, 1);
var poolA = pool1.Add(a);
var poolB = pool2.Add(b);
Assert.Equal(Sign(string.Compare(a, b, StringComparison.Ordinal)), Sign(poolA.CompareTo(poolB)));
}

[Theory]
[InlineData("a", "b")]
[InlineData("b", "a")]
[InlineData("a", "a")]
[InlineData("", "")]
[InlineData(" ", " ")]
[InlineData("", "b")]
[InlineData("a", "")]
[InlineData("a", "å")]
[InlineData("åå", "å")]
[InlineData("åaaaå", "åaaaå")]
public void Test_Compare_Same_Pool(string a, string b)
{
using var pool = StringPool.DeduplicatedUtf8(4096, 1);
var poolA = pool.Add(a);
var poolB = pool.Add(b);
Assert.Equal(Sign(string.Compare(a, b, StringComparison.Ordinal)), Sign(poolA.CompareTo(poolB)));
}

private static int Sign(int x) => x > 0 ? 1 : x < 0 ? -1 : 0;
}
7 changes: 6 additions & 1 deletion src/Combination.StringPools/PooledUtf8String.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Combination.StringPools;

public readonly struct PooledUtf8String : IEquatable<PooledUtf8String>
public readonly struct PooledUtf8String : IEquatable<PooledUtf8String>, IComparable<PooledUtf8String>
{
private readonly ulong handle;

Expand Down Expand Up @@ -33,6 +33,11 @@ public bool Equals(PooledUtf8String other)
return handle == other.handle || Utf8StringPool.StringsEqual(handle, other.handle);
}

public int CompareTo(PooledUtf8String other)
{
return Utf8StringPool.StringsCompare(handle, other.handle);
}

public override bool Equals(object? obj)
{
return obj is PooledUtf8String other && Equals(other);
Expand Down
62 changes: 62 additions & 0 deletions src/Combination.StringPools/Utf8StringPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,4 +523,66 @@ internal static bool StringsEqual(ulong a, ulong b)

public override string ToString() =>
$"Utf8StringPool(bits={deduplicationTableBits}, dedup={deduplicationTable is not null}, pages={pages.Count}, used={usedBytes}, added={addedBytes})";

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int StringsCompare(ulong a, ulong b)
{
if (a == ulong.MaxValue)
{
return b == ulong.MaxValue ? 0 : -1;
}

if (b == ulong.MaxValue)
{
return 1;
}

var aPoolIndex = a >> (64 - PoolIndexBits);
var bPoolIndex = b >> (64 - PoolIndexBits);
if (aPoolIndex >= (ulong)Pools.Count)
{
throw new ArgumentException("Bad string pool offset", nameof(a));
}

if (bPoolIndex >= (ulong)Pools.Count)
{
throw new ArgumentException("Bad string pool offset", nameof(b));
}

var aOffset = a & ((1L << (64 - PoolIndexBits)) - 1);
var bOffset = b & ((1L << (64 - PoolIndexBits)) - 1);
var aPool = Pools[(int)aPoolIndex];
if (aPool is null)
{
throw new ObjectDisposedException("String pool is disposed");
}

if (aPoolIndex != bPoolIndex)
{
var bPool = Pools[(int)bPoolIndex];
if (bPool is null)
{
throw new ObjectDisposedException("String pool is disposed");
}

using (aPool.disposeLock.PreventDispose())
{
using (bPool.disposeLock.PreventDispose())
{
return aPool.GetStringBytes(aOffset).SequenceCompareTo(bPool.GetStringBytes(bOffset));
}
}
}

if (aPool.deduplicationTable is not null && aOffset == bOffset)
{
// If the strings are in the same deduplicated pool, we can just compare the offsets
return 0;
}

using (aPool.disposeLock.PreventDispose())
{
return aPool.GetStringBytes(aOffset).SequenceCompareTo(aPool.GetStringBytes(bOffset));
}
}
}

0 comments on commit 3cd42d9

Please sign in to comment.