Add span AddRange for hash set
This commit is contained in:
1 parent
a178956761
commit
c24d8d54ea
4 files changed
+1540
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal static class zRecyclableHashSetAddRange
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ref RecyclableHashSet<T>.Entry GetEntry<T>(RecyclableHashSet<T> set, int index)
|
||||
where T : notnull
|
||||
{
|
||||
return ref set._entries[index >> set._blockShift][index & set._blockSizeMinus1];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EnsureCapacity<T>(RecyclableHashSet<T> set, int min)
|
||||
where T : notnull
|
||||
{
|
||||
int requiredBlocks = (min + set._blockSizeMinus1) >> set._blockShift;
|
||||
if (set._entries.Length < requiredBlocks)
|
||||
{
|
||||
var newBlocks = requiredBlocks >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<RecyclableHashSet<T>.Entry[]>.RentShared(requiredBlocks)
|
||||
: new RecyclableHashSet<T>.Entry[requiredBlocks][];
|
||||
|
||||
Array.Copy(set._entries, newBlocks, set._entries.Length);
|
||||
|
||||
if (set._entries.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<RecyclableHashSet<T>.Entry[]>.ReturnShared(set._entries, false);
|
||||
}
|
||||
|
||||
set._entries = newBlocks;
|
||||
}
|
||||
|
||||
for (int i = 0; i < requiredBlocks; i++)
|
||||
{
|
||||
if (set._entries[i] == null)
|
||||
{
|
||||
set._entries[i] = set._blockSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<RecyclableHashSet<T>.Entry>.RentShared(set._blockSize)
|
||||
: new RecyclableHashSet<T>.Entry[set._blockSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResizeBuckets<T>(RecyclableHashSet<T> set, int newSize)
|
||||
where T : notnull
|
||||
{
|
||||
int[] newBuckets = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<int>.RentShared(newSize)
|
||||
: new int[newSize];
|
||||
Array.Fill(newBuckets, -1);
|
||||
|
||||
for (int i = 0; i < set._count; i++)
|
||||
{
|
||||
ref var entry = ref GetEntry(set, i);
|
||||
int bucket = entry.HashCode & (newSize - 1);
|
||||
entry.Next = newBuckets[bucket];
|
||||
newBuckets[bucket] = i;
|
||||
}
|
||||
|
||||
if (set._buckets.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<int>.ReturnShared(set._buckets, false);
|
||||
}
|
||||
|
||||
set._buckets = newBuckets;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableHashSet<T> set, ReadOnlySpan<T> items)
|
||||
where T : notnull
|
||||
{
|
||||
int count = items.Length;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int requiredCount = set._count + count;
|
||||
EnsureCapacity(set, requiredCount);
|
||||
|
||||
int bucketsLength = set._buckets.Length;
|
||||
while (requiredCount >= (bucketsLength * 3) / 4)
|
||||
{
|
||||
bucketsLength <<= 1;
|
||||
}
|
||||
|
||||
if (bucketsLength != set._buckets.Length)
|
||||
{
|
||||
ResizeBuckets(set, bucketsLength);
|
||||
}
|
||||
|
||||
int bucketMask = set._buckets.Length - 1;
|
||||
int insertIndex = set._count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T value = items[i];
|
||||
int hash = value?.GetHashCode() & int.MaxValue ?? 0;
|
||||
int bucket = hash & bucketMask;
|
||||
|
||||
int index = set._buckets[bucket];
|
||||
bool exists = false;
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var check = ref GetEntry(set, index);
|
||||
if (check.HashCode == hash && EqualityComparer<T>.Default.Equals(check.Value, value))
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
|
||||
index = check.Next;
|
||||
}
|
||||
|
||||
if (exists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var entry = ref GetEntry(set, insertIndex);
|
||||
entry.HashCode = hash;
|
||||
entry.Value = value!;
|
||||
entry.Next = set._buckets[bucket];
|
||||
set._buckets[bucket] = insertIndex;
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
set._count = insertIndex;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableHashSet<T> set, Span<T> items)
|
||||
where T : notnull
|
||||
=> AddRange(set, (ReadOnlySpan<T>)items);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableHashSet<T> set, T[] items)
|
||||
where T : notnull
|
||||
=> AddRange(set, (ReadOnlySpan<T>)items);
|
||||
}
|
||||
}
|
||||
@@ -95,5 +95,229 @@ namespace Recyclable.CollectionsTests
|
||||
var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToList();
|
||||
_ = actual.Should().ContainInConsecutiveOrder(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSpan()
|
||||
{
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
dict.AddRange(data);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i + 1);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromDictionary()
|
||||
{
|
||||
using var source = new RecyclableDictionary<int, string>();
|
||||
source.Add(1, "a");
|
||||
source.Add(2, "b");
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(2);
|
||||
dict.GetKey(0).Should().Be(1);
|
||||
dict.GetValue(0).Should().Be("a");
|
||||
dict.GetKey(1).Should().Be(2);
|
||||
dict.GetValue(1).Should().Be("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddLongListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableLongList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, long>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i);
|
||||
dict.GetValue(i).Should().Be(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, long>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i);
|
||||
dict.GetValue(i).Should().Be(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddQueueItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableQueue<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddStackItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableStack<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddLinkedListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableLinkedList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclablePriorityQueue<KeyValuePair<int, string>>(comparer: Comparer<KeyValuePair<int, string>>.Create((x, y) => x.Key.CompareTo(y.Key)));
|
||||
foreach (var kvp in data)
|
||||
{
|
||||
source.Enqueue(kvp);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,5 +76,30 @@ namespace Recyclable.CollectionsTests
|
||||
_ = actual.Should().Contain(expected).And.HaveCount(itemsCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSpan()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
var data = new[] { 1, 2, 3 };
|
||||
|
||||
set.AddRange(data);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().ContainInOrder(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldIgnoreDuplicates()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
var data = new[] { 1, 2, 3 };
|
||||
|
||||
set.AddRange(data);
|
||||
set.AddRange(data);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().ContainInOrder(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user