// 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; } }
}
}