From 33187c105363aebfdbf8351d2323ccf8c334de34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Tue, 16 Sep 2025 13:52:29 +0200 Subject: [PATCH] Add dictionary overloads for priority queue --- .../RecyclablePriorityQueue.AddRange.cs | 244 ++++++++++++++++++ .../RecyclablePriorityQueueTests.cs | 148 +++++++++++ 2 files changed, 392 insertions(+) diff --git a/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs b/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs index f406bb5..c7786e2 100644 --- a/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs +++ b/Recyclable.Collections/RecyclablePriorityQueue.AddRange.cs @@ -2,6 +2,8 @@ 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; @@ -9,6 +11,96 @@ 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) { @@ -821,6 +913,155 @@ namespace Recyclable.Collections } } + [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) { @@ -918,6 +1159,9 @@ namespace Recyclable.Collections { AddRange(queue, collection); } + else if (AddRangeHelper.TryAddRange(queue, items)) + { + } else if (items is IReadOnlyList readOnlyList) { AddRange(queue, readOnlyList); diff --git a/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs b/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs index 15903c3..98d90fb 100644 --- a/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclablePriorityQueueTests.cs @@ -223,6 +223,154 @@ namespace Recyclable.CollectionsTests _ = result.Should().Equal(_testData.Order()); } + [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); + } + } }