diff --git a/Recyclable.Collections/RecyclableLinkedList.AddRange.cs b/Recyclable.Collections/RecyclableLinkedList.AddRange.cs new file mode 100644 index 0000000..384bf49 --- /dev/null +++ b/Recyclable.Collections/RecyclableLinkedList.AddRange.cs @@ -0,0 +1,1146 @@ +using System.Collections; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Numerics; +using Recyclable.Collections.Pools; + +namespace Recyclable.Collections +{ + internal static class zRecyclableLinkedListAddRange + { + private static class AddRangeHelper + { + 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() + { + Type elementType = typeof(T); + if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + 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, + binder: null, + types: new[] { typeof(RecyclableLinkedList<>).MakeGenericType(elementType), _dictionaryType }, + 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, _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<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclableLinkedListAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableLinkedList<>).MakeGenericType(elementType), _sortedListType }, + 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, _sortedListType)); + _sortedListAdder = Expression.Lambda, object>>(call, listParam, objParam).Compile(); + } + } + } + + internal static bool TryAddRange(RecyclableLinkedList list, IEnumerable items) + { + if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items)) + { + _dictionaryAdder(list, items); + return true; + } + + if (_sortedDictionaryAdder != null && _sortedDictionaryType!.IsInstanceOfType(items)) + { + _sortedDictionaryAdder(list, items); + return true; + } + + if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items)) + { + _sortedListAdder(list, items); + return true; + } + + return false; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, ReadOnlySpan items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + 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); + items.Slice(copied, toCopy).CopyTo(new Span(chunk.Value, index, toCopy)); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, Span items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + 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); + items.Slice(copied, toCopy).CopyTo(new Span(chunk.Value, index, toCopy)); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, T[] items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + 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); + Array.Copy(items, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, in Array items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + int lower = items.GetLowerBound(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); + Array.Copy(items, lower + copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, List items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + Span source = CollectionsMarshal.AsSpan(items); + + 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); + source.Slice(copied, toCopy).CopyTo(new Span(chunk.Value, index, toCopy)); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, ICollection items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count))) + : new T[count]; + + items.CopyTo(buffer, 0); + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + 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); + Array.Copy(buffer, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclableLinkedList._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, ICollection items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count))) + : new T[count]; + + items.CopyTo(buffer, 0); + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + 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); + Array.Copy(buffer, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclableLinkedList._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, IReadOnlyList items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + EnsureCapacity(list, list._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + for (int i = 0; i < count; i++) + { + if (index == chunkLength) + { + chunk.Top = index; + chunk = chunk.Next!; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = items[i]; + } + + chunk.Top = index; + list._count += count; + } + + internal static IEnumerator AddRange(this RecyclableLinkedList list, IEnumerable source) + { + IEnumerator enumerator = source.GetEnumerator(); + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + while (enumerator.MoveNext()) + { + if (index == chunkLength) + { + chunk.Top = index; + if (chunk.Next == null) + { + Grow(list); + chunk = list._tail; + } + else + { + chunk = chunk.Next; + } + + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = (T)enumerator.Current!; + list._count++; + } + + chunk.Top = index; + return enumerator; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, IEnumerable items) + { + if (items is RecyclableList recyclableList) + { + AddRange(list, recyclableList); + } + else if (items is RecyclableLongList recyclableLongList) + { + AddRange(list, recyclableLongList); + } + else if (items is T[] array) + { + AddRange(list, array); + } + else if (items is List listSource) + { + AddRange(list, listSource); + } + else if (items is ICollection iCollection) + { + AddRange(list, iCollection); + } + else if (items is ICollection collection) + { + AddRange(list, collection); + } + else if (items is RecyclableHashSet hashSet) + { + AddRange(list, hashSet); + } + else if (items is RecyclableStack stack) + { + AddRange(list, stack); + } + else if (items is RecyclableSortedSet sortedSet) + { + AddRange(list, sortedSet); + } + else if (items is RecyclableLinkedList linkedList) + { + AddRange(list, linkedList); + } + else if (items is RecyclablePriorityQueue priorityQueue) + { + AddRange(list, priorityQueue); + } + else if (items is RecyclableQueue queue) + { + AddRange(list, queue); + } + else if (AddRangeHelper.TryAddRange(list, items)) + { + } + else if (items is IReadOnlyList readOnlyList) + { + AddRange(list, readOnlyList); + } + else + { + using IEnumerator enumerator = items.GetEnumerator(); + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + while (enumerator.MoveNext()) + { + if (index == chunkLength) + { + chunk.Top = index; + if (chunk.Next == null) + { + Grow(list); + chunk = list._tail; + } + else + { + chunk = chunk.Next; + } + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = enumerator.Current; + list._count++; + } + + chunk.Top = index; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableList items) + { + 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; + T[] source = items._memoryBlock; + + 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); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableLongList items) + { + long longCount = items._longCount; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + longCount); + + int blockSize = items._blockSize; + int fullBlocks = (int)(longCount >> items._blockSizePow2BitShift); + int lastBlockLength = (int)(longCount & items._blockSizeMinus1); + int chunkLength = chunk.Value.Length; + + for (int block = 0; block < fullBlocks; block++) + { + T[] source = items._memoryBlocks[block]; + int sourceIndex = 0; + while (sourceIndex < blockSize) + { + if (index == chunkLength) + { + chunk.Top = index; + chunk = chunk.Next!; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, blockSize - sourceIndex); + Array.Copy(source, sourceIndex, chunk.Value, index, toCopy); + sourceIndex += toCopy; + index += toCopy; + } + } + + if (lastBlockLength > 0) + { + T[] source = items._memoryBlocks[fullBlocks]; + int sourceIndex = 0; + while (sourceIndex < lastBlockLength) + { + if (index == chunkLength) + { + chunk.Top = index; + chunk = chunk.Next!; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, lastBlockLength - sourceIndex); + Array.Copy(source, sourceIndex, chunk.Value, index, toCopy); + sourceIndex += toCopy; + index += toCopy; + } + } + + chunk.Top = index; + list._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableHashSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + count); + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + 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; + for (int i = 0; i < toCopy; i++) + { + destination[index + i] = entries[(copied + i) >> shift][(copied + i) & mask].Value; + } + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableStack items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = list._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + EnsureCapacity(list, list._count + longCount); + + RecyclableArrayPoolChunk sourceChunk = items._current; + while (sourceChunk.Previous != null) + { + sourceChunk = sourceChunk.Previous; + } + + while (sourceChunk != null) + { + T[] source = sourceChunk.Value; + int sourceCount = sourceChunk.Index; + int sourceIndex = 0; + + while (sourceIndex < sourceCount) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + destChunk = destChunk.Next!; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceCount - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next!; + } + + destChunk.Top = destIndex; + list._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableSortedSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + count); + + T[] source = items._items; + 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); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableLinkedList items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = list._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + EnsureCapacity(list, list._count + longCount); + + var sourceChunk = items._head; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + destChunk = destChunk.Next!; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next; + } + + destChunk.Top = destIndex; + list._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclablePriorityQueue items) + { + int count = items._size; + if (count == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk chunk = list._tail; + int index = chunk.Top; + + EnsureCapacity(list, list._count + count); + + T[] source = items._heap; + 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); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + list._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList list, RecyclableQueue items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = list._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + EnsureCapacity(list, list._count + longCount); + + var sourceChunk = items._head; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + destChunk = destChunk.Next!; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next; + } + + destChunk.Top = destIndex; + list._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableLinkedList> list, RecyclableDictionary 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); + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + 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++) + { + ref var entry = ref entries[(local + i) >> shift][(local + i) & mask]; + destination[index + i] = new KeyValuePair(entry.Key, entry.Value); + } + + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + 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) + { + int newSize; + if (list._capacity >= RecyclableDefaults.MaxPooledBlockSize) + { + newSize = RecyclableDefaults.MaxPooledBlockSize; + } + else + { + long doubled = list._capacity << 1; + newSize = (int)Math.Min(doubled - list._capacity, RecyclableDefaults.MaxPooledBlockSize); + } + + var newChunk = BiDirectionalRecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableLinkedList._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Top = 0; + newChunk.Bottom = 0; + newChunk.Previous = list._tail; + newChunk.Next = null; + list._tail.Next = newChunk; + list._tail = newChunk; + list._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Grow(RecyclableLinkedList list, long additionalCapacity) + { + int newSize = additionalCapacity >= RecyclableDefaults.MaxPooledBlockSize + ? RecyclableDefaults.MaxPooledBlockSize + : (int)Math.Min(BitOperations.RoundUpToPowerOf2((uint)additionalCapacity), RecyclableDefaults.MaxPooledBlockSize); + + var newChunk = BiDirectionalRecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableLinkedList._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Top = 0; + newChunk.Bottom = 0; + newChunk.Previous = list._tail; + newChunk.Next = null; + list._tail.Next = newChunk; + list._tail = newChunk; + list._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(RecyclableLinkedList list, long requiredCapacity) + { + if (requiredCapacity <= list._capacity) + { + return; + } + + long needed = requiredCapacity - list._capacity; + + do + { + int size = needed >= RecyclableDefaults.MaxPooledBlockSize + ? RecyclableDefaults.MaxPooledBlockSize + : (int)BitOperations.RoundUpToPowerOf2((uint)needed); + + var newChunk = BiDirectionalRecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < size) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableLinkedList._needsClearing); + } + + newChunk.Value = size >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(size) + : new T[size]; + } + + newChunk.Top = 0; + newChunk.Bottom = 0; + newChunk.Previous = list._tail; + newChunk.Next = null; + list._tail.Next = newChunk; + list._tail = newChunk; + list._capacity += size; + + needed -= size; + } + while (needed > 0); + } + } +} diff --git a/Recyclable.Collections/RecyclableLinkedList.cs b/Recyclable.Collections/RecyclableLinkedList.cs index a0a95d0..ae2bc16 100644 --- a/Recyclable.Collections/RecyclableLinkedList.cs +++ b/Recyclable.Collections/RecyclableLinkedList.cs @@ -292,7 +292,7 @@ namespace Recyclable.Collections return false; } - if (_index >= _chunk.Top) + while (_index >= _chunk.Top) { _chunk = _chunk.Next; if (_chunk == null) diff --git a/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs b/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs index 20b563e..5f92a3a 100644 --- a/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLinkedListTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Recyclable.Collections; using System.Linq; +using System.Collections; namespace Recyclable.CollectionsTests { @@ -79,5 +80,510 @@ namespace Recyclable.CollectionsTests _ = actual.Should().ContainInConsecutiveOrder(source.Reverse()); } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddLongListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLongList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromLongList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLongList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddHashSetItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableHashSet(); + foreach (long item in data) + { + source.Add(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromHashSet(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableHashSet(); + foreach (long item in data) + { + source.Add(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddStackItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableStack(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromStack(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableStack(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddSortedSetItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedSet(); + foreach (long item in data) + { + source.Add(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromSortedSet(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedSet(); + foreach (long item in data) + { + source.Add(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddLinkedListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLinkedList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromLinkedList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLinkedList(data); + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclablePriorityQueue(); + foreach (long item in data) + { + source.Enqueue(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromPriorityQueue(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclablePriorityQueue(); + foreach (long item in data) + { + source.Enqueue(item); + } + using var list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.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 list = new RecyclableLinkedList(); + + list.AddRange(source); + + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(data); + } + + [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 list = new RecyclableLinkedList(); + + list.AddRange(source); + list.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.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 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 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 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); + } + + [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() + { + long[] data = { 1, 2, 3 }; + using var list = new RecyclableLinkedList(); + + list.AddRange(data); + + _ = list.Should().Equal(data); + } + + [Fact] + public void AddRangeShouldAddCollectionItems() + { + ICollection collection = new long[] { 1, 2, 3 }; + using var list = new RecyclableLinkedList(); + + list.AddRange(collection); + + _ = list.Should().Equal(collection.Cast()); + } + + [Fact] + public void AddRangeShouldAddEnumerableItems() + { + IEnumerable enumerable = new long[] { 1, 2, 3 }; + using var list = new RecyclableLinkedList(); + + list.AddRange(enumerable); + + _ = list.Should().Equal(enumerable); + } + + [Fact] + public void AddRangeShouldAddSpanItems() + { + long[] data = { 1, 2, 3 }; + Span span = data; + using var list = new RecyclableLinkedList(); + + list.AddRange(span); + + _ = list.Should().Equal(data); + } + + [Fact] + public void AddRangeShouldAddListItems() + { + var data = new List { 1, 2, 3 }; + using var list = new RecyclableLinkedList(); + + list.AddRange(data); + + _ = list.Should().Equal(data); + } } }