// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // QueuedMap.cs // // // A key-value pair queue, where pushing an existing key into the collection overwrites // the existing value. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Threading.Tasks.Dataflow.Internal { /// /// Provides a data structure that supports pushing and popping key/value pairs. /// Pushing a key/value pair for which the key already exists results in overwriting /// the existing key entry's value. /// /// Specifies the type of keys in the map. /// Specifies the type of values in the map. /// This type is not thread-safe. [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(EnumerableDebugView<,>))] internal sealed class QueuedMap { /// /// A queue structure that uses an array-based list to store its items /// and that supports overwriting elements at specific indices. /// /// The type of the items storedin the queue /// This type is not thread-safe. private sealed class ArrayBasedLinkedQueue { /// Terminator index. private const int TERMINATOR_INDEX = -1; /// /// The queue where the items will be stored. /// The key of each entry is the index of the next entry in the queue. /// private readonly List> _storage; /// Index of the first queue item. private int _headIndex = TERMINATOR_INDEX; /// Index of the last queue item. private int _tailIndex = TERMINATOR_INDEX; /// Index of the first free slot. private int _freeIndex = TERMINATOR_INDEX; /// Initializes the Queue instance. internal ArrayBasedLinkedQueue() { _storage = new List>(); } /// Initializes the Queue instance. /// The capacity of the internal storage. internal ArrayBasedLinkedQueue(int capacity) { _storage = new List>(capacity); } /// Enqueues an item. /// The item to be enqueued. /// The index of the slot where item was stored. internal int Enqueue(T item) { int newIndex; // If there is a free slot, reuse it if (_freeIndex != TERMINATOR_INDEX) { Debug.Assert(0 <= _freeIndex && _freeIndex < _storage.Count, "Index is out of range."); newIndex = _freeIndex; _freeIndex = _storage[_freeIndex].Key; _storage[newIndex] = new KeyValuePair(TERMINATOR_INDEX, item); } // If there is no free slot, add one else { newIndex = _storage.Count; _storage.Add(new KeyValuePair(TERMINATOR_INDEX, item)); } if (_headIndex == TERMINATOR_INDEX) { // Point _headIndex to newIndex if the queue was empty Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail."); _headIndex = newIndex; } else { // Point the tail slot to newIndex if the queue was not empty Debug.Assert(_tailIndex != TERMINATOR_INDEX, "If head does not indicate empty, neither should tail."); _storage[_tailIndex] = new KeyValuePair(newIndex, _storage[_tailIndex].Value); } // Point the tail slot newIndex _tailIndex = newIndex; return newIndex; } /// Tries to dequeue an item. /// The item that is dequeued. internal bool TryDequeue(out T item) { // If the queue is empty, just initialize the output item and return false if (_headIndex == TERMINATOR_INDEX) { Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail."); item = default(T); return false; } // If there are items in the queue, start with populating the output item Debug.Assert(0 <= _headIndex && _headIndex < _storage.Count, "Head is out of range."); item = _storage[_headIndex].Value; // Move the popped slot to the head of the free list int newHeadIndex = _storage[_headIndex].Key; _storage[_headIndex] = new KeyValuePair(_freeIndex, default(T)); _freeIndex = _headIndex; _headIndex = newHeadIndex; if (_headIndex == TERMINATOR_INDEX) _tailIndex = TERMINATOR_INDEX; return true; } /// Replaces the item of a given slot. /// The index of the slot where the value should be replaced. /// The item to be places. internal void Replace(int index, T item) { Debug.Assert(0 <= index && index < _storage.Count, "Index is out of range."); #if DEBUG // Also assert that index does not belong to the list of free slots for (int idx = _freeIndex; idx != TERMINATOR_INDEX; idx = _storage[idx].Key) Debug.Assert(idx != index, "Index should not belong to the list of free slots."); #endif _storage[index] = new KeyValuePair(_storage[index].Key, item); } internal bool IsEmpty { get { return _headIndex == TERMINATOR_INDEX; } } } /// The queue of elements. private readonly ArrayBasedLinkedQueue> _queue; /// A map from key to index into the list. /// The correctness of this map relies on the list only having elements removed from its end. private readonly Dictionary _mapKeyToIndex; /// Initializes the QueuedMap. internal QueuedMap() { _queue = new ArrayBasedLinkedQueue>(); _mapKeyToIndex = new Dictionary(); } /// Initializes the QueuedMap. /// The initial capacity of the data structure. internal QueuedMap(int capacity) { _queue = new ArrayBasedLinkedQueue>(capacity); _mapKeyToIndex = new Dictionary(capacity); } /// Pushes a key/value pair into the data structure. /// The key for the pair. /// The value for the pair. internal void Push(TKey key, TValue value) { // Try to get the index of the key in the queue. If it's there, replace the value. int indexOfKeyInQueue; if (!_queue.IsEmpty && _mapKeyToIndex.TryGetValue(key, out indexOfKeyInQueue)) { _queue.Replace(indexOfKeyInQueue, new KeyValuePair(key, value)); } // If it's not there, add it to the queue and then add the mapping. else { indexOfKeyInQueue = _queue.Enqueue(new KeyValuePair(key, value)); _mapKeyToIndex.Add(key, indexOfKeyInQueue); } } /// Try to pop the next element from the data structure. /// The popped pair. /// true if an item could be popped; otherwise, false. internal bool TryPop(out KeyValuePair item) { bool popped = _queue.TryDequeue(out item); if (popped) _mapKeyToIndex.Remove(item.Key); return popped; } /// Tries to pop one or more elements from the data structure. /// The items array into which the popped elements should be stored. /// The offset into the array at which to start storing popped items. /// The number of items to be popped. /// The number of items popped, which may be less than the requested number if fewer existed in the data structure. internal int PopRange(KeyValuePair[] items, int arrayOffset, int count) { // As this data structure is internal, only assert incorrect usage. // If this were to ever be made public, these would need to be real argument checks. Contract.Requires(items != null, "Requires non-null array to store into."); Contract.Requires(count >= 0 && arrayOffset >= 0, "Count and offset must be non-negative"); Contract.Requires(arrayOffset + count >= 0, "Offset plus count overflowed"); Contract.Requires(arrayOffset + count <= items.Length, "Range must be within array size"); int actualCount = 0; for (int i = arrayOffset; actualCount < count; i++, actualCount++) { KeyValuePair item; if (TryPop(out item)) items[i] = item; else break; } return actualCount; } /// Gets the number of items in the data structure. internal int Count { get { return _mapKeyToIndex.Count; } } } }