2026-02-01 20:52:23 +01:00
|
|
|
using System.Collections;
|
|
|
|
|
|
2026-05-07 07:44:55 +02:00
|
|
|
namespace PersistentOrderedMap;
|
2026-02-01 20:52:23 +01:00
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public abstract class BaseOrderedMap<TK, TV, TStrategy> : IEnumerable<KeyValuePair<TK, TV>> where TStrategy : IKeyStrategy<TK>
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
internal Node<TK> Root;
|
|
|
|
|
internal readonly TStrategy Strategy;
|
2026-02-01 20:52:23 +01:00
|
|
|
|
|
|
|
|
public int Count { get; protected set; }
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
protected BaseOrderedMap(Node<TK> root, TStrategy strategy, int count)
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
Root = root ?? throw new ArgumentNullException(nameof(root));
|
|
|
|
|
Strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
|
2026-02-01 20:52:23 +01:00
|
|
|
Count = count;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
// Read Operations (Shared)
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool TryGetValue(TK key, out TV value)
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
return BTreeFunctions.TryGetValue(Root, key, Strategy, out value);
|
2026-02-01 20:52:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool ContainsKey(TK key)
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
return BTreeFunctions.TryGetValue<TK,TV, TStrategy>(Root, key, Strategy, out _);
|
2026-02-01 20:52:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
// Bootstrap / Factory Helpers
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public static PersistentOrderedMap<TK, TV, TStrategy> Create(TStrategy strategy)
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
|
|
|
|
// Start with an empty leaf owned by None so the first write triggers CoW.
|
2026-05-21 13:13:22 +02:00
|
|
|
var emptyRoot = new LeafNode<TK, TV>(OwnerId.None, strategy.UsesPrefixes);
|
|
|
|
|
return new PersistentOrderedMap<TK, TV, TStrategy>(emptyRoot, strategy, 0);
|
2026-02-01 20:52:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public static TransientOrderedMap<TK, TV, TStrategy> CreateTransient(TStrategy strategy)
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
var emptyRoot = new LeafNode<TK, TV>(OwnerId.None, strategy.UsesPrefixes);
|
|
|
|
|
return new TransientOrderedMap<TK, TV, TStrategy>(emptyRoot, strategy,0);
|
2026-02-01 20:52:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public BTreeEnumerator<TK, TV, TStrategy> GetEnumerator()
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
|
|
|
|
return AsEnumerable().GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
IEnumerator<KeyValuePair<TK, TV>> IEnumerable<KeyValuePair<TK, TV>>.GetEnumerator()
|
2026-02-01 20:52:23 +01:00
|
|
|
{
|
|
|
|
|
return GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
|
|
|
{
|
|
|
|
|
return GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1. Full Scan
|
2026-05-21 13:13:22 +02:00
|
|
|
public BTreeEnumerable<TK, TV, TStrategy> AsEnumerable()
|
|
|
|
|
=> new(Root, Strategy, false, default!, false, default!);
|
2026-02-01 20:52:23 +01:00
|
|
|
|
|
|
|
|
// 2. Exact Range
|
2026-05-21 13:13:22 +02:00
|
|
|
public BTreeEnumerable<TK, TV, TStrategy> Range(TK min, TK max)
|
|
|
|
|
=> new(Root, Strategy, true, min, true, max);
|
2026-02-01 20:52:23 +01:00
|
|
|
|
|
|
|
|
// 3. Start From (Open Ended)
|
2026-05-21 13:13:22 +02:00
|
|
|
public BTreeEnumerable<TK, TV, TStrategy> From(TK min) => new(Root, Strategy, true, min, false, default!);
|
2026-02-01 20:52:23 +01:00
|
|
|
|
|
|
|
|
// 4. Until (Start at beginning)
|
2026-05-21 13:13:22 +02:00
|
|
|
public BTreeEnumerable<TK, TV, TStrategy> Until(TK max)
|
|
|
|
|
=> new(Root, Strategy, false, default!, true, max);
|
2026-04-16 11:51:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
// Navigation Operations
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool TryGetMin(out TK key, out TV value) => BTreeFunctions.TryGetMin(Root, out key, out value);
|
2026-04-16 11:51:38 +02:00
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool TryGetMax(out TK key, out TV value) => BTreeFunctions.TryGetMax(Root, out key, out value);
|
2026-04-16 11:51:38 +02:00
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool TryGetSuccessor(TK key, out TK nextKey, out TV nextValue) => BTreeFunctions.TryGetSuccessor(Root, key, Strategy, out nextKey, out nextValue);
|
2026-04-16 11:51:38 +02:00
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public bool TryGetPredecessor(TK key, out TK prevKey, out TV prevValue) => BTreeFunctions.TryGetPredecessor(Root, key, Strategy, out prevKey, out prevValue);
|
2026-04-16 11:51:38 +02:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
// Set Operations (Linear Merge O(N+M))
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public IEnumerable<KeyValuePair<TK, TV>> Intersect(BaseOrderedMap<TK, TV, TStrategy> other)
|
2026-04-16 11:51:38 +02:00
|
|
|
{
|
|
|
|
|
using var enum1 = this.GetEnumerator();
|
|
|
|
|
using var enum2 = other.GetEnumerator();
|
|
|
|
|
|
|
|
|
|
bool has1 = enum1.MoveNext();
|
|
|
|
|
bool has2 = enum2.MoveNext();
|
|
|
|
|
|
|
|
|
|
while (has1 && has2)
|
|
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
2026-04-16 11:51:38 +02:00
|
|
|
if (cmp == 0)
|
|
|
|
|
{
|
|
|
|
|
yield return enum1.Current;
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
else if (cmp < 0) has1 = enum1.MoveNext();
|
|
|
|
|
else has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public IEnumerable<KeyValuePair<TK, TV>> Except(BaseOrderedMap<TK, TV, TStrategy> other)
|
2026-04-16 11:51:38 +02:00
|
|
|
{
|
|
|
|
|
using var enum1 = this.GetEnumerator();
|
|
|
|
|
using var enum2 = other.GetEnumerator();
|
|
|
|
|
|
|
|
|
|
bool has1 = enum1.MoveNext();
|
|
|
|
|
bool has2 = enum2.MoveNext();
|
|
|
|
|
|
|
|
|
|
while (has1 && has2)
|
|
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
2026-04-16 11:51:38 +02:00
|
|
|
if (cmp == 0)
|
|
|
|
|
{
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
else if (cmp < 0)
|
|
|
|
|
{
|
|
|
|
|
yield return enum1.Current;
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (has1)
|
|
|
|
|
{
|
|
|
|
|
yield return enum1.Current;
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:13:22 +02:00
|
|
|
public IEnumerable<KeyValuePair<TK, TV>> SymmetricExcept(BaseOrderedMap<TK, TV, TStrategy> other)
|
2026-04-16 11:51:38 +02:00
|
|
|
{
|
|
|
|
|
using var enum1 = this.GetEnumerator();
|
|
|
|
|
using var enum2 = other.GetEnumerator();
|
|
|
|
|
|
|
|
|
|
bool has1 = enum1.MoveNext();
|
|
|
|
|
bool has2 = enum2.MoveNext();
|
|
|
|
|
|
|
|
|
|
while (has1 && has2)
|
|
|
|
|
{
|
2026-05-21 13:13:22 +02:00
|
|
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
2026-04-16 11:51:38 +02:00
|
|
|
if (cmp == 0)
|
|
|
|
|
{
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
else if (cmp < 0)
|
|
|
|
|
{
|
|
|
|
|
yield return enum1.Current;
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
yield return enum2.Current;
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (has1)
|
|
|
|
|
{
|
|
|
|
|
yield return enum1.Current;
|
|
|
|
|
has1 = enum1.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
while (has2)
|
|
|
|
|
{
|
|
|
|
|
yield return enum2.Current;
|
|
|
|
|
has2 = enum2.MoveNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|