com.fofx.observething 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.md.meta +7 -0
- package/Runtime/CollectionIdProvider.cs +72 -0
- package/Runtime/CollectionIdProvider.cs.meta +2 -0
- package/Runtime/IObservable.cs +66 -0
- package/Runtime/IObservable.cs.meta +2 -0
- package/Runtime/Implementations/Observable.cs +30 -0
- package/Runtime/Implementations/Observable.cs.meta +2 -0
- package/Runtime/Implementations/ObservableBase.cs +173 -0
- package/Runtime/Implementations/ObservableBase.cs.meta +2 -0
- package/Runtime/Implementations/ObservableCollection.cs +141 -0
- package/Runtime/Implementations/ObservableCollection.cs.meta +2 -0
- package/Runtime/Implementations/ObservableDictionary.cs +166 -0
- package/Runtime/Implementations/ObservableDictionary.cs.meta +2 -0
- package/Runtime/Implementations/ObservableList.cs +163 -0
- package/Runtime/Implementations/ObservableList.cs.meta +2 -0
- package/Runtime/Implementations/ObservableSet.cs +137 -0
- package/Runtime/Implementations/ObservableSet.cs.meta +2 -0
- package/Runtime/Implementations/ObservableValue.cs +59 -0
- package/Runtime/Implementations/ObservableValue.cs.meta +2 -0
- package/Runtime/Implementations.meta +8 -0
- package/Runtime/Observables/BatchObservable.cs +72 -0
- package/Runtime/Observables/BatchObservable.cs.meta +2 -0
- package/Runtime/Observables/CastObservable.cs +128 -0
- package/Runtime/Observables/CastObservable.cs.meta +2 -0
- package/Runtime/Observables/CombineObservable.cs +96 -0
- package/Runtime/Observables/CombineObservable.cs.meta +2 -0
- package/Runtime/Observables/CombineValueObservable.cs +319 -0
- package/Runtime/Observables/CombineValueObservable.cs.meta +2 -0
- package/Runtime/Observables/ConcatObservable.cs +75 -0
- package/Runtime/Observables/ConcatObservable.cs.meta +2 -0
- package/Runtime/Observables/ContainsObservable.cs +71 -0
- package/Runtime/Observables/ContainsObservable.cs.meta +2 -0
- package/Runtime/Observables/CountObservable.cs +29 -0
- package/Runtime/Observables/CountObservable.cs.meta +2 -0
- package/Runtime/Observables/DistinctObservable.cs +55 -0
- package/Runtime/Observables/DistinctObservable.cs.meta +2 -0
- package/Runtime/Observables/FirstObservable.cs +53 -0
- package/Runtime/Observables/FirstObservable.cs.meta +2 -0
- package/Runtime/Observables/IndexOfObservable.cs +83 -0
- package/Runtime/Observables/IndexOfObservable.cs.meta +2 -0
- package/Runtime/Observables/OperationStreamObservable.cs +194 -0
- package/Runtime/Observables/OperationStreamObservable.cs.meta +2 -0
- package/Runtime/Observables/OrderByObservable.cs +116 -0
- package/Runtime/Observables/OrderByObservable.cs.meta +2 -0
- package/Runtime/Observables/SelectCollectionObservable.cs +29 -0
- package/Runtime/Observables/SelectCollectionObservable.cs.meta +2 -0
- package/Runtime/Observables/SelectListObservable.cs +29 -0
- package/Runtime/Observables/SelectListObservable.cs.meta +2 -0
- package/Runtime/Observables/SelectManyObservable.cs +78 -0
- package/Runtime/Observables/SelectManyObservable.cs.meta +2 -0
- package/Runtime/Observables/SelectValueObservable.cs +28 -0
- package/Runtime/Observables/SelectValueObservable.cs.meta +2 -0
- package/Runtime/Observables/SkipWhileObservable.cs +32 -0
- package/Runtime/Observables/SkipWhileObservable.cs.meta +2 -0
- package/Runtime/Observables/ThenObservable.cs +209 -0
- package/Runtime/Observables/ThenObservable.cs.meta +2 -0
- package/Runtime/Observables/ToDictionaryObservable.cs +35 -0
- package/Runtime/Observables/ToDictionaryObservable.cs.meta +2 -0
- package/Runtime/Observables/TrackObservable.cs +74 -0
- package/Runtime/Observables/TrackObservable.cs.meta +2 -0
- package/Runtime/Observables/UnwrapCollectionObservable.cs +64 -0
- package/Runtime/Observables/UnwrapCollectionObservable.cs.meta +2 -0
- package/Runtime/Observables/UnwrapListObservable.cs +69 -0
- package/Runtime/Observables/UnwrapListObservable.cs.meta +2 -0
- package/Runtime/Observables/UnwrapValueObservable.cs +56 -0
- package/Runtime/Observables/UnwrapValueObservable.cs.meta +2 -0
- package/Runtime/Observables/WhereObservable.cs +85 -0
- package/Runtime/Observables/WhereObservable.cs.meta +2 -0
- package/Runtime/Observables/WithPreviousObservable.cs +36 -0
- package/Runtime/Observables/WithPreviousObservable.cs.meta +2 -0
- package/Runtime/Observables.cs +467 -0
- package/Runtime/Observables.cs.meta +2 -0
- package/Runtime/Observables.meta +8 -0
- package/Runtime/ObservationContext.cs +126 -0
- package/Runtime/ObservationContext.cs.meta +2 -0
- package/Runtime/Observers.cs +403 -0
- package/Runtime/Observers.cs.meta +2 -0
- package/Runtime/Operators/BatchOperator.cs +71 -0
- package/Runtime/Operators/BatchOperator.cs.meta +2 -0
- package/Runtime/Operators/CollectionOperator.cs +30 -0
- package/Runtime/Operators/CollectionOperator.cs.meta +2 -0
- package/Runtime/Operators/DictionaryOperator.cs +45 -0
- package/Runtime/Operators/DictionaryOperator.cs.meta +2 -0
- package/Runtime/Operators/IInitializationOperationsProvider.cs +10 -0
- package/Runtime/Operators/IInitializationOperationsProvider.cs.meta +2 -0
- package/Runtime/Operators/ListOperator.cs +44 -0
- package/Runtime/Operators/ListOperator.cs.meta +2 -0
- package/Runtime/Operators/ObservableOperator.cs +75 -0
- package/Runtime/Operators/ObservableOperator.cs.meta +2 -0
- package/Runtime/Operators/Operator.cs +65 -0
- package/Runtime/Operators/Operator.cs.meta +2 -0
- package/Runtime/Operators/SetOperator.cs +44 -0
- package/Runtime/Operators/SetOperator.cs.meta +2 -0
- package/Runtime/Operators/ValueOperator.cs +30 -0
- package/Runtime/Operators/ValueOperator.cs.meta +2 -0
- package/Runtime/Operators.meta +8 -0
- package/Runtime/PriorityQueue.cs +1096 -0
- package/Runtime/PriorityQueue.cs.meta +2 -0
- package/Runtime/com.fofx.observething.asmdef +14 -0
- package/Runtime/com.fofx.observething.asmdef.meta +7 -0
- package/Runtime.meta +8 -0
- package/Tests/CollectionObservableTests.cs +739 -0
- package/Tests/CollectionObservableTests.cs.meta +2 -0
- package/Tests/DictionaryObservableTests.cs +85 -0
- package/Tests/DictionaryObservableTests.cs.meta +2 -0
- package/Tests/ListObservableTests.cs +148 -0
- package/Tests/ListObservableTests.cs.meta +2 -0
- package/Tests/ObservationContextTests.cs +359 -0
- package/Tests/ObservationContextTests.cs.meta +2 -0
- package/Tests/ValueObservableTests.cs +322 -0
- package/Tests/ValueObservableTests.cs.meta +2 -0
- package/Tests/com.fofx.observething.tests.asmdef +24 -0
- package/Tests/com.fofx.observething.tests.asmdef.meta +7 -0
- package/Tests.meta +8 -0
- package/package.json +24 -0
- package/package.json.meta +7 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
// Licensed to the .NET Foundation under one or more agreements.
|
|
2
|
+
// The .NET Foundation licenses this file to you under the MIT license.
|
|
3
|
+
|
|
4
|
+
using System.Diagnostics;
|
|
5
|
+
using System.Diagnostics.CodeAnalysis;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using System.Runtime.CompilerServices;
|
|
8
|
+
|
|
9
|
+
namespace System.Collections.Generic
|
|
10
|
+
{
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// Represents a min priority queue.
|
|
13
|
+
/// </summary>
|
|
14
|
+
/// <typeparam name="TElement">Specifies the type of elements in the queue.</typeparam>
|
|
15
|
+
/// <typeparam name="TPriority">Specifies the type of priority associated with enqueued elements.</typeparam>
|
|
16
|
+
/// <remarks>
|
|
17
|
+
/// Implements an array-backed quaternary min-heap. Each element is enqueued with an associated priority
|
|
18
|
+
/// that determines the dequeue order: elements with the lowest priority get dequeued first.
|
|
19
|
+
/// </remarks>
|
|
20
|
+
[DebuggerDisplay("Count = {Count}")]
|
|
21
|
+
// [DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
|
|
22
|
+
public class PriorityQueue<TElement, TPriority>
|
|
23
|
+
{
|
|
24
|
+
/// <summary>
|
|
25
|
+
/// Represents an implicit heap-ordered complete d-ary tree, stored as an array.
|
|
26
|
+
/// </summary>
|
|
27
|
+
private (TElement Element, TPriority Priority)[] _nodes;
|
|
28
|
+
|
|
29
|
+
/// <summary>
|
|
30
|
+
/// Custom comparer used to order the heap.
|
|
31
|
+
/// </summary>
|
|
32
|
+
private readonly IComparer<TPriority> _comparer;
|
|
33
|
+
|
|
34
|
+
/// <summary>
|
|
35
|
+
/// The number of nodes in the heap.
|
|
36
|
+
/// </summary>
|
|
37
|
+
private int _size;
|
|
38
|
+
|
|
39
|
+
/// <summary>
|
|
40
|
+
/// Version updated on mutation to help validate enumerators operate on a consistent state.
|
|
41
|
+
/// </summary>
|
|
42
|
+
private int _version;
|
|
43
|
+
|
|
44
|
+
/// <summary>
|
|
45
|
+
/// Specifies the arity of the d-ary heap, which here is quaternary.
|
|
46
|
+
/// It is assumed that this value is a power of 2.
|
|
47
|
+
/// </summary>
|
|
48
|
+
private const int Arity = 4;
|
|
49
|
+
|
|
50
|
+
/// <summary>
|
|
51
|
+
/// The binary logarithm of <see cref="Arity" />.
|
|
52
|
+
/// </summary>
|
|
53
|
+
private const int Log2Arity = 2;
|
|
54
|
+
|
|
55
|
+
#if DEBUG
|
|
56
|
+
static PriorityQueue()
|
|
57
|
+
{
|
|
58
|
+
Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity);
|
|
59
|
+
}
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
/// <summary>
|
|
63
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class.
|
|
64
|
+
/// </summary>
|
|
65
|
+
public PriorityQueue()
|
|
66
|
+
{
|
|
67
|
+
_nodes = Array.Empty<(TElement, TPriority)>();
|
|
68
|
+
_comparer = InitializeComparer(null);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// <summary>
|
|
72
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class
|
|
73
|
+
/// with the specified initial capacity.
|
|
74
|
+
/// </summary>
|
|
75
|
+
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
|
76
|
+
/// <exception cref="ArgumentOutOfRangeException">
|
|
77
|
+
/// The specified <paramref name="initialCapacity"/> was negative.
|
|
78
|
+
/// </exception>
|
|
79
|
+
public PriorityQueue(int initialCapacity)
|
|
80
|
+
: this(initialCapacity, comparer: null)
|
|
81
|
+
{
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// <summary>
|
|
85
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class
|
|
86
|
+
/// with the specified custom priority comparer.
|
|
87
|
+
/// </summary>
|
|
88
|
+
/// <param name="comparer">
|
|
89
|
+
/// Custom comparer dictating the ordering of elements.
|
|
90
|
+
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null"/>.
|
|
91
|
+
/// </param>
|
|
92
|
+
public PriorityQueue(IComparer<TPriority> comparer)
|
|
93
|
+
{
|
|
94
|
+
_nodes = Array.Empty<(TElement, TPriority)>();
|
|
95
|
+
_comparer = InitializeComparer(comparer);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// <summary>
|
|
99
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class
|
|
100
|
+
/// with the specified initial capacity and custom priority comparer.
|
|
101
|
+
/// </summary>
|
|
102
|
+
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
|
103
|
+
/// <param name="comparer">
|
|
104
|
+
/// Custom comparer dictating the ordering of elements.
|
|
105
|
+
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null"/>.
|
|
106
|
+
/// </param>
|
|
107
|
+
/// <exception cref="ArgumentOutOfRangeException">
|
|
108
|
+
/// The specified <paramref name="initialCapacity"/> was negative.
|
|
109
|
+
/// </exception>
|
|
110
|
+
public PriorityQueue(int initialCapacity, IComparer<TPriority> comparer)
|
|
111
|
+
{
|
|
112
|
+
if (initialCapacity < 0)
|
|
113
|
+
throw new ArgumentOutOfRangeException(nameof(initialCapacity));
|
|
114
|
+
|
|
115
|
+
_nodes = new (TElement, TPriority)[initialCapacity];
|
|
116
|
+
_comparer = InitializeComparer(comparer);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// <summary>
|
|
120
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class
|
|
121
|
+
/// that is populated with the specified elements and priorities.
|
|
122
|
+
/// </summary>
|
|
123
|
+
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
|
124
|
+
/// <exception cref="ArgumentNullException">
|
|
125
|
+
/// The specified <paramref name="items"/> argument was <see langword="null"/>.
|
|
126
|
+
/// </exception>
|
|
127
|
+
/// <remarks>
|
|
128
|
+
/// Constructs the heap using a heapify operation,
|
|
129
|
+
/// which is generally faster than enqueuing individual elements sequentially.
|
|
130
|
+
/// </remarks>
|
|
131
|
+
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items)
|
|
132
|
+
: this(items, comparer: null)
|
|
133
|
+
{
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// <summary>
|
|
137
|
+
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class
|
|
138
|
+
/// that is populated with the specified elements and priorities,
|
|
139
|
+
/// and with the specified custom priority comparer.
|
|
140
|
+
/// </summary>
|
|
141
|
+
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
|
142
|
+
/// <param name="comparer">
|
|
143
|
+
/// Custom comparer dictating the ordering of elements.
|
|
144
|
+
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null"/>.
|
|
145
|
+
/// </param>
|
|
146
|
+
/// <exception cref="ArgumentNullException">
|
|
147
|
+
/// The specified <paramref name="items"/> argument was <see langword="null"/>.
|
|
148
|
+
/// </exception>
|
|
149
|
+
/// <remarks>
|
|
150
|
+
/// Constructs the heap using a heapify operation,
|
|
151
|
+
/// which is generally faster than enqueuing individual elements sequentially.
|
|
152
|
+
/// </remarks>
|
|
153
|
+
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items, IComparer<TPriority> comparer)
|
|
154
|
+
{
|
|
155
|
+
if (items == null)
|
|
156
|
+
throw new ArgumentNullException(nameof(items));
|
|
157
|
+
|
|
158
|
+
_nodes = EnumerableHelpers.ToArray(items, out _size);
|
|
159
|
+
_comparer = InitializeComparer(comparer);
|
|
160
|
+
|
|
161
|
+
if (_size > 1)
|
|
162
|
+
{
|
|
163
|
+
Heapify();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// <summary>
|
|
168
|
+
/// Gets the number of elements contained in the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
169
|
+
/// </summary>
|
|
170
|
+
public int Count => _size;
|
|
171
|
+
|
|
172
|
+
/// <summary>
|
|
173
|
+
/// Gets the total numbers of elements the queue's backing storage can hold without resizing.
|
|
174
|
+
/// </summary>
|
|
175
|
+
public int Capacity => _nodes.Length;
|
|
176
|
+
|
|
177
|
+
/// <summary>
|
|
178
|
+
/// Gets the priority comparer used by the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
179
|
+
/// </summary>
|
|
180
|
+
public IComparer<TPriority> Comparer => _comparer ?? Comparer<TPriority>.Default;
|
|
181
|
+
|
|
182
|
+
/// <summary>
|
|
183
|
+
/// Gets a collection that enumerates the elements of the queue in an unordered manner.
|
|
184
|
+
/// </summary>
|
|
185
|
+
/// <remarks>
|
|
186
|
+
/// The enumeration does not order items by priority, since that would require N * log(N) time and N space.
|
|
187
|
+
/// Items are instead enumerated following the internal array heap layout.
|
|
188
|
+
/// </remarks>
|
|
189
|
+
public UnorderedItemsCollection UnorderedItems
|
|
190
|
+
{
|
|
191
|
+
get
|
|
192
|
+
{
|
|
193
|
+
if (_UnorderedItems == null)
|
|
194
|
+
_UnorderedItems = new UnorderedItemsCollection(this);
|
|
195
|
+
|
|
196
|
+
return _UnorderedItems;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private UnorderedItemsCollection _UnorderedItems;
|
|
202
|
+
|
|
203
|
+
/// <summary>
|
|
204
|
+
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
205
|
+
/// </summary>
|
|
206
|
+
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}"/>.</param>
|
|
207
|
+
/// <param name="priority">The priority with which to associate the new element.</param>
|
|
208
|
+
public void Enqueue(TElement element, TPriority priority)
|
|
209
|
+
{
|
|
210
|
+
// Virtually add the node at the end of the underlying array.
|
|
211
|
+
// Note that the node being enqueued does not need to be physically placed
|
|
212
|
+
// there at this point, as such an assignment would be redundant.
|
|
213
|
+
|
|
214
|
+
int currentSize = _size;
|
|
215
|
+
_version++;
|
|
216
|
+
|
|
217
|
+
if (_nodes.Length == currentSize)
|
|
218
|
+
{
|
|
219
|
+
Grow(currentSize + 1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_size = currentSize + 1;
|
|
223
|
+
|
|
224
|
+
if (_comparer == null)
|
|
225
|
+
{
|
|
226
|
+
MoveUpDefaultComparer((element, priority), currentSize);
|
|
227
|
+
}
|
|
228
|
+
else
|
|
229
|
+
{
|
|
230
|
+
MoveUpCustomComparer((element, priority), currentSize);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// <summary>
|
|
235
|
+
/// Returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}"/> without removing it.
|
|
236
|
+
/// </summary>
|
|
237
|
+
/// <exception cref="InvalidOperationException">The <see cref="PriorityQueue{TElement, TPriority}"/> is empty.</exception>
|
|
238
|
+
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}"/>.</returns>
|
|
239
|
+
public TElement Peek()
|
|
240
|
+
{
|
|
241
|
+
if (_size == 0)
|
|
242
|
+
{
|
|
243
|
+
throw new InvalidOperationException("Queue is empty.");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return _nodes[0].Element;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// <summary>
|
|
250
|
+
/// Removes and returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
251
|
+
/// </summary>
|
|
252
|
+
/// <exception cref="InvalidOperationException">The queue is empty.</exception>
|
|
253
|
+
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}"/>.</returns>
|
|
254
|
+
public TElement Dequeue()
|
|
255
|
+
{
|
|
256
|
+
if (_size == 0)
|
|
257
|
+
{
|
|
258
|
+
throw new InvalidOperationException("Queue is empty.");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
TElement element = _nodes[0].Element;
|
|
262
|
+
RemoveRootNode();
|
|
263
|
+
return element;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// <summary>
|
|
267
|
+
/// Removes the minimal element and then immediately adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
268
|
+
/// </summary>
|
|
269
|
+
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}"/>.</param>
|
|
270
|
+
/// <param name="priority">The priority with which to associate the new element.</param>
|
|
271
|
+
/// <exception cref="InvalidOperationException">The queue is empty.</exception>
|
|
272
|
+
/// <returns>The minimal element removed before performing the enqueue operation.</returns>
|
|
273
|
+
/// <remarks>
|
|
274
|
+
/// Implements an extract-then-insert heap operation that is generally more efficient
|
|
275
|
+
/// than sequencing Dequeue and Enqueue operations: in the worst case scenario only one
|
|
276
|
+
/// shift-down operation is required.
|
|
277
|
+
/// </remarks>
|
|
278
|
+
public TElement DequeueEnqueue(TElement element, TPriority priority)
|
|
279
|
+
{
|
|
280
|
+
if (_size == 0)
|
|
281
|
+
{
|
|
282
|
+
throw new InvalidOperationException("Queue is empty.");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
(TElement Element, TPriority Priority) root = _nodes[0];
|
|
286
|
+
|
|
287
|
+
if (_comparer == null)
|
|
288
|
+
{
|
|
289
|
+
if (Comparer<TPriority>.Default.Compare(priority, root.Priority) > 0)
|
|
290
|
+
{
|
|
291
|
+
MoveDownDefaultComparer((element, priority), 0);
|
|
292
|
+
}
|
|
293
|
+
else
|
|
294
|
+
{
|
|
295
|
+
_nodes[0] = (element, priority);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else
|
|
299
|
+
{
|
|
300
|
+
if (_comparer.Compare(priority, root.Priority) > 0)
|
|
301
|
+
{
|
|
302
|
+
MoveDownCustomComparer((element, priority), 0);
|
|
303
|
+
}
|
|
304
|
+
else
|
|
305
|
+
{
|
|
306
|
+
_nodes[0] = (element, priority);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_version++;
|
|
311
|
+
return root.Element;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// <summary>
|
|
315
|
+
/// Removes the minimal element from the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
316
|
+
/// and copies it to the <paramref name="element"/> parameter,
|
|
317
|
+
/// and its associated priority to the <paramref name="priority"/> parameter.
|
|
318
|
+
/// </summary>
|
|
319
|
+
/// <param name="element">The removed element.</param>
|
|
320
|
+
/// <param name="priority">The priority associated with the removed element.</param>
|
|
321
|
+
/// <returns>
|
|
322
|
+
/// <see langword="true"/> if the element is successfully removed;
|
|
323
|
+
/// <see langword="false"/> if the <see cref="PriorityQueue{TElement, TPriority}"/> is empty.
|
|
324
|
+
/// </returns>
|
|
325
|
+
public bool TryDequeue([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority)
|
|
326
|
+
{
|
|
327
|
+
if (_size != 0)
|
|
328
|
+
{
|
|
329
|
+
(element, priority) = _nodes[0];
|
|
330
|
+
RemoveRootNode();
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
element = default;
|
|
335
|
+
priority = default;
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// <summary>
|
|
340
|
+
/// Returns a value that indicates whether there is a minimal element in the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
341
|
+
/// and if one is present, copies it to the <paramref name="element"/> parameter,
|
|
342
|
+
/// and its associated priority to the <paramref name="priority"/> parameter.
|
|
343
|
+
/// The element is not removed from the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
344
|
+
/// </summary>
|
|
345
|
+
/// <param name="element">The minimal element in the queue.</param>
|
|
346
|
+
/// <param name="priority">The priority associated with the minimal element.</param>
|
|
347
|
+
/// <returns>
|
|
348
|
+
/// <see langword="true"/> if there is a minimal element;
|
|
349
|
+
/// <see langword="false"/> if the <see cref="PriorityQueue{TElement, TPriority}"/> is empty.
|
|
350
|
+
/// </returns>
|
|
351
|
+
public bool TryPeek([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority)
|
|
352
|
+
{
|
|
353
|
+
if (_size != 0)
|
|
354
|
+
{
|
|
355
|
+
(element, priority) = _nodes[0];
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
element = default;
|
|
360
|
+
priority = default;
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// <summary>
|
|
365
|
+
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
366
|
+
/// and immediately removes the minimal element, returning the result.
|
|
367
|
+
/// </summary>
|
|
368
|
+
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}"/>.</param>
|
|
369
|
+
/// <param name="priority">The priority with which to associate the new element.</param>
|
|
370
|
+
/// <returns>The minimal element removed after the enqueue operation.</returns>
|
|
371
|
+
/// <remarks>
|
|
372
|
+
/// Implements an insert-then-extract heap operation that is generally more efficient
|
|
373
|
+
/// than sequencing Enqueue and Dequeue operations: in the worst case scenario only one
|
|
374
|
+
/// shift-down operation is required.
|
|
375
|
+
/// </remarks>
|
|
376
|
+
public TElement EnqueueDequeue(TElement element, TPriority priority)
|
|
377
|
+
{
|
|
378
|
+
if (_size != 0)
|
|
379
|
+
{
|
|
380
|
+
(TElement Element, TPriority Priority) root = _nodes[0];
|
|
381
|
+
|
|
382
|
+
if (_comparer == null)
|
|
383
|
+
{
|
|
384
|
+
if (Comparer<TPriority>.Default.Compare(priority, root.Priority) > 0)
|
|
385
|
+
{
|
|
386
|
+
MoveDownDefaultComparer((element, priority), 0);
|
|
387
|
+
_version++;
|
|
388
|
+
return root.Element;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else
|
|
392
|
+
{
|
|
393
|
+
if (_comparer.Compare(priority, root.Priority) > 0)
|
|
394
|
+
{
|
|
395
|
+
MoveDownCustomComparer((element, priority), 0);
|
|
396
|
+
_version++;
|
|
397
|
+
return root.Element;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return element;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// <summary>
|
|
406
|
+
/// Enqueues a sequence of element/priority pairs to the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
407
|
+
/// </summary>
|
|
408
|
+
/// <param name="items">The pairs of elements and priorities to add to the queue.</param>
|
|
409
|
+
/// <exception cref="ArgumentNullException">
|
|
410
|
+
/// The specified <paramref name="items"/> argument was <see langword="null"/>.
|
|
411
|
+
/// </exception>
|
|
412
|
+
public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> items)
|
|
413
|
+
{
|
|
414
|
+
if (items == null)
|
|
415
|
+
throw new ArgumentNullException(nameof(items));
|
|
416
|
+
|
|
417
|
+
int count = 0;
|
|
418
|
+
var collection = items as ICollection<(TElement Element, TPriority Priority)>;
|
|
419
|
+
if (collection is not null && (count = collection.Count) > _nodes.Length - _size)
|
|
420
|
+
{
|
|
421
|
+
Grow(checked(_size + count));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (_size == 0)
|
|
425
|
+
{
|
|
426
|
+
// build using Heapify() if the queue is empty.
|
|
427
|
+
|
|
428
|
+
if (collection is not null)
|
|
429
|
+
{
|
|
430
|
+
collection.CopyTo(_nodes, 0);
|
|
431
|
+
_size = count;
|
|
432
|
+
}
|
|
433
|
+
else
|
|
434
|
+
{
|
|
435
|
+
int i = 0;
|
|
436
|
+
(TElement, TPriority)[] nodes = _nodes;
|
|
437
|
+
foreach ((TElement element, TPriority priority) in items)
|
|
438
|
+
{
|
|
439
|
+
if (nodes.Length == i)
|
|
440
|
+
{
|
|
441
|
+
Grow(i + 1);
|
|
442
|
+
nodes = _nodes;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
nodes[i++] = (element, priority);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
_size = i;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
_version++;
|
|
452
|
+
|
|
453
|
+
if (_size > 1)
|
|
454
|
+
{
|
|
455
|
+
Heapify();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else
|
|
459
|
+
{
|
|
460
|
+
foreach ((TElement element, TPriority priority) in items)
|
|
461
|
+
{
|
|
462
|
+
Enqueue(element, priority);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/// <summary>
|
|
468
|
+
/// Enqueues a sequence of elements pairs to the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
469
|
+
/// all associated with the specified priority.
|
|
470
|
+
/// </summary>
|
|
471
|
+
/// <param name="elements">The elements to add to the queue.</param>
|
|
472
|
+
/// <param name="priority">The priority to associate with the new elements.</param>
|
|
473
|
+
/// <exception cref="ArgumentNullException">
|
|
474
|
+
/// The specified <paramref name="elements"/> argument was <see langword="null"/>.
|
|
475
|
+
/// </exception>
|
|
476
|
+
public void EnqueueRange(IEnumerable<TElement> elements, TPriority priority)
|
|
477
|
+
{
|
|
478
|
+
if (elements == null)
|
|
479
|
+
throw new ArgumentNullException(nameof(elements));
|
|
480
|
+
|
|
481
|
+
int count;
|
|
482
|
+
if (elements is ICollection<TElement> collection &&
|
|
483
|
+
(count = collection.Count) > _nodes.Length - _size)
|
|
484
|
+
{
|
|
485
|
+
Grow(checked(_size + count));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (_size == 0)
|
|
489
|
+
{
|
|
490
|
+
// If the queue is empty just append the elements since they all have the same priority.
|
|
491
|
+
|
|
492
|
+
int i = 0;
|
|
493
|
+
(TElement, TPriority)[] nodes = _nodes;
|
|
494
|
+
foreach (TElement element in elements)
|
|
495
|
+
{
|
|
496
|
+
if (nodes.Length == i)
|
|
497
|
+
{
|
|
498
|
+
Grow(i + 1);
|
|
499
|
+
nodes = _nodes;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
nodes[i++] = (element, priority);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
_size = i;
|
|
506
|
+
_version++;
|
|
507
|
+
}
|
|
508
|
+
else
|
|
509
|
+
{
|
|
510
|
+
foreach (TElement element in elements)
|
|
511
|
+
{
|
|
512
|
+
Enqueue(element, priority);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/// <summary>
|
|
518
|
+
/// Removes the first occurrence that equals the specified parameter.
|
|
519
|
+
/// </summary>
|
|
520
|
+
/// <param name="element">The element to try to remove.</param>
|
|
521
|
+
/// <param name="removedElement">The actual element that got removed from the queue.</param>
|
|
522
|
+
/// <param name="priority">The priority value associated with the removed element.</param>
|
|
523
|
+
/// <param name="equalityComparer">The equality comparer governing element equality.</param>
|
|
524
|
+
/// <returns><see langword="true"/> if matching entry was found and removed, <see langword="false"/> otherwise.</returns>
|
|
525
|
+
/// <remarks>
|
|
526
|
+
/// The method performs a linear-time scan of every element in the heap, removing the first value found to match the <paramref name="element"/> parameter.
|
|
527
|
+
/// In case of duplicate entries, what entry does get removed is non-deterministic and does not take priority into account.
|
|
528
|
+
///
|
|
529
|
+
/// If no <paramref name="equalityComparer"/> is specified, <see cref="EqualityComparer{TElement}.Default"/> will be used instead.
|
|
530
|
+
/// </remarks>
|
|
531
|
+
public bool Remove(
|
|
532
|
+
TElement element,
|
|
533
|
+
[MaybeNullWhen(false)] out TElement removedElement,
|
|
534
|
+
[MaybeNullWhen(false)] out TPriority priority,
|
|
535
|
+
IEqualityComparer<TElement> equalityComparer = null)
|
|
536
|
+
{
|
|
537
|
+
int index = FindIndex(element, equalityComparer);
|
|
538
|
+
if (index < 0)
|
|
539
|
+
{
|
|
540
|
+
removedElement = default;
|
|
541
|
+
priority = default;
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
546
|
+
(removedElement, priority) = nodes[index];
|
|
547
|
+
int newSize = --_size;
|
|
548
|
+
|
|
549
|
+
if (index < newSize)
|
|
550
|
+
{
|
|
551
|
+
// We're removing an element from the middle of the heap.
|
|
552
|
+
// Pop the last element in the collection and sift from the removed index.
|
|
553
|
+
(TElement Element, TPriority Priority) lastNode = nodes[newSize];
|
|
554
|
+
|
|
555
|
+
if (_comparer == null)
|
|
556
|
+
{
|
|
557
|
+
if (Comparer<TPriority>.Default.Compare(lastNode.Priority, priority) < 0)
|
|
558
|
+
{
|
|
559
|
+
MoveUpDefaultComparer(lastNode, index);
|
|
560
|
+
}
|
|
561
|
+
else
|
|
562
|
+
{
|
|
563
|
+
MoveDownDefaultComparer(lastNode, index);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else
|
|
567
|
+
{
|
|
568
|
+
if (_comparer.Compare(lastNode.Priority, priority) < 0)
|
|
569
|
+
{
|
|
570
|
+
MoveUpCustomComparer(lastNode, index);
|
|
571
|
+
}
|
|
572
|
+
else
|
|
573
|
+
{
|
|
574
|
+
MoveDownCustomComparer(lastNode, index);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
nodes[newSize] = default;
|
|
580
|
+
_version++;
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/// <summary>
|
|
585
|
+
/// Removes all items from the <see cref="PriorityQueue{TElement, TPriority}"/>.
|
|
586
|
+
/// </summary>
|
|
587
|
+
public void Clear()
|
|
588
|
+
{
|
|
589
|
+
if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
|
|
590
|
+
{
|
|
591
|
+
// Clear the elements so that the gc can reclaim the references
|
|
592
|
+
Array.Clear(_nodes, 0, _size);
|
|
593
|
+
}
|
|
594
|
+
_size = 0;
|
|
595
|
+
_version++;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/// <summary>
|
|
599
|
+
/// Ensures that the <see cref="PriorityQueue{TElement, TPriority}"/> can hold up to
|
|
600
|
+
/// <paramref name="capacity"/> items without further expansion of its backing storage.
|
|
601
|
+
/// </summary>
|
|
602
|
+
/// <param name="capacity">The minimum capacity to be used.</param>
|
|
603
|
+
/// <exception cref="ArgumentOutOfRangeException">
|
|
604
|
+
/// The specified <paramref name="capacity"/> is negative.
|
|
605
|
+
/// </exception>
|
|
606
|
+
/// <returns>The current capacity of the <see cref="PriorityQueue{TElement, TPriority}"/>.</returns>
|
|
607
|
+
public int EnsureCapacity(int capacity)
|
|
608
|
+
{
|
|
609
|
+
if (capacity < 0)
|
|
610
|
+
throw new ArgumentOutOfRangeException(nameof(capacity));
|
|
611
|
+
|
|
612
|
+
if (_nodes.Length < capacity)
|
|
613
|
+
{
|
|
614
|
+
Grow(capacity);
|
|
615
|
+
_version++;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return _nodes.Length;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/// <summary>
|
|
622
|
+
/// Sets the capacity to the actual number of items in the <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
623
|
+
/// if that is less than 90 percent of current capacity.
|
|
624
|
+
/// </summary>
|
|
625
|
+
/// <remarks>
|
|
626
|
+
/// This method can be used to minimize a collection's memory overhead
|
|
627
|
+
/// if no new elements will be added to the collection.
|
|
628
|
+
/// </remarks>
|
|
629
|
+
public void TrimExcess()
|
|
630
|
+
{
|
|
631
|
+
int threshold = (int)(_nodes.Length * 0.9);
|
|
632
|
+
if (_size < threshold)
|
|
633
|
+
{
|
|
634
|
+
Array.Resize(ref _nodes, _size);
|
|
635
|
+
_version++;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/// <summary>
|
|
640
|
+
/// Grows the priority queue to match the specified min capacity.
|
|
641
|
+
/// </summary>
|
|
642
|
+
private void Grow(int minCapacity)
|
|
643
|
+
{
|
|
644
|
+
Debug.Assert(_nodes.Length < minCapacity);
|
|
645
|
+
|
|
646
|
+
const int GrowFactor = 2;
|
|
647
|
+
const int MinimumGrow = 4;
|
|
648
|
+
|
|
649
|
+
int newcapacity = GrowFactor * _nodes.Length;
|
|
650
|
+
|
|
651
|
+
// Allow the queue to grow to maximum possible capacity (~2G elements) before encountering overflow.
|
|
652
|
+
// Note that this check works even when _nodes.Length overflowed thanks to the (uint) cast
|
|
653
|
+
if ((uint)newcapacity > 2000000000) newcapacity = 2000000000;
|
|
654
|
+
|
|
655
|
+
// Ensure minimum growth is respected.
|
|
656
|
+
newcapacity = Math.Max(newcapacity, _nodes.Length + MinimumGrow);
|
|
657
|
+
|
|
658
|
+
// If the computed capacity is still less than specified, set to the original argument.
|
|
659
|
+
// Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
|
|
660
|
+
if (newcapacity < minCapacity) newcapacity = minCapacity;
|
|
661
|
+
|
|
662
|
+
Array.Resize(ref _nodes, newcapacity);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/// <summary>
|
|
666
|
+
/// Removes the node from the root of the heap
|
|
667
|
+
/// </summary>
|
|
668
|
+
private void RemoveRootNode()
|
|
669
|
+
{
|
|
670
|
+
int lastNodeIndex = --_size;
|
|
671
|
+
_version++;
|
|
672
|
+
|
|
673
|
+
if (lastNodeIndex > 0)
|
|
674
|
+
{
|
|
675
|
+
(TElement Element, TPriority Priority) lastNode = _nodes[lastNodeIndex];
|
|
676
|
+
if (_comparer == null)
|
|
677
|
+
{
|
|
678
|
+
MoveDownDefaultComparer(lastNode, 0);
|
|
679
|
+
}
|
|
680
|
+
else
|
|
681
|
+
{
|
|
682
|
+
MoveDownCustomComparer(lastNode, 0);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
|
|
687
|
+
{
|
|
688
|
+
_nodes[lastNodeIndex] = default;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/// <summary>
|
|
693
|
+
/// Gets the index of an element's parent.
|
|
694
|
+
/// </summary>
|
|
695
|
+
private static int GetParentIndex(int index) => (index - 1) >> Log2Arity;
|
|
696
|
+
|
|
697
|
+
/// <summary>
|
|
698
|
+
/// Gets the index of the first child of an element.
|
|
699
|
+
/// </summary>
|
|
700
|
+
private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1;
|
|
701
|
+
|
|
702
|
+
/// <summary>
|
|
703
|
+
/// Converts an unordered list into a heap.
|
|
704
|
+
/// </summary>
|
|
705
|
+
private void Heapify()
|
|
706
|
+
{
|
|
707
|
+
// Leaves of the tree are in fact 1-element heaps, for which there
|
|
708
|
+
// is no need to correct them. The heap property needs to be restored
|
|
709
|
+
// only for higher nodes, starting from the first node that has children.
|
|
710
|
+
// It is the parent of the very last element in the array.
|
|
711
|
+
|
|
712
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
713
|
+
int lastParentWithChildren = GetParentIndex(_size - 1);
|
|
714
|
+
|
|
715
|
+
if (_comparer == null)
|
|
716
|
+
{
|
|
717
|
+
for (int index = lastParentWithChildren; index >= 0; --index)
|
|
718
|
+
{
|
|
719
|
+
MoveDownDefaultComparer(nodes[index], index);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else
|
|
723
|
+
{
|
|
724
|
+
for (int index = lastParentWithChildren; index >= 0; --index)
|
|
725
|
+
{
|
|
726
|
+
MoveDownCustomComparer(nodes[index], index);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/// <summary>
|
|
732
|
+
/// Moves a node up in the tree to restore heap order.
|
|
733
|
+
/// </summary>
|
|
734
|
+
private void MoveUpDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
735
|
+
{
|
|
736
|
+
// Instead of swapping items all the way to the root, we will perform
|
|
737
|
+
// a similar optimization as in the insertion sort.
|
|
738
|
+
|
|
739
|
+
Debug.Assert(_comparer is null);
|
|
740
|
+
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
741
|
+
|
|
742
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
743
|
+
|
|
744
|
+
while (nodeIndex > 0)
|
|
745
|
+
{
|
|
746
|
+
int parentIndex = GetParentIndex(nodeIndex);
|
|
747
|
+
(TElement Element, TPriority Priority) parent = nodes[parentIndex];
|
|
748
|
+
|
|
749
|
+
if (Comparer<TPriority>.Default.Compare(node.Priority, parent.Priority) < 0)
|
|
750
|
+
{
|
|
751
|
+
nodes[nodeIndex] = parent;
|
|
752
|
+
nodeIndex = parentIndex;
|
|
753
|
+
}
|
|
754
|
+
else
|
|
755
|
+
{
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
nodes[nodeIndex] = node;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/// <summary>
|
|
764
|
+
/// Moves a node up in the tree to restore heap order.
|
|
765
|
+
/// </summary>
|
|
766
|
+
private void MoveUpCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
767
|
+
{
|
|
768
|
+
// Instead of swapping items all the way to the root, we will perform
|
|
769
|
+
// a similar optimization as in the insertion sort.
|
|
770
|
+
|
|
771
|
+
Debug.Assert(_comparer is not null);
|
|
772
|
+
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
773
|
+
|
|
774
|
+
IComparer<TPriority> comparer = _comparer;
|
|
775
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
776
|
+
|
|
777
|
+
while (nodeIndex > 0)
|
|
778
|
+
{
|
|
779
|
+
int parentIndex = GetParentIndex(nodeIndex);
|
|
780
|
+
(TElement Element, TPriority Priority) parent = nodes[parentIndex];
|
|
781
|
+
|
|
782
|
+
if (comparer.Compare(node.Priority, parent.Priority) < 0)
|
|
783
|
+
{
|
|
784
|
+
nodes[nodeIndex] = parent;
|
|
785
|
+
nodeIndex = parentIndex;
|
|
786
|
+
}
|
|
787
|
+
else
|
|
788
|
+
{
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
nodes[nodeIndex] = node;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/// <summary>
|
|
797
|
+
/// Moves a node down in the tree to restore heap order.
|
|
798
|
+
/// </summary>
|
|
799
|
+
private void MoveDownDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
800
|
+
{
|
|
801
|
+
// The node to move down will not actually be swapped every time.
|
|
802
|
+
// Rather, values on the affected path will be moved up, thus leaving a free spot
|
|
803
|
+
// for this value to drop in. Similar optimization as in the insertion sort.
|
|
804
|
+
|
|
805
|
+
Debug.Assert(_comparer is null);
|
|
806
|
+
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
807
|
+
|
|
808
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
809
|
+
int size = _size;
|
|
810
|
+
|
|
811
|
+
int i;
|
|
812
|
+
while ((i = GetFirstChildIndex(nodeIndex)) < size)
|
|
813
|
+
{
|
|
814
|
+
// Find the child node with the minimal priority
|
|
815
|
+
(TElement Element, TPriority Priority) minChild = nodes[i];
|
|
816
|
+
int minChildIndex = i;
|
|
817
|
+
|
|
818
|
+
int childIndexUpperBound = Math.Min(i + Arity, size);
|
|
819
|
+
while (++i < childIndexUpperBound)
|
|
820
|
+
{
|
|
821
|
+
(TElement Element, TPriority Priority) nextChild = nodes[i];
|
|
822
|
+
if (Comparer<TPriority>.Default.Compare(nextChild.Priority, minChild.Priority) < 0)
|
|
823
|
+
{
|
|
824
|
+
minChild = nextChild;
|
|
825
|
+
minChildIndex = i;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Heap property is satisfied; insert node in this location.
|
|
830
|
+
if (Comparer<TPriority>.Default.Compare(node.Priority, minChild.Priority) <= 0)
|
|
831
|
+
{
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Move the minimal child up by one node and
|
|
836
|
+
// continue recursively from its location.
|
|
837
|
+
nodes[nodeIndex] = minChild;
|
|
838
|
+
nodeIndex = minChildIndex;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
nodes[nodeIndex] = node;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/// <summary>
|
|
845
|
+
/// Moves a node down in the tree to restore heap order.
|
|
846
|
+
/// </summary>
|
|
847
|
+
private void MoveDownCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
848
|
+
{
|
|
849
|
+
// The node to move down will not actually be swapped every time.
|
|
850
|
+
// Rather, values on the affected path will be moved up, thus leaving a free spot
|
|
851
|
+
// for this value to drop in. Similar optimization as in the insertion sort.
|
|
852
|
+
|
|
853
|
+
Debug.Assert(_comparer is not null);
|
|
854
|
+
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
855
|
+
|
|
856
|
+
IComparer<TPriority> comparer = _comparer;
|
|
857
|
+
(TElement Element, TPriority Priority)[] nodes = _nodes;
|
|
858
|
+
int size = _size;
|
|
859
|
+
|
|
860
|
+
int i;
|
|
861
|
+
while ((i = GetFirstChildIndex(nodeIndex)) < size)
|
|
862
|
+
{
|
|
863
|
+
// Find the child node with the minimal priority
|
|
864
|
+
(TElement Element, TPriority Priority) minChild = nodes[i];
|
|
865
|
+
int minChildIndex = i;
|
|
866
|
+
|
|
867
|
+
int childIndexUpperBound = Math.Min(i + Arity, size);
|
|
868
|
+
while (++i < childIndexUpperBound)
|
|
869
|
+
{
|
|
870
|
+
(TElement Element, TPriority Priority) nextChild = nodes[i];
|
|
871
|
+
if (comparer.Compare(nextChild.Priority, minChild.Priority) < 0)
|
|
872
|
+
{
|
|
873
|
+
minChild = nextChild;
|
|
874
|
+
minChildIndex = i;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Heap property is satisfied; insert node in this location.
|
|
879
|
+
if (comparer.Compare(node.Priority, minChild.Priority) <= 0)
|
|
880
|
+
{
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Move the minimal child up by one node and continue recursively from its location.
|
|
885
|
+
nodes[nodeIndex] = minChild;
|
|
886
|
+
nodeIndex = minChildIndex;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
nodes[nodeIndex] = node;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/// <summary>
|
|
893
|
+
/// Scans the heap for the first index containing an element equal to the specified parameter.
|
|
894
|
+
/// </summary>
|
|
895
|
+
private int FindIndex(TElement element, IEqualityComparer<TElement> equalityComparer)
|
|
896
|
+
{
|
|
897
|
+
equalityComparer ??= EqualityComparer<TElement>.Default;
|
|
898
|
+
ReadOnlySpan<(TElement Element, TPriority Priority)> nodes = _nodes.AsSpan(0, _size);
|
|
899
|
+
|
|
900
|
+
// Currently the JIT doesn't optimize direct EqualityComparer<T>.Default.Equals
|
|
901
|
+
// calls for reference types, so we want to cache the comparer instance instead.
|
|
902
|
+
// TODO https://github.com/dotnet/runtime/issues/10050: Update if this changes in the future.
|
|
903
|
+
if (typeof(TElement).IsValueType && equalityComparer == EqualityComparer<TElement>.Default)
|
|
904
|
+
{
|
|
905
|
+
for (int i = 0; i < nodes.Length; i++)
|
|
906
|
+
{
|
|
907
|
+
if (EqualityComparer<TElement>.Default.Equals(element, nodes[i].Element))
|
|
908
|
+
{
|
|
909
|
+
return i;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
else
|
|
914
|
+
{
|
|
915
|
+
for (int i = 0; i < nodes.Length; i++)
|
|
916
|
+
{
|
|
917
|
+
if (equalityComparer.Equals(element, nodes[i].Element))
|
|
918
|
+
{
|
|
919
|
+
return i;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return -1;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/// <summary>
|
|
928
|
+
/// Initializes the custom comparer to be used internally by the heap.
|
|
929
|
+
/// </summary>
|
|
930
|
+
private static IComparer<TPriority> InitializeComparer(IComparer<TPriority> comparer)
|
|
931
|
+
{
|
|
932
|
+
if (typeof(TPriority).IsValueType)
|
|
933
|
+
{
|
|
934
|
+
if (comparer == Comparer<TPriority>.Default)
|
|
935
|
+
{
|
|
936
|
+
// if the user manually specifies the default comparer,
|
|
937
|
+
// revert to using the optimized path.
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return comparer;
|
|
942
|
+
}
|
|
943
|
+
else
|
|
944
|
+
{
|
|
945
|
+
// Currently the JIT doesn't optimize direct Comparer<T>.Default.Compare
|
|
946
|
+
// calls for reference types, so we want to cache the comparer instance instead.
|
|
947
|
+
// TODO https://github.com/dotnet/runtime/issues/10050: Update if this changes in the future.
|
|
948
|
+
return comparer ?? Comparer<TPriority>.Default;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/// <summary>
|
|
953
|
+
/// Enumerates the contents of a <see cref="PriorityQueue{TElement, TPriority}"/>, without any ordering guarantees.
|
|
954
|
+
/// </summary>
|
|
955
|
+
[DebuggerDisplay("Count = {Count}")]
|
|
956
|
+
public sealed class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>, ICollection
|
|
957
|
+
{
|
|
958
|
+
internal readonly PriorityQueue<TElement, TPriority> _queue;
|
|
959
|
+
|
|
960
|
+
internal UnorderedItemsCollection(PriorityQueue<TElement, TPriority> queue) => _queue = queue;
|
|
961
|
+
|
|
962
|
+
public int Count => _queue._size;
|
|
963
|
+
object ICollection.SyncRoot => this;
|
|
964
|
+
bool ICollection.IsSynchronized => false;
|
|
965
|
+
|
|
966
|
+
void ICollection.CopyTo(Array array, int index)
|
|
967
|
+
{
|
|
968
|
+
if (array == null)
|
|
969
|
+
throw new ArgumentNullException(nameof(array));
|
|
970
|
+
|
|
971
|
+
if (array.Rank != 1)
|
|
972
|
+
{
|
|
973
|
+
throw new ArgumentException("array must be single dimensional.");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (array.GetLowerBound(0) != 0)
|
|
977
|
+
{
|
|
978
|
+
throw new ArgumentException("array's lower bound must be 0.");
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (index < 0 || index > array.Length)
|
|
982
|
+
{
|
|
983
|
+
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be at least 0 and less than array.Length");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (array.Length - index < _queue._size)
|
|
987
|
+
{
|
|
988
|
+
throw new ArgumentException("SR.Argument_InvalidOffLen");
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
try
|
|
992
|
+
{
|
|
993
|
+
Array.Copy(_queue._nodes, 0, array, index, _queue._size);
|
|
994
|
+
}
|
|
995
|
+
catch (ArrayTypeMismatchException)
|
|
996
|
+
{
|
|
997
|
+
throw new ArgumentException("array is of the incorrect type.");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/// <summary>
|
|
1002
|
+
/// Enumerates the element and priority pairs of a <see cref="PriorityQueue{TElement, TPriority}"/>,
|
|
1003
|
+
/// without any ordering guarantees.
|
|
1004
|
+
/// </summary>
|
|
1005
|
+
public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>
|
|
1006
|
+
{
|
|
1007
|
+
private readonly PriorityQueue<TElement, TPriority> _queue;
|
|
1008
|
+
private readonly int _version;
|
|
1009
|
+
private int _index;
|
|
1010
|
+
private (TElement, TPriority) _current;
|
|
1011
|
+
|
|
1012
|
+
internal Enumerator(PriorityQueue<TElement, TPriority> queue)
|
|
1013
|
+
{
|
|
1014
|
+
_queue = queue;
|
|
1015
|
+
_index = 0;
|
|
1016
|
+
_version = queue._version;
|
|
1017
|
+
_current = default;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/// <summary>
|
|
1021
|
+
/// Releases all resources used by the <see cref="Enumerator"/>.
|
|
1022
|
+
/// </summary>
|
|
1023
|
+
public void Dispose() { }
|
|
1024
|
+
|
|
1025
|
+
/// <summary>
|
|
1026
|
+
/// Advances the enumerator to the next element of the <see cref="UnorderedItems"/>.
|
|
1027
|
+
/// </summary>
|
|
1028
|
+
/// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
|
|
1029
|
+
public bool MoveNext()
|
|
1030
|
+
{
|
|
1031
|
+
PriorityQueue<TElement, TPriority> localQueue = _queue;
|
|
1032
|
+
|
|
1033
|
+
if (_version != localQueue._version)
|
|
1034
|
+
{
|
|
1035
|
+
throw new Exception("Version check failed.");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if ((uint)_index < (uint)localQueue._size)
|
|
1039
|
+
{
|
|
1040
|
+
_current = localQueue._nodes[_index];
|
|
1041
|
+
_index++;
|
|
1042
|
+
return true;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
_current = default;
|
|
1046
|
+
_index = -1;
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/// <summary>
|
|
1051
|
+
/// Gets the element at the current position of the enumerator.
|
|
1052
|
+
/// </summary>
|
|
1053
|
+
public (TElement Element, TPriority Priority) Current => _current;
|
|
1054
|
+
object IEnumerator.Current => _current;
|
|
1055
|
+
|
|
1056
|
+
void IEnumerator.Reset()
|
|
1057
|
+
{
|
|
1058
|
+
if (_version != _queue._version)
|
|
1059
|
+
{
|
|
1060
|
+
throw new Exception("Version check failed.");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
_index = 0;
|
|
1064
|
+
_current = default;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/// <summary>
|
|
1069
|
+
/// Returns an enumerator that iterates through the <see cref="UnorderedItems"/>.
|
|
1070
|
+
/// </summary>
|
|
1071
|
+
/// <returns>An <see cref="Enumerator"/> for the <see cref="UnorderedItems"/>.</returns>
|
|
1072
|
+
public Enumerator GetEnumerator() => new Enumerator(_queue);
|
|
1073
|
+
|
|
1074
|
+
IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.GetEnumerator() =>
|
|
1075
|
+
_queue.Count == 0 ? EnumerableHelpers.GetEmptyEnumerator<(TElement Element, TPriority Priority)>() :
|
|
1076
|
+
GetEnumerator();
|
|
1077
|
+
|
|
1078
|
+
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<(TElement Element, TPriority Priority)>)this).GetEnumerator();
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
public static class EnumerableHelpers
|
|
1083
|
+
{
|
|
1084
|
+
public static IEnumerator<T> GetEmptyEnumerator<T>()
|
|
1085
|
+
{
|
|
1086
|
+
yield break;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
public static T[] ToArray<T>(IEnumerable<T> source, out int length)
|
|
1090
|
+
{
|
|
1091
|
+
var result = source.ToArray();
|
|
1092
|
+
length = result.Length;
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|