< Summary

Information
Class: CounterpointCollective.Collections.UniquePriorityQueue<T1, T2, T3>
Assembly: CounterpointCollective.UniquePriorityQueue
File(s): /builds/counterpointcollective/prestoprimitives/UniquePriorityQueue/Collections/UniquePriorityQueue.cs
Line coverage
87%
Covered lines: 69
Uncovered lines: 10
Coverable lines: 79
Total lines: 281
Line coverage: 87.3%
Branch coverage
85%
Covered branches: 46
Total branches: 54
Branch coverage: 85.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Count()100%11100%
.ctor()100%11100%
Enqueue(...)100%44100%
TryDequeue(...)100%66100%
MinimumPrioUsingQueue(...)78.57%181473.33%
MinimumPrioUsingLinearDictScan(...)100%88100%
TryPeek(...)100%11100%
FindMinimum(...)100%22100%
Peek()100%22100%
Remove(...)50%2262.5%
TryGet(...)100%22100%
Get(...)0%620%
Clear()100%11100%
AmortizedCleanupIfNecessary()83.33%131280%

File(s)

/builds/counterpointcollective/prestoprimitives/UniquePriorityQueue/Collections/UniquePriorityQueue.cs

#LineLine coverage
 1using System.Diagnostics.CodeAnalysis;
 2
 3namespace CounterpointCollective.Collections
 4{
 5
 6    /// <summary>
 7    /// A priority queue where each key is unique and associated with a value and a priority.
 8    /// When <see cref="Enqueue"/> is called for a key already existing in the queue, its associated value and priority 
 9    /// Dequeue operations always return the item with the highest priority.
 10    /// </summary>
 11    /// <typeparam name="K">The type of keys in the queue. Must be non-nullable.</typeparam>
 12    /// <typeparam name="V">The type of values associated with the keys.</typeparam>
 13    /// <typeparam name="P">The type used for priority comparisons. Typically implements <see cref="IComparable{P}"/> or
 14    public class UniquePriorityQueue<K, V, P> where K : notnull
 15    {
 916        private readonly Dictionary<K, (V Value, P Priority)> _dictionary = [];
 17        private readonly PriorityQueue<K, P> _priorityQueue;
 18        private readonly IComparer<P> _priorityComparer;
 19
 20        private const int CutOffMayUseQueueCount = 12;
 21        private const int CutoffMustUseQueueCount = 32;
 22        private bool usingPriorityQueue = false;
 23
 24        /// <summary>
 25        /// Get the number of unique keys currently in the queue.
 26        /// </summary>
 127        public int Count => _dictionary.Count;
 28
 29        /// <summary>
 30        /// Initializes a new instance of the <see cref="UniquePriorityQueue{K,V,P}"/> class
 31        /// using the default priority comparer for type <typeparamref name="P"/>.
 32        /// </summary>
 933        public UniquePriorityQueue() : this(Comparer<P>.Default)
 34        {
 935        }
 36
 37        /// <summary>
 38        /// Initializes a new instance of the <see cref="UniquePriorityQueue{K,V,P}"/> class
 39        /// using the specified priority comparer.
 40        /// </summary>
 41        /// <param name="priorityComparer">
 42        /// An <see cref="IComparer{P}"/> implementation used to determine the ordering of priorities in the queue.
 43        /// </param>
 944        public UniquePriorityQueue(IComparer<P> priorityComparer)
 45        {
 946            _priorityQueue = new(comparer: priorityComparer);
 947            _priorityComparer = _priorityQueue.Comparer;
 948        }
 49
 50        /// <summary>
 51        /// Adds a key, value, and priority to the queue.
 52        /// If the key already exists, its value and priority are updated.
 53        /// </summary>
 54        /// <param name="k">The key to add or update in the queue. Must be unique.</param>
 55        /// <param name="v">The value associated with the key.</param>
 56        /// <param name="p">The priority of the item.</param>
 57        /// <remarks>
 58        /// The operation has an amortized time complexity of O(log n).
 59        /// </remarks>
 60        public void Enqueue(K k, V v, P p)
 61        {
 21362            _dictionary[k] = (v, p);
 63
 21364            if (!AmortizedCleanupIfNecessary() && usingPriorityQueue)
 65            {
 13766                _priorityQueue.Enqueue(k, p);
 67            }
 21368        }
 69
 70        /// <summary>
 71        /// Attempts to remove and return the item with the highest priority from the queue.
 72        /// </summary>
 73        /// <param name="key">When this method returns, contains the key of the removed item, if successful; otherwise, 
 74        /// <param name="value">When this method returns, contains the value of the removed item, if successful; otherwi
 75        /// <param name="highestPriority">When this method returns, contains the priority of the removed item, if succes
 76        /// <returns><c>true</c> if an item was successfully removed; otherwise, <c>false</c> if the queue is empty.</re
 77        /// <remarks>
 78        /// The operation has an amortized time complexity of O(log n).
 79        /// </remarks>
 80        public bool TryDequeue([MaybeNullWhen(false)] out K key, [MaybeNullWhen(false)] out V value, [MaybeNullWhen(fals
 81        {
 10782            if (FindMinimum(true, out key, out value, out highestPriority))
 83            {
 10584                if (usingPriorityQueue && _dictionary.Count < CutOffMayUseQueueCount)
 85                {
 186                    _priorityQueue.Clear();
 187                    usingPriorityQueue = false;
 88                }
 10589                return true;
 90            }
 91            else
 92            {
 293                return false;
 94            }
 95        }
 96
 97
 98        private bool MinimumPrioUsingQueue(bool remove, [MaybeNullWhen(false)] out K key, [MaybeNullWhen(false)] out V v
 99        {
 180100            while (remove ? _priorityQueue.TryDequeue(out key, out highestPriority) : _priorityQueue.TryPeek(out key, ou
 101            {
 180102                var keyWasInDict = remove
 180103                    ? _dictionary.Remove(key, out (V Value, P Priority) vpFromDict)  //we optimistically remove. It'll p
 180104                    : _dictionary.TryGetValue(key, out vpFromDict);
 180105                if (keyWasInDict)
 106                {
 180107                    var currentPriority = vpFromDict.Priority;
 180108                    if (_priorityComparer.Compare(highestPriority, currentPriority) == 0)
 109                    {
 179110                        value = vpFromDict.Value;
 179111                        return true;
 112                    }
 1113                    else if (remove)
 114                    {
 115                        // Our optimism was a case of premature swag; the priority was stale.
 116                        // So we put it back.
 1117                        _dictionary[key] = vpFromDict;
 118                    }
 119                }
 120                else
 121                {
 0122                    if (!remove)
 123                    {
 0124                        _priorityQueue.Dequeue(); // stale entry, and we didn't dequeue it yet.
 125                    }
 126                }
 127            }
 0128            value = default;
 0129            return false;
 130        }
 131
 132        private bool MinimumPrioUsingLinearDictScan(bool remove, [MaybeNullWhen(false)] out K k, [MaybeNullWhen(false)] 
 133        {
 31134            using var e = _dictionary.GetEnumerator();
 47135            if (!e.MoveNext()) { k = default; v = default; p = default; return false; }
 81136            k = e.Current.Key; v = e.Current.Value.Value; p = e.Current.Value.Priority;
 142137            while (e.MoveNext())
 138            {
 115139                if (_priorityComparer.Compare(e.Current.Value.Priority, p) < 0)
 140                {
 339141                    k = e.Current.Key; v = e.Current.Value.Value; p = e.Current.Value.Priority;
 142                }
 143            }
 27144            if (remove)
 145            {
 15146                _dictionary.Remove(k);
 147            }
 27148            return true;
 31149        }
 150
 151        /// <summary>
 152        /// Attempts to return the item with the highest priority from the queue without removing it.
 153        /// </summary>
 154        /// <param name="key">When this method returns, contains the key of the highest-priority item, if successful; ot
 155        /// <param name="value">When this method returns, contains the value of the highest-priority item, if successful
 156        /// <param name="highestPriority">When this method returns, contains the priority of the highest-priority item, 
 157        /// <returns><c>true</c> if the queue contains at least one item; otherwise, <c>false</c>.</returns>
 158        /// <remarks>
 159        /// The operation has an amortized time complexity of O(log n).
 160        /// </remarks>
 161        public bool TryPeek([MaybeNullWhen(false)] out K key, [MaybeNullWhen(false)] out V value, [MaybeNullWhen(false)]
 103162        => FindMinimum(false, out key, out value, out highestPriority);
 163
 164        private bool FindMinimum(bool remove, [MaybeNullWhen(false)] out K key, [MaybeNullWhen(false)] out V value, [May
 165        =>
 210166            usingPriorityQueue
 210167            ? MinimumPrioUsingQueue(remove, out key, out value, out highestPriority)
 210168            : MinimumPrioUsingLinearDictScan(remove, out key, out value, out highestPriority);
 169
 170        /// <summary>
 171        /// Returns the item with the highest priority without removing it from the queue.
 172        /// </summary>
 173        /// <returns>A tuple containing the key, value, and priority of the highest-priority item.</returns>
 174        /// <exception cref="InvalidOperationException">Thrown if the queue is empty.</exception>
 175        /// <remarks>
 176        /// The operation has an amortized time complexity of O(log n).
 177        /// </remarks>
 178        public (K key, V value, P priority) Peek() =>
 2179            TryPeek(out var key, out var value, out var priority)
 2180            ? (key, value, priority)
 2181            : throw new InvalidOperationException("Queue is empty");
 182
 183        /// <summary>
 184        /// Removes the item with the specified key from the queue, if it exists.
 185        /// </summary>
 186        /// <param name="s">The key of the item to remove.</param>
 187        /// <param name="value">When this method returns, contains the value of the removed item, if successful; otherwi
 188        /// <param name="priority">When this method returns, contains the priority of the removed item, if successful; o
 189        /// <returns><c>true</c> if the item was found and removed; otherwise, <c>false</c>.</returns>
 190        /// <remarks>
 191        /// The operation has an amortized time complexity of O(log n).
 192        /// </remarks>
 193        public bool Remove(K s, [MaybeNullWhen(false)] out V value, [MaybeNullWhen(false)] out P priority)
 194        {
 1195            if (_dictionary.Remove(s, out var kv))
 196            {
 1197                value = kv.Value;
 1198                priority = kv.Priority;
 1199                AmortizedCleanupIfNecessary();
 1200                return true;
 201            }
 202            else
 203            {
 0204                value = default;
 0205                priority = default;
 0206                return false;
 207            }
 208        }
 209
 210        /// <summary>
 211        /// Attempts to get the value and priority associated with the specified key without removing it from the queue.
 212        /// </summary>
 213        /// <param name="k">The key of the item to look up.</param>
 214        /// <param name="v">When this method returns, contains the value associated with the key, if found; otherwise, t
 215        /// <param name="p">When this method returns, contains the priority associated with the key, if found; otherwise
 216        /// <returns><c>true</c> if the key exists in the queue; otherwise, <c>false</c>.</returns>
 217        /// <remarks>
 218        /// The operation has a time complexity of O(1).
 219        /// </remarks>
 220        public bool TryGet(K k, [MaybeNullWhen(false)] out V v, [MaybeNullWhen(false)] out P p)
 221        {
 3222            if (_dictionary.TryGetValue(k, out var vp))
 223            {
 1224                v = vp.Value;
 1225                p = vp.Priority;
 1226                return true;
 227            }
 228            else
 229            {
 2230                v = default;
 2231                p = default;
 2232                return false;
 233            }
 234        }
 235
 236        /// <summary>
 237        /// Returns the value and priority associated with the specified key.
 238        /// </summary>
 239        /// <param name="k">The key of the item to retrieve.</param>
 240        /// <returns>A tuple containing the value and priority associated with the key.</returns>
 241        /// <exception cref="KeyNotFoundException">Thrown if the specified key does not exist in the queue.</exception>
 242        /// <remarks>
 243        /// The operation has a time complexity of O(1).
 244        /// </remarks>
 245        public (V Value, P Priority) Get(K k) =>
 0246            TryGet(k, out var v, out var p) ? (v, p) : throw new KeyNotFoundException();
 247
 248        /// <summary>
 249        /// Removes all items from the queue.
 250        /// </summary>
 251        public void Clear()
 252        {
 1253            _dictionary.Clear();
 1254            _priorityQueue.Clear();
 1255        }
 256
 257        /// <returns>Whether we rebuilt the queue</returns>
 258        private bool AmortizedCleanupIfNecessary()
 259        {
 214260            var c = _dictionary.Count;
 214261            if (usingPriorityQueue && (c < CutOffMayUseQueueCount || _priorityQueue.Count > c * 2))
 262            {
 263                //Ditch the queue: There are too many stale entries, or our Count is simply too small.
 0264                _priorityQueue.Clear();
 0265                usingPriorityQueue = false;
 266            }
 267
 214268            if (!usingPriorityQueue && c >= CutoffMustUseQueueCount)
 269            {
 2270                usingPriorityQueue = true;
 132271                foreach (var (k, (v, p)) in _dictionary)
 272                {
 64273                    _priorityQueue.Enqueue(k, p);
 274                }
 2275                return true;
 276            }
 277
 212278            return false;
 279        }
 280    }
 281}