diff --git a/Recyclable.Collections/RecyclableStack.AddRange.cs b/Recyclable.Collections/RecyclableStack.AddRange.cs index b194203..0053375 100644 --- a/Recyclable.Collections/RecyclableStack.AddRange.cs +++ b/Recyclable.Collections/RecyclableStack.AddRange.cs @@ -694,29 +694,104 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] internal static void AddRange(this RecyclableStack stack, RecyclableQueue items) { - var chunk = items._head; - while (chunk != null) + long longCount = items._count; + if (longCount == 0) { - for (int i = chunk.Bottom; i < chunk.Top; i++) - { - stack.Push(chunk.Value[i]); - } - chunk = chunk.Next; + return; } + + EnsureCapacity(stack, stack._count + longCount); + + RecyclableArrayPoolChunk destChunk = stack._current; + while (destChunk.Previous != null && destChunk.Previous.Index < destChunk.Previous.Value.Length) + { + destChunk = destChunk.Previous; + } + + int destIndex = destChunk.Index; + int destLength = destChunk.Value.Length; + + var sourceChunk = items._head; + while (sourceChunk != null) + { + int sourceCount = sourceChunk.Top - sourceChunk.Bottom; + int sourceIndex = sourceChunk.Bottom; + + while (sourceCount > 0) + { + int toCopy = Math.Min(destLength - destIndex, sourceCount); + Array.Copy(sourceChunk.Value, sourceIndex, destChunk.Value, destIndex, toCopy); + destIndex += toCopy; + sourceIndex += toCopy; + sourceCount -= toCopy; + + if (destIndex == destLength && (sourceCount > 0 || sourceChunk.Next != null)) + { + destChunk.Index = destIndex; + destChunk = destChunk.Next!; + destIndex = 0; + destLength = destChunk.Value.Length; + } + } + + sourceChunk = sourceChunk.Next; + } + + destChunk.Index = destIndex; + stack._count += longCount; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] internal static void AddRange(this RecyclableStack> stack, RecyclableDictionary items) where TKey : notnull { + int count = items._count; + if (count == 0) + { + return; + } + + EnsureCapacity(stack, stack._count + count); + var entries = items._entries; int shift = items._blockShift, mask = items._blockSizeMinus1; - for (int i = 0; i < items._count; i++) + + RecyclableArrayPoolChunk> chunk = stack._current; + while (chunk.Previous != null && chunk.Previous.Index < chunk.Previous.Value.Length) { - ref var entry = ref entries[i >> shift][i & mask]; - stack.Push(new KeyValuePair(entry.Key, entry.Value)); + chunk = chunk.Previous; } + + int index = chunk.Index; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + int local = copied; + for (int i = 0; i < toCopy; i++) + { + ref var entry = ref entries[(local + i) >> shift][(local + i) & mask]; + destination[index + i] = new KeyValuePair(entry.Key, entry.Value); + } + + copied += toCopy; + index += toCopy; + + if (index == chunkLength && copied < count) + { + chunk.Index = index; + chunk = chunk.Next!; + index = 0; + chunkLength = chunk.Value.Length; + } + } + + chunk.Index = index; + stack._count += count; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index 90a9f1c..24e6553 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -610,5 +610,98 @@ namespace Recyclable.CollectionsTests _ = stack.Should().Equal(expected); } + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddQueueItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableQueue(data); + using var stack = new RecyclableStack(); + + stack.AddRange(source); + + List expected = new(itemsCount); + expected.AddRange(data); + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount); + _ = stack.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromQueue(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableQueue(data); + using var stack = new RecyclableStack(); + + stack.AddRange(source); + stack.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount * 2); + _ = stack.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddDictionaryItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var stack = new RecyclableStack>(); + + stack.AddRange(source); + + List> expected = new(itemsCount); + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount); + _ = stack.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromDictionary(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var stack = new RecyclableStack>(); + + stack.AddRange(source); + stack.AddRange(source); + + List> expected = new(itemsCount * 2); + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount * 2); + _ = stack.Should().Equal(expected); + } + } }