diff --git a/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs b/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs new file mode 100644 index 0000000..1fd4b36 --- /dev/null +++ b/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs @@ -0,0 +1,1335 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Numerics; +using Recyclable.Collections.Pools; + +namespace Recyclable.Collections +{ + internal static class zRecyclablePriorityQueueAddRange + { + 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(zRecyclablePriorityQueueAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclablePriorityQueue<>).MakeGenericType(elementType), _dictionaryType }, + modifiers: null); + if (method != null) + { + var queueParam = Expression.Parameter(typeof(RecyclablePriorityQueue)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _dictionaryType)); + _dictionaryAdder = Expression.Lambda, object>>(call, queueParam, objParam).Compile(); + } + + method = typeof(zRecyclablePriorityQueueAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclablePriorityQueue<>).MakeGenericType(elementType), _sortedDictionaryType }, + modifiers: null); + if (method != null) + { + var queueParam = Expression.Parameter(typeof(RecyclablePriorityQueue)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _sortedDictionaryType)); + _sortedDictionaryAdder = Expression.Lambda, object>>(call, queueParam, objParam).Compile(); + } + } + else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclablePriorityQueueAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclablePriorityQueue<>).MakeGenericType(elementType), _sortedListType }, + modifiers: null); + if (method != null) + { + var queueParam = Expression.Parameter(typeof(RecyclablePriorityQueue)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _sortedListType)); + _sortedListAdder = Expression.Lambda, object>>(call, queueParam, objParam).Compile(); + } + } + } + + internal static bool TryAddRange(RecyclablePriorityQueue queue, IEnumerable items) + { + if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items)) + { + _dictionaryAdder(queue, items); + return true; + } + + if (_sortedDictionaryAdder != null && _sortedDictionaryType!.IsInstanceOfType(items)) + { + _sortedDictionaryAdder(queue, items); + return true; + } + + if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items)) + { + _sortedListAdder(queue, items); + return true; + } + + return false; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(RecyclablePriorityQueue queue, int min) + { + if (queue._heap.Length >= min) + { + return; + } + + int newSize = queue._heap.Length; + do + { + if (newSize >= RecyclableDefaults.MaxPooledBlockSize) + { + newSize = RecyclableDefaults.MaxPooledBlockSize; + break; + } + + newSize <<= 1; + } + while (newSize < min); + + if (newSize < min) + { + newSize = min; + } + + T[] newHeap = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + System.Array.Copy(queue._heap, newHeap, queue._size); + + if (queue._heap.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(queue._heap, RecyclablePriorityQueue._needsClearing); + } + + queue._heap = newHeap; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, ReadOnlySpan items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = items[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, Span items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = items[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, ReadOnlyMemory items) + { + ReadOnlySpan span = items.Span; + int count = span.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = span[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, Memory items) + { + Span span = items.Span; + int count = span.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = span[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, T[] items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = items[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, in Array items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + Array.Copy(items, items.GetLowerBound(0), queue._heap, startIndex, count); + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, List items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = items[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, 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); + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + Array.Copy(buffer, 0, queue._heap, startIndex, count); + queue._size = newCount; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclablePriorityQueue._needsClearing); + } + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, 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); + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + Array.Copy(buffer, 0, queue._heap, startIndex, count); + queue._size = newCount; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclablePriorityQueue._needsClearing); + } + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableList items) + { + int count = items._count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + T[] source = items._memoryBlock; + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = source[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclablePriorityQueue items) + { + int count = items._size; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + T[] source = items._heap; + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = source[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableQueue items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + int count = checked((int)longCount); + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + var sourceChunk = items._head; + int destIndex = startIndex; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + queue._heap[destIndex++] = source[sourceIndex++]; + } + + sourceChunk = sourceChunk.Next; + } + + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableStack items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + int count = checked((int)longCount); + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + var sourceChunk = items._current; + while (sourceChunk.Previous != null) + { + sourceChunk = sourceChunk.Previous; + } + + int destIndex = startIndex; + while (sourceChunk != null) + { + T[] source = sourceChunk.Value; + int sourceCount = sourceChunk.Index; + int sourceIndex = 0; + + while (sourceIndex < sourceCount) + { + queue._heap[destIndex++] = source[sourceIndex++]; + } + + sourceChunk = sourceChunk.Next; + } + + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableLinkedList items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + int count = checked((int)longCount); + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + var sourceChunk = items._head; + int destIndex = startIndex; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + queue._heap[destIndex++] = source[sourceIndex++]; + } + + sourceChunk = sourceChunk.Next; + } + + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableSortedSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + T[] source = items._items; + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = source[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableHashSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = entries[i >> shift][i & mask].Value; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, RecyclableLongList items) + { + long longCount = items._longCount; + if (longCount == 0) + { + return; + } + + int blockSize = items._blockSize; + int fullBlocks = (int)(longCount >> items._blockSizePow2BitShift); + int lastBlockLength = (int)(longCount & items._blockSizeMinus1); + + int startIndex = queue._size; + int newCount = startIndex + checked((int)longCount); + EnsureCapacity(queue, newCount); + + int destIndex = startIndex; + for (int blockIndex = 0; blockIndex < fullBlocks; blockIndex++) + { + T[] block = items._memoryBlocks[blockIndex]; + int sourceIndex = 0; + while (sourceIndex < blockSize) + { + queue._heap[destIndex++] = block[sourceIndex++]; + } + } + + if (lastBlockLength > 0) + { + T[] block = items._memoryBlocks[fullBlocks]; + int sourceIndex = 0; + while (sourceIndex < lastBlockLength) + { + queue._heap[destIndex++] = block[sourceIndex++]; + } + } + + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue> queue, RecyclableDictionary items) + where TKey : notnull + { + int count = items._count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + for (int i = 0; i < count; i++) + { + ref var src = ref entries[i >> shift][i & mask]; + queue._heap[startIndex + i] = new KeyValuePair(src.Key, src.Value); + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + var item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue> queue, RecyclableSortedDictionary items) + where TKey : notnull + { + int count = items.Count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = new KeyValuePair(items.GetKey(i), items.GetValue(i)); + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + var item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue<(TKey Key, TValue Value)> queue, RecyclableSortedList items) + where TKey : notnull + { + int count = items._count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + TKey[] keys = items._keys; + TValue[] values = items._values; + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = (keys[i], values[i]); + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + var item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, IReadOnlyList items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + for (int i = 0; i < count; i++) + { + queue._heap[startIndex + i] = items[i]; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int index = i; + T item = queue._heap[index]; + while (index < half) + { + int child = (index << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], item) >= 0) + { + break; + } + + queue._heap[index] = queue._heap[child]; + index = child; + } + + queue._heap[index] = item; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, IReadOnlyCollection items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + int startIndex = queue._size; + int newCount = startIndex + count; + EnsureCapacity(queue, newCount); + + int index = startIndex; + foreach (T item in items) + { + queue._heap[index++] = item; + } + queue._size = newCount; + + int half = newCount >> 1; + for (int i = half - 1; i >= 0; i--) + { + int idx = i; + T current = queue._heap[idx]; + while (idx < half) + { + int child = (idx << 1) + 1; + int right = child + 1; + if (right < newCount && queue._comparer.Compare(queue._heap[right], queue._heap[child]) < 0) + { + child = right; + } + + if (queue._comparer.Compare(queue._heap[child], current) >= 0) + { + break; + } + + queue._heap[idx] = queue._heap[child]; + idx = child; + } + + queue._heap[idx] = current; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static IEnumerator AddRange(this RecyclablePriorityQueue queue, IEnumerable source) + { + IEnumerator enumerator = source.GetEnumerator(); + while (enumerator.MoveNext()) + { + queue.Enqueue((T)enumerator.Current!); + } + + return enumerator; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclablePriorityQueue queue, IEnumerable items) + { + if (items is RecyclableList recyclableList) + { + AddRange(queue, recyclableList); + } + else if (items is RecyclablePriorityQueue priorityQueue) + { + AddRange(queue, priorityQueue); + } + else if (items is RecyclableQueue recyclableQueue) + { + AddRange(queue, recyclableQueue); + } + else if (items is RecyclableStack recyclableStack) + { + AddRange(queue, recyclableStack); + } + else if (items is RecyclableLinkedList linkedList) + { + AddRange(queue, linkedList); + } + else if (items is RecyclableSortedSet sortedSet) + { + AddRange(queue, sortedSet); + } + else if (items is RecyclableHashSet hashSet) + { + AddRange(queue, hashSet); + } + else if (items is RecyclableLongList longList) + { + AddRange(queue, longList); + } + else if (items is T[] array) + { + AddRange(queue, array); + } + else if (items is List list) + { + AddRange(queue, list); + } + else if (items is ICollection genericCollection) + { + AddRange(queue, genericCollection); + } + else if (items is ICollection collection) + { + AddRange(queue, collection); + } + else if (AddRangeHelper.TryAddRange(queue, items)) + { + } + else if (items is IReadOnlyList readOnlyList) + { + AddRange(queue, readOnlyList); + } + else if (items is IReadOnlyCollection readOnlyCollection) + { + AddRange(queue, readOnlyCollection); + } + else + { + foreach (T item in items) + { + queue.Enqueue(item); + } + } + } + } +} diff --git a/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs b/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs index b9a1df2..44d03d4 100644 --- a/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Recyclable.Collections; +using System.Collections; namespace Recyclable.CollectionsTests { @@ -52,5 +53,443 @@ namespace Recyclable.CollectionsTests using var queue = new RecyclablePriorityQueue(); _ = Assert.Throws(() => queue.Dequeue()); } + + [Fact] + public void AddRangeSpanShouldAddItemsInSortedOrder() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange((ReadOnlySpan)_testData); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeSpanShouldDoNothingWhenEmpty() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(ReadOnlySpan.Empty); + _ = queue.Should().BeEmpty(); + } + + [Fact] + public void AddRangeSpanShouldNotOverrideItems() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange((ReadOnlySpan)_testData); + queue.AddRange((ReadOnlySpan)_testData); + + var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray(); + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeArrayShouldAddItemsInSortedOrder() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(_testData); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeListShouldAddItemsInSortedOrder() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(new List(_testData)); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeReadOnlyMemoryShouldAddItemsInSortedOrder() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange((ReadOnlyMemory)_testData); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeMemoryShouldNotOverrideItems() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(_testData.AsMemory()); + queue.AddRange(_testData.AsMemory()); + + var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray(); + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeArrayShouldNotOverrideItems() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(_testData); + queue.AddRange(_testData); + + var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray(); + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeShouldAcceptNulls() + { + using var queue = new RecyclablePriorityQueue(); + queue.AddRange(new long?[] { null, 1 }); + _ = queue.Should().HaveCount(2).And.Contain((long?)null); + } + + [Fact] + public void AddRangeQueueShouldAddItemsInSortedOrder() + { + using var source = new RecyclableQueue(_testData); + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeQueueShouldNotOverrideItems() + { + using var source = new RecyclableQueue(_testData); + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray(); + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeStackShouldAddItemsInSortedOrder() + { + using var source = new RecyclableStack(_testData); + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeSortedSetShouldAddItemsInSortedOrder() + { + using var source = new RecyclableSortedSet(); + foreach (int item in _testData) + { + source.Add(item); + } + + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeDictionaryShouldAddItemsInSortedOrder() + { + using var source = new RecyclableDictionary(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue>(comparer: Comparer>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + + var result = new List>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => new KeyValuePair(i, v)).ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeDictionaryShouldNotOverrideItems() + { + using var source = new RecyclableDictionary(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue>(comparer: Comparer>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + queue.AddRange(source); + + var result = new List>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => new KeyValuePair(i, v)) + .Concat(_testData.Select((v, i) => new KeyValuePair(i, v))) + .ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeSortedListShouldAddItemsInSortedOrder() + { + using var source = new RecyclableSortedList(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue<(int Key, int Value)>(comparer: Comparer<(int Key, int Value)>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + + var result = new List<(int Key, int Value)>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => (i, v)).ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeSortedListShouldNotOverrideItems() + { + using var source = new RecyclableSortedList(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue<(int Key, int Value)>(comparer: Comparer<(int Key, int Value)>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + queue.AddRange(source); + + var result = new List<(int Key, int Value)>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => (i, v)) + .Concat(_testData.Select((v, i) => (i, v))) + .OrderBy(p => p.Item1) + .ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeSortedDictionaryShouldAddItemsInSortedOrder() + { + using var source = new RecyclableSortedDictionary(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue>(comparer: Comparer>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + + var result = new List>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => new KeyValuePair(i, v)).ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeSortedDictionaryShouldNotOverrideItems() + { + using var source = new RecyclableSortedDictionary(); + for (int i = 0; i < _testData.Length; i++) + { + source.Add(i, _testData[i]); + } + + using var queue = new RecyclablePriorityQueue>(comparer: Comparer>.Create((a, b) => a.Key.CompareTo(b.Key))); + + queue.AddRange(source); + queue.AddRange(source); + + var result = new List>(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Select((v, i) => new KeyValuePair(i, v)) + .Concat(_testData.Select((v, i) => new KeyValuePair(i, v))) + .ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeEnumerableShouldAddItemsInSortedOrder() + { + using var queue = new RecyclablePriorityQueue(); + + _ = queue.AddRange((System.Collections.IEnumerable)_testData); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.OrderBy(v => v).ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeEnumerableShouldNotOverrideItems() + { + using var queue = new RecyclablePriorityQueue(); + + _ = queue.AddRange((System.Collections.IEnumerable)_testData); + _ = queue.AddRange((System.Collections.IEnumerable)_testData); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + var expected = _testData.Concat(_testData).OrderBy(v => v).ToList(); + _ = result.Should().Equal(expected); + } + + [Fact] + public void AddRangeReadOnlyCollectionShouldAddItemsInSortedOrder() + { + var source = new TestReadOnlyCollection(_testData); + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(_testData.OrderBy(static value => value)); + } + + [Fact] + public void AddRangeReadOnlyCollectionShouldNotOverrideItems() + { + var source = new TestReadOnlyCollection(_testData); + using var queue = new RecyclablePriorityQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray(); + var result = new List(); + while (queue.LongCount > 0) + { + result.Add(queue.Dequeue()); + } + + _ = result.Should().Equal(expected); + } + + private sealed class TestReadOnlyCollection : IReadOnlyCollection + { + private readonly T[] _items; + + public TestReadOnlyCollection(T[] items) + { + _items = items; + } + + public int Count => _items.Length; + + public IEnumerator GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + } } } +