From 1dc6c479ae490f8c2a8350aa41233926282ee0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 17 Jul 2025 15:36:49 +0200 Subject: [PATCH] Add more AddRange overloads for RecyclableHashSet --- .../RecyclableHashSet.AddRange.cs | 275 ++++++++++++++++++ .../RecyclableHashSetTests.cs | 36 +++ 2 files changed, 311 insertions(+) diff --git a/Recyclable.Collections/RecyclableHashSet.AddRange.cs b/Recyclable.Collections/RecyclableHashSet.AddRange.cs index 6d38e8c..3e2da0e 100644 --- a/Recyclable.Collections/RecyclableHashSet.AddRange.cs +++ b/Recyclable.Collections/RecyclableHashSet.AddRange.cs @@ -454,6 +454,281 @@ namespace Recyclable.Collections insertIndex++; } + set._count = insertIndex; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableHashSet set, RecyclableLongList items) + where T : notnull + { + long longCount = items._longCount; + if (longCount == 0) + { + return; + } + + if (longCount > int.MaxValue - set._count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items exceeds {int.MaxValue}"); + } + + int count = (int)longCount; + int fullBlocks = (int)(longCount >> items._blockSizePow2BitShift); + int lastBlockLength = (int)(longCount & items._blockSizeMinus1); + 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; + var blocks = items._memoryBlocks; + int blockSize = items._blockSize; + + for (int block = 0; block < fullBlocks; block++) + { + var source = blocks[block]; + for (int j = 0; j < blockSize; j++) + { + T value = source[j]; + 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.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++; + } + } + + if (lastBlockLength > 0) + { + var source = blocks[fullBlocks]; + for (int j = 0; j < lastBlockLength; j++) + { + T value = source[j]; + 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.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(this RecyclableHashSet set, RecyclableStack items) + where T : notnull + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + if (longCount > int.MaxValue - set._count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items exceeds {int.MaxValue}"); + } + + int count = (int)longCount; + 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; + + var sourceChunk = items._current; + while (sourceChunk.Previous != null) + { + sourceChunk = sourceChunk.Previous; + } + + while (sourceChunk != null) + { + var source = sourceChunk.Value; + int sourceCount = sourceChunk.Index; + for (int i = 0; i < sourceCount; i++) + { + T value = source[i]; + int hash = value?.GetHashCode() & int.MaxValue ?? 0; + int bucket = hash & bucketMask; + + int idx = set._buckets[bucket]; + bool exists = false; + while (idx >= 0) + { + ref var check = ref GetEntry(set, idx); + if (check.HashCode == hash && EqualityComparer.Default.Equals(check.Value, value)) + { + exists = true; + break; + } + + idx = 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++; + } + + sourceChunk = sourceChunk.Next!; + } + + set._count = insertIndex; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableHashSet set, RecyclableQueue items) + where T : notnull + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + if (longCount > int.MaxValue - set._count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items exceeds {int.MaxValue}"); + } + + int count = (int)longCount; + 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; + + var chunk = items._head; + while (chunk != null) + { + int idx = chunk.Bottom; + int top = chunk.Top; + var source = chunk.Value; + + while (idx < top) + { + T value = source[idx++]; + int hash = value?.GetHashCode() & int.MaxValue ?? 0; + int bucket = hash & bucketMask; + + int checkIndex = set._buckets[bucket]; + bool exists = false; + while (checkIndex >= 0) + { + ref var check = ref GetEntry(set, checkIndex); + if (check.HashCode == hash && EqualityComparer.Default.Equals(check.Value, value)) + { + exists = true; + break; + } + + checkIndex = 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++; + } + + chunk = chunk.Next; + } + set._count = insertIndex; } diff --git a/Recyclable.CollectionsTests/RecyclableHashSetTests.cs b/Recyclable.CollectionsTests/RecyclableHashSetTests.cs index c522316..88298f5 100644 --- a/Recyclable.CollectionsTests/RecyclableHashSetTests.cs +++ b/Recyclable.CollectionsTests/RecyclableHashSetTests.cs @@ -125,5 +125,41 @@ namespace Recyclable.CollectionsTests _ = set.Should().Contain(new[] { 1, 2, 3 }); } + [Fact] + public void AddRangeShouldAddItemsFromLongList() + { + using var set = new RecyclableHashSet(); + using var list = new RecyclableLongList(new[] { 1, 2, 3 }); + + set.AddRange(list); + + _ = set.Count.Should().Be(3); + _ = set.Should().Contain(new[] { 1, 2, 3 }); + } + + [Fact] + public void AddRangeShouldAddItemsFromStack() + { + using var set = new RecyclableHashSet(); + using var stack = new RecyclableStack(new[] { 1, 2, 3 }); + + set.AddRange(stack); + + _ = set.Count.Should().Be(3); + _ = set.Should().Contain(new[] { 1, 2, 3 }); + } + + [Fact] + public void AddRangeShouldAddItemsFromQueue() + { + using var set = new RecyclableHashSet(); + using var queue = new RecyclableQueue(new[] { 1, 2, 3 }); + + set.AddRange(queue); + + _ = set.Count.Should().Be(3); + _ = set.Should().Contain(new[] { 1, 2, 3 }); + } + } }