453 lines
14 KiB
C#
453 lines
14 KiB
C#
using System.Collections;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using SqrtSpace.SpaceTime.Core;
|
|
|
|
namespace SqrtSpace.SpaceTime.Collections;
|
|
|
|
/// <summary>
|
|
/// Dictionary that automatically adapts its implementation based on size
|
|
/// </summary>
|
|
public class AdaptiveDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull
|
|
{
|
|
private IDictionary<TKey, TValue> _implementation;
|
|
private readonly AdaptiveStrategy _strategy;
|
|
private readonly IEqualityComparer<TKey> _comparer;
|
|
|
|
// Thresholds for switching implementations
|
|
private const int ArrayThreshold = 16;
|
|
private const int DictionaryThreshold = 10_000;
|
|
private const int ExternalThreshold = 1_000_000;
|
|
|
|
/// <summary>
|
|
/// Initializes a new adaptive dictionary
|
|
/// </summary>
|
|
public AdaptiveDictionary() : this(0, null, AdaptiveStrategy.Automatic)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new adaptive dictionary with specified capacity
|
|
/// </summary>
|
|
public AdaptiveDictionary(int capacity, IEqualityComparer<TKey>? comparer = null, AdaptiveStrategy strategy = AdaptiveStrategy.Automatic)
|
|
{
|
|
_comparer = comparer ?? EqualityComparer<TKey>.Default;
|
|
_strategy = strategy;
|
|
_implementation = CreateImplementation(capacity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current implementation type
|
|
/// </summary>
|
|
public ImplementationType CurrentImplementation
|
|
{
|
|
get
|
|
{
|
|
return _implementation switch
|
|
{
|
|
ArrayDictionary<TKey, TValue> => ImplementationType.Array,
|
|
Dictionary<TKey, TValue> => ImplementationType.Dictionary,
|
|
SortedDictionary<TKey, TValue> => ImplementationType.SortedDictionary,
|
|
ExternalDictionary<TKey, TValue> => ImplementationType.External,
|
|
_ => ImplementationType.Unknown
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets memory usage statistics
|
|
/// </summary>
|
|
public MemoryStatistics GetMemoryStatistics()
|
|
{
|
|
var itemSize = IntPtr.Size * 2; // Rough estimate for key-value pair
|
|
var totalSize = Count * itemSize;
|
|
var memoryLevel = MemoryHierarchy.DetectSystem().GetOptimalLevel(totalSize);
|
|
|
|
return new MemoryStatistics
|
|
{
|
|
ItemCount = Count,
|
|
EstimatedMemoryBytes = totalSize,
|
|
MemoryLevel = memoryLevel,
|
|
Implementation = CurrentImplementation
|
|
};
|
|
}
|
|
|
|
#region IDictionary Implementation
|
|
|
|
public TValue this[TKey key]
|
|
{
|
|
get => _implementation[key];
|
|
set
|
|
{
|
|
_implementation[key] = value;
|
|
AdaptIfNeeded();
|
|
}
|
|
}
|
|
|
|
public ICollection<TKey> Keys => _implementation.Keys;
|
|
public ICollection<TValue> Values => _implementation.Values;
|
|
public int Count => _implementation.Count;
|
|
public bool IsReadOnly => _implementation.IsReadOnly;
|
|
|
|
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
|
|
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
_implementation.Add(key, value);
|
|
AdaptIfNeeded();
|
|
}
|
|
|
|
public void Add(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
_implementation.Add(item);
|
|
AdaptIfNeeded();
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_implementation.Clear();
|
|
AdaptIfNeeded();
|
|
}
|
|
|
|
public bool Contains(KeyValuePair<TKey, TValue> item) => _implementation.Contains(item);
|
|
public bool ContainsKey(TKey key) => _implementation.ContainsKey(key);
|
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => _implementation.CopyTo(array, arrayIndex);
|
|
|
|
public bool Remove(TKey key)
|
|
{
|
|
var result = _implementation.Remove(key);
|
|
AdaptIfNeeded();
|
|
return result;
|
|
}
|
|
|
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
var result = _implementation.Remove(item);
|
|
AdaptIfNeeded();
|
|
return result;
|
|
}
|
|
|
|
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => _implementation.TryGetValue(key, out value);
|
|
|
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _implementation.GetEnumerator();
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
#endregion
|
|
|
|
private void AdaptIfNeeded()
|
|
{
|
|
if (_strategy != AdaptiveStrategy.Automatic)
|
|
return;
|
|
|
|
IDictionary<TKey, TValue>? newImplementation = Count switch
|
|
{
|
|
<= ArrayThreshold when CurrentImplementation != ImplementationType.Array =>
|
|
new ArrayDictionary<TKey, TValue>(_comparer),
|
|
|
|
> ArrayThreshold and <= DictionaryThreshold when CurrentImplementation == ImplementationType.Array =>
|
|
new Dictionary<TKey, TValue>(_comparer),
|
|
|
|
> DictionaryThreshold and <= ExternalThreshold when CurrentImplementation != ImplementationType.SortedDictionary =>
|
|
new SortedDictionary<TKey, TValue>(Comparer<TKey>.Create((x, y) => _comparer.GetHashCode(x).CompareTo(_comparer.GetHashCode(y)))),
|
|
|
|
> ExternalThreshold when CurrentImplementation != ImplementationType.External =>
|
|
new ExternalDictionary<TKey, TValue>(_comparer),
|
|
|
|
_ => null
|
|
};
|
|
|
|
if (newImplementation != null)
|
|
{
|
|
// Copy data to new implementation
|
|
foreach (var kvp in _implementation)
|
|
{
|
|
newImplementation.Add(kvp);
|
|
}
|
|
|
|
// Dispose old implementation if needed
|
|
if (_implementation is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
|
|
_implementation = newImplementation;
|
|
}
|
|
}
|
|
|
|
private IDictionary<TKey, TValue> CreateImplementation(int capacity)
|
|
{
|
|
return capacity switch
|
|
{
|
|
<= ArrayThreshold => new ArrayDictionary<TKey, TValue>(_comparer),
|
|
<= DictionaryThreshold => new Dictionary<TKey, TValue>(capacity, _comparer),
|
|
<= ExternalThreshold => new SortedDictionary<TKey, TValue>(Comparer<TKey>.Create((x, y) => _comparer.GetHashCode(x).CompareTo(_comparer.GetHashCode(y)))),
|
|
_ => new ExternalDictionary<TKey, TValue>(_comparer)
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Array-based dictionary for small collections
|
|
/// </summary>
|
|
internal class ArrayDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
|
|
{
|
|
private readonly List<KeyValuePair<TKey, TValue>> _items;
|
|
private readonly IEqualityComparer<TKey> _comparer;
|
|
|
|
public ArrayDictionary(IEqualityComparer<TKey> comparer)
|
|
{
|
|
_items = new List<KeyValuePair<TKey, TValue>>();
|
|
_comparer = comparer;
|
|
}
|
|
|
|
public TValue this[TKey key]
|
|
{
|
|
get
|
|
{
|
|
var index = FindIndex(key);
|
|
if (index < 0) throw new KeyNotFoundException();
|
|
return _items[index].Value;
|
|
}
|
|
set
|
|
{
|
|
var index = FindIndex(key);
|
|
if (index < 0)
|
|
{
|
|
_items.Add(new KeyValuePair<TKey, TValue>(key, value));
|
|
}
|
|
else
|
|
{
|
|
_items[index] = new KeyValuePair<TKey, TValue>(key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public ICollection<TKey> Keys => _items.Select(kvp => kvp.Key).ToList();
|
|
public ICollection<TValue> Values => _items.Select(kvp => kvp.Value).ToList();
|
|
public int Count => _items.Count;
|
|
public bool IsReadOnly => false;
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
if (ContainsKey(key)) throw new ArgumentException("Key already exists");
|
|
_items.Add(new KeyValuePair<TKey, TValue>(key, value));
|
|
}
|
|
|
|
public void Add(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
Add(item.Key, item.Value);
|
|
}
|
|
|
|
public void Clear() => _items.Clear();
|
|
|
|
public bool Contains(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
var index = FindIndex(item.Key);
|
|
return index >= 0 && EqualityComparer<TValue>.Default.Equals(_items[index].Value, item.Value);
|
|
}
|
|
|
|
public bool ContainsKey(TKey key) => FindIndex(key) >= 0;
|
|
|
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
|
{
|
|
_items.CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
public bool Remove(TKey key)
|
|
{
|
|
var index = FindIndex(key);
|
|
if (index < 0) return false;
|
|
_items.RemoveAt(index);
|
|
return true;
|
|
}
|
|
|
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
return _items.Remove(item);
|
|
}
|
|
|
|
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
|
{
|
|
var index = FindIndex(key);
|
|
if (index < 0)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
value = _items[index].Value;
|
|
return true;
|
|
}
|
|
|
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _items.GetEnumerator();
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
private int FindIndex(TKey key)
|
|
{
|
|
for (int i = 0; i < _items.Count; i++)
|
|
{
|
|
if (_comparer.Equals(_items[i].Key, key))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// External dictionary for very large collections
|
|
/// </summary>
|
|
internal class ExternalDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDisposable where TKey : notnull
|
|
{
|
|
private readonly Dictionary<TKey, TValue> _cache;
|
|
private readonly ExternalStorage<KeyValuePair<TKey, TValue>> _storage;
|
|
private readonly IEqualityComparer<TKey> _comparer;
|
|
private readonly int _cacheSize;
|
|
private int _totalCount;
|
|
|
|
public ExternalDictionary(IEqualityComparer<TKey> comparer)
|
|
{
|
|
_comparer = comparer;
|
|
_cache = new Dictionary<TKey, TValue>(_comparer);
|
|
_storage = new ExternalStorage<KeyValuePair<TKey, TValue>>();
|
|
_cacheSize = SpaceTimeCalculator.CalculateSqrtInterval(1_000_000);
|
|
}
|
|
|
|
public TValue this[TKey key]
|
|
{
|
|
get
|
|
{
|
|
if (_cache.TryGetValue(key, out var value))
|
|
return value;
|
|
|
|
// Search in external storage
|
|
foreach (var kvp in ReadAllFromStorage())
|
|
{
|
|
if (_comparer.Equals(kvp.Key, key))
|
|
return kvp.Value;
|
|
}
|
|
|
|
throw new KeyNotFoundException();
|
|
}
|
|
set
|
|
{
|
|
if (_cache.Count >= _cacheSize)
|
|
{
|
|
SpillCacheToDisk();
|
|
}
|
|
_cache[key] = value;
|
|
_totalCount = Math.Max(_totalCount, _cache.Count);
|
|
}
|
|
}
|
|
|
|
public ICollection<TKey> Keys => throw new NotSupportedException("Keys collection not supported for external dictionary");
|
|
public ICollection<TValue> Values => throw new NotSupportedException("Values collection not supported for external dictionary");
|
|
public int Count => _totalCount;
|
|
public bool IsReadOnly => false;
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
if (ContainsKey(key)) throw new ArgumentException("Key already exists");
|
|
this[key] = value;
|
|
}
|
|
|
|
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
|
public void Clear()
|
|
{
|
|
_cache.Clear();
|
|
_storage.Dispose();
|
|
_totalCount = 0;
|
|
}
|
|
|
|
public bool Contains(KeyValuePair<TKey, TValue> item) => ContainsKey(item.Key);
|
|
public bool ContainsKey(TKey key) => _cache.ContainsKey(key) || ExistsInStorage(key);
|
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotSupportedException();
|
|
|
|
public bool Remove(TKey key) => _cache.Remove(key);
|
|
public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
|
|
|
|
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
|
{
|
|
try
|
|
{
|
|
value = this[key];
|
|
return true;
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
|
{
|
|
foreach (var kvp in _cache)
|
|
yield return kvp;
|
|
|
|
foreach (var kvp in ReadAllFromStorage())
|
|
yield return kvp;
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
public void Dispose() => _storage.Dispose();
|
|
|
|
private void SpillCacheToDisk()
|
|
{
|
|
_storage.SpillToDiskAsync(_cache).GetAwaiter().GetResult();
|
|
_cache.Clear();
|
|
}
|
|
|
|
private bool ExistsInStorage(TKey key)
|
|
{
|
|
foreach (var kvp in ReadAllFromStorage())
|
|
{
|
|
if (_comparer.Equals(kvp.Key, key))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private IEnumerable<KeyValuePair<TKey, TValue>> ReadAllFromStorage()
|
|
{
|
|
// This is simplified - production would be more efficient
|
|
return Enumerable.Empty<KeyValuePair<TKey, TValue>>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation type of adaptive collection
|
|
/// </summary>
|
|
public enum ImplementationType
|
|
{
|
|
Unknown,
|
|
Array,
|
|
Dictionary,
|
|
SortedDictionary,
|
|
External
|
|
}
|
|
|
|
/// <summary>
|
|
/// Memory usage statistics
|
|
/// </summary>
|
|
public class MemoryStatistics
|
|
{
|
|
public int ItemCount { get; init; }
|
|
public long EstimatedMemoryBytes { get; init; }
|
|
public MemoryLevel MemoryLevel { get; init; }
|
|
public ImplementationType Implementation { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Strategy for adaptive collections
|
|
/// </summary>
|
|
public enum AdaptiveStrategy
|
|
{
|
|
/// <summary>Automatically adapt based on size</summary>
|
|
Automatic,
|
|
/// <summary>Always use array implementation</summary>
|
|
ForceArray,
|
|
/// <summary>Always use dictionary implementation</summary>
|
|
ForceDictionary,
|
|
/// <summary>Always use external implementation</summary>
|
|
ForceExternal
|
|
} |