diff --git a/Recyclable.Collections/RecyclableLinkedList.AddRange.cs b/Recyclable.Collections/RecyclableLinkedList.AddRange.cs index f9aa4aa..384bf49 100644 --- a/Recyclable.Collections/RecyclableLinkedList.AddRange.cs +++ b/Recyclable.Collections/RecyclableLinkedList.AddRange.cs @@ -14,8 +14,10 @@ namespace Recyclable.Collections { private static readonly Action, object>? _dictionaryAdder; private static readonly Action, object>? _sortedListAdder; + private static readonly Action, object>? _sortedDictionaryAdder; private static readonly Type? _dictionaryType; private static readonly Type? _sortedListType; + private static readonly Type? _sortedDictionaryType; static AddRangeHelper() { @@ -24,6 +26,8 @@ namespace Recyclable.Collections { Type[] args = elementType.GetGenericArguments(); _dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args); + _sortedDictionaryType = typeof(RecyclableSortedDictionary<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclableLinkedListAddRange).GetMethod( nameof(AddRange), BindingFlags.NonPublic | BindingFlags.Static, @@ -37,6 +41,20 @@ namespace Recyclable.Collections var call = Expression.Call(method, listParam, Expression.Convert(objParam, _dictionaryType)); _dictionaryAdder = Expression.Lambda, object>>(call, listParam, objParam).Compile(); } + + method = typeof(zRecyclableLinkedListAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableLinkedList<>).MakeGenericType(elementType), _sortedDictionaryType }, + modifiers: null); + if (method != null) + { + var listParam = Expression.Parameter(typeof(RecyclableLinkedList)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, listParam, Expression.Convert(objParam, _sortedDictionaryType)); + _sortedDictionaryAdder = Expression.Lambda, object>>(call, listParam, objParam).Compile(); + } } else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)) { @@ -66,6 +84,12 @@ namespace Recyclable.Collections return true; } + if (_sortedDictionaryAdder != null && _sortedDictionaryType!.IsInstanceOfType(items)) + { + _sortedDictionaryAdder(list, items); + return true; + } + if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items)) { _sortedListAdder(list, items); @@ -922,6 +946,96 @@ namespace Recyclable.Collections list._count += count; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList> list, RecyclableSortedDictionary items) + where TKey : notnull + { + int count = items.Count; + if (count == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk> chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + count); + + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + chunk = chunk.Next!; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + int local = copied; + for (int i = 0; i < toCopy; i++) + { + destination[index + i] = new KeyValuePair(items.GetKey(local + i), items.GetValue(local + i)); + } + + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList<(TKey Key, TValue Value)> list, RecyclableSortedList items) + where TKey : notnull + { + int count = items._count; + if (count == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk<(TKey Key, TValue Value)> chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + count); + + TKey[] keys = items._keys; + TValue[] values = items._values; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + chunk = chunk.Next!; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + int local = copied; + for (int i = 0; i < toCopy; i++) + { + destination[index + i] = (keys[local + i], values[local + i]); + } + + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Grow(RecyclableLinkedList list) { diff --git a/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs b/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs index 10c1db3..5f92a3a 100644 --- a/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs @@ -424,6 +424,112 @@ namespace Recyclable.CollectionsTests _ = list.Should().Equal(expected); } + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedList(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var list = new RecyclableLinkedList<(int Key, long Value)>(); + + list.AddRange(source); + + List<(int Key, long Value)> expected = new(itemsCount); + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromSortedList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedList(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var list = new RecyclableLinkedList<(int Key, long Value)>(); + + list.AddRange(source); + list.AddRange(source); + + List<(int Key, long Value)> expected = new(itemsCount * 2); + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddSortedDictionaryItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var list = new RecyclableLinkedList>(); + + list.AddRange(source); + + List> expected = new(itemsCount); + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromSortedDictionary(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var list = new RecyclableLinkedList>(); + + list.AddRange(source); + list.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])); + } + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + [Fact] public void AddRangeShouldAddArrayItems() {