surpressed all style warnings from the LSP server.
This commit is contained in:
parent
7ee2238248
commit
23c4ac299e
13 changed files with 346 additions and 371 deletions
|
|
@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks",
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstImmutableDict", "benchmarks\AgainstImmutableDict\AgainstImmutableDict.csproj", "{13304F19-7ED3-4C40-9A08-46D539667D50}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstImmutableDict", "benchmarks\AgainstImmutableDict\AgainstImmutableDict.csproj", "{13304F19-7ED3-4C40-9A08-46D539667D50}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstLanguageExt", "benchmarks\AgainstLanguageExt\AgainstLanguageExt.csproj", "{6C16526B-5139-4EA3-BF74-E6320F467198}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyBenchMarks", "benchmarks\MyBenchMarks\MyBenchMarks.csproj", "{769E1CEA-7E01-405B-80A2-95CBF432A2BA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyBenchMarks", "benchmarks\MyBenchMarks\MyBenchMarks.csproj", "{769E1CEA-7E01-405B-80A2-95CBF432A2BA}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|
@ -22,7 +20,6 @@ Global
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{CA49AA3C-0CE6-4735-887F-FB3631D63CEE} = {B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE}
|
{CA49AA3C-0CE6-4735-887F-FB3631D63CEE} = {B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE}
|
||||||
{13304F19-7ED3-4C40-9A08-46D539667D50} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
{13304F19-7ED3-4C40-9A08-46D539667D50} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
||||||
{6C16526B-5139-4EA3-BF74-E6320F467198} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
|
||||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
{769E1CEA-7E01-405B-80A2-95CBF432A2BA} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
|
@ -38,10 +35,6 @@ Global
|
||||||
{13304F19-7ED3-4C40-9A08-46D539667D50}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{13304F19-7ED3-4C40-9A08-46D539667D50}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.Build.0 = Release|Any CPU
|
{13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,20 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
/// <summary>TryGetValue tries to get the value at mapping key. If it finds the key it sets th
|
/// <summary>TryGetValue tries to get the value at mapping key. If it finds the key it sets th
|
||||||
/// out var to value and returns true. </summary>
|
/// out var to value and returns true. </summary>
|
||||||
public static bool TryGetValue<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out V value)
|
public static bool TryGetValue<TK, TV, TStrategy>(Node<TK> root, TK key, TStrategy strategy, out TV value)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
// We always get a strategy to avoid branching already here
|
// We always get a strategy to avoid branching already here
|
||||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||||
|
|
||||||
Node<K> current = root;
|
Node<TK> current = root;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (current.IsLeaf)
|
if (current.IsLeaf)
|
||||||
{
|
{
|
||||||
var leaf = current.AsLeaf<V>();
|
var leaf = current.AsLeaf<TV>();
|
||||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0)
|
||||||
{
|
{
|
||||||
value = leaf.Values[index];
|
value = leaf.Values[index];
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -41,8 +41,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Node<K> Set<K, V, TStrategy>(Node<K> root, K key, V value, TStrategy strategy, OwnerId owner, out bool countChanged)
|
public static Node<TK> Set<TK, TV, TStrategy>(Node<TK> root, TK key, TV value, TStrategy strategy, OwnerId owner, out bool countChanged)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
root = root.EnsureEditable(owner);
|
root = root.EnsureEditable(owner);
|
||||||
|
|
||||||
|
|
@ -52,8 +52,8 @@ namespace PersistentOrderedMap
|
||||||
if (newNode != null)
|
if (newNode != null)
|
||||||
{
|
{
|
||||||
var newRoot = strategy.UsesPrefixes
|
var newRoot = strategy.UsesPrefixes
|
||||||
? new PrefixInternalNode<K>(owner)
|
? new PrefixInternalNode<TK>(owner)
|
||||||
: new InternalNode<K>(owner);
|
: new InternalNode<TK>(owner);
|
||||||
|
|
||||||
newRoot.Keys[0] = sep;
|
newRoot.Keys[0] = sep;
|
||||||
newRoot.Children[0] = root;
|
newRoot.Children[0] = root;
|
||||||
|
|
@ -71,27 +71,27 @@ namespace PersistentOrderedMap
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (Node<K>? newNode, K separator ) InsertRecursive<K, V>(Node<K> node, K key, V value, IKeyStrategy<K> strategy, OwnerId owner, out bool added)
|
private static (Node<TK>? newNode, TK separator ) InsertRecursive<TK, TV>(Node<TK> node, TK key, TV value, IKeyStrategy<TK> strategy, OwnerId owner, out bool added)
|
||||||
{
|
{
|
||||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||||
|
|
||||||
if (node.IsLeaf)
|
if (node.IsLeaf)
|
||||||
{
|
{
|
||||||
var leaf = node.AsLeaf<V>();
|
var leaf = node.AsLeaf<TV>();
|
||||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||||
|
|
||||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0)
|
||||||
{
|
{
|
||||||
leaf.Values[index] = value;
|
leaf.Values[index] = value;
|
||||||
added = false;
|
added = false;
|
||||||
return (null, default);
|
return (null, default)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
added = true;
|
added = true;
|
||||||
if (leaf.Header.Count < LeafNode<K, V>.Capacity)
|
if (leaf.Header.Count < LeafNode<TK, TV>.Capacity)
|
||||||
{
|
{
|
||||||
InsertIntoLeaf(leaf, index, key, value, strategy);
|
InsertIntoLeaf(leaf, index, key, value, strategy);
|
||||||
return (null, default);
|
return (null, default)!;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -110,26 +110,25 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
if (newNode != null)
|
if (newNode != null)
|
||||||
{
|
{
|
||||||
if (internalNode.Header.Count < InternalNode<K>.Capacity - 1)
|
if (internalNode.Header.Count < InternalNode<TK>.Capacity - 1)
|
||||||
{
|
{
|
||||||
InsertIntoInternal(internalNode, index, sep, newNode, strategy);
|
InsertIntoInternal(internalNode, index, sep, newNode, strategy);
|
||||||
return (null, default);
|
return (null, default)!;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return SplitInternal(internalNode, index, sep, newNode, strategy, owner);
|
return SplitInternal(internalNode, index, sep, newNode, strategy, owner);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
return (null, default)!;
|
||||||
return (null, default);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Node<K> Remove<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, OwnerId owner, out bool countChanged)
|
public static Node<TK> Remove<TK, TV, TStrategy>(Node<TK> root, TK key, TStrategy strategy, OwnerId owner, out bool countChanged)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
root = root.EnsureEditable(owner);
|
root = root.EnsureEditable(owner);
|
||||||
|
|
||||||
bool rebalanceNeeded = RemoveRecursive<K, V, TStrategy>(root, key, strategy, owner, out countChanged);
|
bool rebalanceNeeded = RemoveRecursive<TK, TV, TStrategy>(root, key, strategy, owner, out countChanged);
|
||||||
|
|
||||||
if (rebalanceNeeded)
|
if (rebalanceNeeded)
|
||||||
{
|
{
|
||||||
|
|
@ -146,21 +145,21 @@ namespace PersistentOrderedMap
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool RemoveRecursive<K, V, TStrategy>(Node<K> node, K key, TStrategy strategy, OwnerId owner, out bool removed)
|
private static bool RemoveRecursive<TK, TV, TStrategy>(Node<TK> node, TK key, TStrategy strategy, OwnerId owner, out bool removed)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||||
|
|
||||||
if (node.IsLeaf)
|
if (node.IsLeaf)
|
||||||
{
|
{
|
||||||
var leaf = node.AsLeaf<V>();
|
var leaf = node.AsLeaf<TV>();
|
||||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||||
|
|
||||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
|
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0)
|
||||||
{
|
{
|
||||||
RemoveFromLeaf(leaf, index, strategy);
|
RemoveFromLeaf(leaf, index, strategy);
|
||||||
removed = true;
|
removed = true;
|
||||||
return leaf.Header.Count < LeafNode<K, V>.MergeThreshold;
|
return leaf.Header.Count < LeafNode<TK, TV>.MergeThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
removed = false;
|
removed = false;
|
||||||
|
|
@ -174,11 +173,11 @@ namespace PersistentOrderedMap
|
||||||
var child = internalNode.Children[index]!.EnsureEditable(owner);
|
var child = internalNode.Children[index]!.EnsureEditable(owner);
|
||||||
internalNode.Children[index] = child;
|
internalNode.Children[index] = child;
|
||||||
|
|
||||||
bool childUnderflow = RemoveRecursive<K, V, TStrategy>(child, key, strategy, owner, out removed);
|
bool childUnderflow = RemoveRecursive<TK, TV, TStrategy>(child, key, strategy, owner, out removed);
|
||||||
|
|
||||||
if (removed && childUnderflow)
|
if (removed && childUnderflow)
|
||||||
{
|
{
|
||||||
return HandleUnderflow<K, V, TStrategy>(internalNode, index, strategy, owner);
|
return HandleUnderflow<TK, TV, TStrategy>(internalNode, index, strategy, owner);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -189,16 +188,16 @@ namespace PersistentOrderedMap
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static int FindIndex<K,V, TStrategy>(LeafNode<K,V> node, K key, long keyPrefix, TStrategy strategy)
|
internal static int FindIndex<TK,TV, TStrategy>(LeafNode<TK,TV> node, TK key, long keyPrefix, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
if (typeof(K) == typeof(int))
|
if (typeof(TK) == typeof(int))
|
||||||
{
|
{
|
||||||
Span<K> keys = node.GetKeys();
|
Span<TK> keys = node.GetKeys();
|
||||||
ref K firstKeyRef = ref MemoryMarshal.GetReference(keys);
|
ref TK firstKeyRef = ref MemoryMarshal.GetReference(keys);
|
||||||
ref int firstIntRef = ref Unsafe.As<K, int>(ref firstKeyRef);
|
ref int firstIntRef = ref Unsafe.As<TK, int>(ref firstKeyRef);
|
||||||
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
|
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
|
||||||
int intKey = Unsafe.As<K, int>(ref key);
|
int intKey = Unsafe.As<TK, int>(ref key);
|
||||||
return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey);
|
return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,17 +211,17 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static int FindRoutingIndex<K, TStrategy>(InternalNode<K> node, K key, long keyPrefix, TStrategy strategy)
|
internal static int FindRoutingIndex<TK, TStrategy>(InternalNode<TK> node, TK key, long keyPrefix, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
|
|
||||||
if (typeof(K) == typeof(int))
|
if (typeof(TK) == typeof(int))
|
||||||
{
|
{
|
||||||
Span<K> keys = node.GetKeys();
|
Span<TK> keys = node.GetKeys();
|
||||||
ref K firstKeyRef = ref MemoryMarshal.GetReference(keys);
|
ref TK firstKeyRef = ref MemoryMarshal.GetReference(keys);
|
||||||
ref int firstIntRef = ref Unsafe.As<K, int>(ref firstKeyRef);
|
ref int firstIntRef = ref Unsafe.As<TK, int>(ref firstKeyRef);
|
||||||
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
|
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
|
||||||
int intKey = Unsafe.As<K, int>(ref key);
|
int intKey = Unsafe.As<TK, int>(ref key);
|
||||||
return IntScanner.FindFirstGreater(intKeys, intKey);
|
return IntScanner.FindFirstGreater(intKeys, intKey);
|
||||||
}
|
}
|
||||||
if (!strategy.UsesPrefixes)
|
if (!strategy.UsesPrefixes)
|
||||||
|
|
@ -235,8 +234,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int RefineSearch<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int RefineSearch<TK, TStrategy>(int startIndex, ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int i = startIndex;
|
int i = startIndex;
|
||||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
||||||
|
|
@ -244,8 +243,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int RefineRouting<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int RefineRouting<TK, TStrategy>(int startIndex, ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int i = startIndex;
|
int i = startIndex;
|
||||||
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
||||||
|
|
@ -253,8 +252,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int FallbackSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int FallbackSearchKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
return strategy.UseBinarySearch
|
return strategy.UseBinarySearch
|
||||||
? BinarySearchKeys(keys, key, strategy)
|
? BinarySearchKeys(keys, key, strategy)
|
||||||
|
|
@ -262,8 +261,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int FallbackRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int FallbackRoutingKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
return strategy.UseBinarySearch
|
return strategy.UseBinarySearch
|
||||||
? BinaryRoutingKeys(keys, key, strategy)
|
? BinaryRoutingKeys(keys, key, strategy)
|
||||||
|
|
@ -271,8 +270,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int LinearSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int LinearSearchKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
|
||||||
|
|
@ -280,8 +279,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int LinearRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int LinearRoutingKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
|
||||||
|
|
@ -290,17 +289,17 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int BinarySearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int BinarySearchKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int low = 0;
|
int low = 0;
|
||||||
int high = keys.Length - 1;
|
int high = keys.Length - 1;
|
||||||
ref K keysRef = ref MemoryMarshal.GetReference(keys);
|
ref TK keysRef = ref MemoryMarshal.GetReference(keys);
|
||||||
|
|
||||||
while (low <= high)
|
while (low <= high)
|
||||||
{
|
{
|
||||||
int mid = low + ((high - low) >> 1);
|
int mid = low + ((high - low) >> 1);
|
||||||
K midKey = Unsafe.Add(ref keysRef, mid);
|
TK midKey = Unsafe.Add(ref keysRef, mid);
|
||||||
int cmp = strategy.Compare(midKey, key);
|
int cmp = strategy.Compare(midKey, key);
|
||||||
|
|
||||||
if (cmp == 0) return mid;
|
if (cmp == 0) return mid;
|
||||||
|
|
@ -310,17 +309,17 @@ namespace PersistentOrderedMap
|
||||||
return low;
|
return low;
|
||||||
}
|
}
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int BinaryRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
|
private static int BinaryRoutingKeys<TK, TStrategy>(ReadOnlySpan<TK> keys, TK key, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int low = 0;
|
int low = 0;
|
||||||
int high = keys.Length - 1;
|
int high = keys.Length - 1;
|
||||||
ref K keysRef = ref MemoryMarshal.GetReference(keys);
|
ref TK keysRef = ref MemoryMarshal.GetReference(keys);
|
||||||
|
|
||||||
while (low <= high)
|
while (low <= high)
|
||||||
{
|
{
|
||||||
int mid = low + ((high - low) >> 1);
|
int mid = low + ((high - low) >> 1);
|
||||||
K midKey = Unsafe.Add(ref keysRef, mid);
|
TK midKey = Unsafe.Add(ref keysRef, mid);
|
||||||
int cmp = strategy.Compare(midKey, key);
|
int cmp = strategy.Compare(midKey, key);
|
||||||
|
|
||||||
if (cmp <= 0) low = mid + 1;
|
if (cmp <= 0) low = mid + 1;
|
||||||
|
|
@ -333,19 +332,8 @@ namespace PersistentOrderedMap
|
||||||
// Insertion Logic
|
// Insertion Logic
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
private class SeplitResult<K>
|
private static void InsertIntoLeaf<TK, TV, TStrategy>(LeafNode<TK, TV> leaf, int index, TK key, TV value, TStrategy strategy)
|
||||||
{
|
where TStrategy : IKeyStrategy<TK>
|
||||||
public Node<K> NewNode;
|
|
||||||
public K Separator;
|
|
||||||
public SeplitResult(Node<K> newNode, K separator)
|
|
||||||
{
|
|
||||||
NewNode = newNode;
|
|
||||||
Separator = separator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InsertIntoLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, K key, V value, TStrategy strategy)
|
|
||||||
where TStrategy : IKeyStrategy<K>
|
|
||||||
{
|
{
|
||||||
int count = leaf.Header.Count;
|
int count = leaf.Header.Count;
|
||||||
if (index < count)
|
if (index < count)
|
||||||
|
|
@ -360,7 +348,7 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf.Keys[index] = key;
|
leaf.Keys![index] = key;
|
||||||
leaf.Values[index] = value;
|
leaf.Values[index] = value;
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -371,10 +359,10 @@ namespace PersistentOrderedMap
|
||||||
leaf.SetCount(count + 1);
|
leaf.SetCount(count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (Node<K>, K) SplitLeaf<K, V, TStrategy>(LeafNode<K, V> left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner)
|
private static (Node<TK>, TK) SplitLeaf<TK, TV, TStrategy>(LeafNode<TK, TV> left, int insertIndex, TK key, TV value, TStrategy strategy, OwnerId owner)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
var right = new LeafNode<K, V>(owner, strategy.UsesPrefixes);
|
var right = new LeafNode<TK, TV>(owner, strategy.UsesPrefixes);
|
||||||
int totalCount = left.Header.Count;
|
int totalCount = left.Header.Count;
|
||||||
|
|
||||||
int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2);
|
int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2);
|
||||||
|
|
@ -403,21 +391,21 @@ namespace PersistentOrderedMap
|
||||||
InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy);
|
InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (right, right.Keys[0]);
|
return (right, right.Keys![0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InsertIntoInternal<K, TStrategy>(InternalNode<K> node, int index, K separator, Node<K> newChild, TStrategy strategy)
|
private static void InsertIntoInternal<TK, TStrategy>(InternalNode<TK> node, int index, TK separator, Node<TK> newChild, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int count = node.Header.Count;
|
int count = node.Header.Count;
|
||||||
|
|
||||||
if (index < count)
|
if (index < count)
|
||||||
{
|
{
|
||||||
int moveCount = count - index;
|
int moveCount = count - index;
|
||||||
Span<K> keysSpan = node.Keys;
|
Span<TK> keysSpan = node.Keys;
|
||||||
keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1));
|
keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1));
|
||||||
|
|
||||||
Span<Node<K>> childrenSpan = node.Children;
|
Span<Node<TK>> childrenSpan = node.Children!;
|
||||||
childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2));
|
childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2));
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -437,26 +425,26 @@ namespace PersistentOrderedMap
|
||||||
node.SetCount(count + 1);
|
node.SetCount(count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (Node<K>, K) SplitInternal<K, TStrategy>(InternalNode<K> left, int insertIndex, K separator, Node<K> newChild, TStrategy strategy, OwnerId owner)
|
private static (Node<TK>, TK) SplitInternal<TK, TStrategy>(InternalNode<TK> left, int insertIndex, TK separator, Node<TK> newChild, TStrategy strategy, OwnerId owner)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
var right = strategy.UsesPrefixes
|
var right = strategy.UsesPrefixes
|
||||||
? new PrefixInternalNode<K>(owner)
|
? new PrefixInternalNode<TK>(owner)
|
||||||
: new InternalNode<K>(owner);
|
: new InternalNode<TK>(owner);
|
||||||
|
|
||||||
int count = left.Header.Count;
|
int count = left.Header.Count;
|
||||||
int splitPoint = count / 2;
|
int splitPoint = count / 2;
|
||||||
K upKey = left.Keys[splitPoint];
|
TK upKey = left.Keys[splitPoint];
|
||||||
int moveCount = count - splitPoint - 1;
|
int moveCount = count - splitPoint - 1;
|
||||||
|
|
||||||
if (moveCount > 0)
|
if (moveCount > 0)
|
||||||
{
|
{
|
||||||
Span<K> leftKeys = left.Keys;
|
Span<TK> leftKeys = left.Keys;
|
||||||
Span<K> rightKeys = right.Keys;
|
Span<TK> rightKeys = right.Keys;
|
||||||
leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys);
|
leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys);
|
||||||
|
|
||||||
Span<Node<K>> leftChildren = left.Children;
|
Span<Node<TK>> leftChildren = left.Children!;
|
||||||
Span<Node<K>> rightChildren = right.Children;
|
Span<Node<TK>> rightChildren = right.Children!;
|
||||||
leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren);
|
leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren);
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -484,8 +472,8 @@ namespace PersistentOrderedMap
|
||||||
// Removal Logic
|
// Removal Logic
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
private static void RemoveFromLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, TStrategy strategy)
|
private static void RemoveFromLeaf<TK, TV, TStrategy>(LeafNode<TK, TV> leaf, int index, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
int count = leaf.Header.Count;
|
int count = leaf.Header.Count;
|
||||||
int moveCount = count - index - 1;
|
int moveCount = count - index - 1;
|
||||||
|
|
@ -504,8 +492,8 @@ namespace PersistentOrderedMap
|
||||||
leaf.SetCount(count - 1);
|
leaf.SetCount(count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HandleUnderflow<K, V, TStrategy>(InternalNode<K> parent, int childIndex, TStrategy strategy, OwnerId owner)
|
private static bool HandleUnderflow<TK, TV, TStrategy>(InternalNode<TK> parent, int childIndex, TStrategy strategy, OwnerId owner)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
if (childIndex < parent.Header.Count)
|
if (childIndex < parent.Header.Count)
|
||||||
{
|
{
|
||||||
|
|
@ -515,13 +503,13 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
if (CanBorrow(rightSibling))
|
if (CanBorrow(rightSibling))
|
||||||
{
|
{
|
||||||
RotateLeft<K, V, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
|
RotateLeft<TK, TV, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Merge<K, V, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
|
Merge<TK, TV, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
|
||||||
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
|
return parent.Header.Count < LeafNode<TK, TV>.MergeThreshold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (childIndex > 0)
|
else if (childIndex > 0)
|
||||||
|
|
@ -532,31 +520,31 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
if (CanBorrow(leftSibling))
|
if (CanBorrow(leftSibling))
|
||||||
{
|
{
|
||||||
RotateRight<K, V, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
|
RotateRight<TK, TV, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Merge<K, V, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
|
Merge<TK, TV, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
|
||||||
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
|
return parent.Header.Count < LeafNode<TK, TV>.MergeThreshold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CanBorrow<K>(Node<K> node)
|
private static bool CanBorrow<TK>(Node<TK> node)
|
||||||
{
|
{
|
||||||
return node.Header.Count > 8 + 1;
|
return node.Header.Count > 8 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Merge<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
private static void Merge<TK, TV, TStrategy>(InternalNode<TK> parent, int separatorIndex, Node<TK> left, Node<TK> right, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
if (left.IsLeaf)
|
if (left.IsLeaf)
|
||||||
{
|
{
|
||||||
var leftLeaf = left.AsLeaf<V>();
|
var leftLeaf = left.AsLeaf<TV>();
|
||||||
var rightLeaf = right.AsLeaf<V>();
|
var rightLeaf = right.AsLeaf<TV>();
|
||||||
|
|
||||||
int lCount = leftLeaf.Header.Count;
|
int lCount = leftLeaf.Header.Count;
|
||||||
int rCount = rightLeaf.Header.Count;
|
int rCount = rightLeaf.Header.Count;
|
||||||
|
|
@ -576,7 +564,7 @@ namespace PersistentOrderedMap
|
||||||
var leftInternal = left.AsInternal();
|
var leftInternal = left.AsInternal();
|
||||||
var rightInternal = right.AsInternal();
|
var rightInternal = right.AsInternal();
|
||||||
|
|
||||||
K separator = parent.Keys[separatorIndex];
|
TK separator = parent.Keys[separatorIndex];
|
||||||
|
|
||||||
int lCount = leftInternal.Header.Count;
|
int lCount = leftInternal.Header.Count;
|
||||||
leftInternal.Keys[lCount] = separator;
|
leftInternal.Keys[lCount] = separator;
|
||||||
|
|
@ -587,8 +575,8 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
int rCount = rightInternal.Header.Count;
|
int rCount = rightInternal.Header.Count;
|
||||||
Span<K> rightKeys = rightInternal.Keys;
|
Span<TK> rightKeys = rightInternal.Keys;
|
||||||
Span<K> leftKeys = leftInternal.Keys;
|
Span<TK> leftKeys = leftInternal.Keys;
|
||||||
rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1));
|
rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1));
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -596,8 +584,8 @@ namespace PersistentOrderedMap
|
||||||
rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
|
rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<Node<K>> rightChildren = rightInternal.Children;
|
Span<Node<TK>> rightChildren = rightInternal.Children!;
|
||||||
Span<Node<K>> leftChildren = leftInternal.Children;
|
Span<Node<TK>> leftChildren = leftInternal.Children!;
|
||||||
rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1));
|
rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1));
|
||||||
|
|
||||||
leftInternal.SetCount(lCount + 1 + rCount);
|
leftInternal.SetCount(lCount + 1 + rCount);
|
||||||
|
|
@ -608,7 +596,7 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
if (moveCount > 0)
|
if (moveCount > 0)
|
||||||
{
|
{
|
||||||
Span<K> parentKeys = parent.Keys;
|
Span<TK> parentKeys = parent.Keys;
|
||||||
parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex));
|
parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex));
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -616,22 +604,22 @@ namespace PersistentOrderedMap
|
||||||
parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex));
|
parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<Node<K>> parentChildren = parent.Children;
|
Span<Node<TK>> parentChildren = parent.Children!;
|
||||||
parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1));
|
parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.SetCount(pCount - 1);
|
parent.SetCount(pCount - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RotateLeft<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
private static void RotateLeft<TK, TV, TStrategy>(InternalNode<TK> parent, int separatorIndex, Node<TK> left, Node<TK> right, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
if (left.IsLeaf)
|
if (left.IsLeaf)
|
||||||
{
|
{
|
||||||
var leftLeaf = left.AsLeaf<V>();
|
var leftLeaf = left.AsLeaf<TV>();
|
||||||
var rightLeaf = right.AsLeaf<V>();
|
var rightLeaf = right.AsLeaf<TV>();
|
||||||
|
|
||||||
InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys[0], rightLeaf.Values[0], strategy);
|
InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys![0], rightLeaf.Values[0], strategy);
|
||||||
RemoveFromLeaf(rightLeaf, 0, strategy);
|
RemoveFromLeaf(rightLeaf, 0, strategy);
|
||||||
|
|
||||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
||||||
|
|
@ -645,7 +633,7 @@ namespace PersistentOrderedMap
|
||||||
var leftInternal = left.AsInternal();
|
var leftInternal = left.AsInternal();
|
||||||
var rightInternal = right.AsInternal();
|
var rightInternal = right.AsInternal();
|
||||||
|
|
||||||
K sep = parent.Keys[separatorIndex];
|
TK sep = parent.Keys[separatorIndex];
|
||||||
InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy);
|
InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy);
|
||||||
|
|
||||||
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
parent.Keys[separatorIndex] = rightInternal.Keys[0];
|
||||||
|
|
@ -656,12 +644,12 @@ namespace PersistentOrderedMap
|
||||||
|
|
||||||
int rCount = rightInternal.Header.Count;
|
int rCount = rightInternal.Header.Count;
|
||||||
|
|
||||||
Span<Node<K>> rightChildren = rightInternal.Children;
|
Span<Node<TK>> rightChildren = rightInternal.Children!;
|
||||||
rightChildren.Slice(1, rCount).CopyTo(rightChildren);
|
rightChildren.Slice(1, rCount).CopyTo(rightChildren);
|
||||||
|
|
||||||
if (rCount > 1)
|
if (rCount > 1)
|
||||||
{
|
{
|
||||||
Span<K> rightKeys = rightInternal.Keys;
|
Span<TK> rightKeys = rightInternal.Keys;
|
||||||
rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys);
|
rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys);
|
||||||
|
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
|
|
@ -674,19 +662,19 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RotateRight<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
|
private static void RotateRight<TK, TV, TStrategy>(InternalNode<TK> parent, int separatorIndex, Node<TK> left, Node<TK> right, TStrategy strategy)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
if (left.IsLeaf)
|
if (left.IsLeaf)
|
||||||
{
|
{
|
||||||
var leftLeaf = left.AsLeaf<V>();
|
var leftLeaf = left.AsLeaf<TV>();
|
||||||
var rightLeaf = right.AsLeaf<V>();
|
var rightLeaf = right.AsLeaf<TV>();
|
||||||
int last = leftLeaf.Header.Count - 1;
|
int last = leftLeaf.Header.Count - 1;
|
||||||
|
|
||||||
InsertIntoLeaf(rightLeaf, 0, leftLeaf.Keys[last], leftLeaf.Values[last], strategy);
|
InsertIntoLeaf(rightLeaf, 0, leftLeaf.Keys![last], leftLeaf.Values[last], strategy);
|
||||||
RemoveFromLeaf(leftLeaf, last, strategy);
|
RemoveFromLeaf(leftLeaf, last, strategy);
|
||||||
|
|
||||||
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
|
parent.Keys[separatorIndex] = rightLeaf.Keys![0];
|
||||||
if (strategy.UsesPrefixes)
|
if (strategy.UsesPrefixes)
|
||||||
{
|
{
|
||||||
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
|
||||||
|
|
@ -698,7 +686,7 @@ namespace PersistentOrderedMap
|
||||||
var rightInternal = right.AsInternal();
|
var rightInternal = right.AsInternal();
|
||||||
int last = leftInternal.Header.Count - 1;
|
int last = leftInternal.Header.Count - 1;
|
||||||
|
|
||||||
K sep = parent.Keys[separatorIndex];
|
TK sep = parent.Keys[separatorIndex];
|
||||||
InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy);
|
InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy);
|
||||||
|
|
||||||
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
parent.Keys[separatorIndex] = leftInternal.Keys[last];
|
||||||
|
|
@ -711,7 +699,7 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetMin<K, V>(Node<K> root, out K key, out V value)
|
public static bool TryGetMin<TK, TV>(Node<TK> root, out TK key, out TV value)
|
||||||
{
|
{
|
||||||
var current = root;
|
var current = root;
|
||||||
while (!current.IsLeaf)
|
while (!current.IsLeaf)
|
||||||
|
|
@ -719,7 +707,7 @@ namespace PersistentOrderedMap
|
||||||
current = current.AsInternal().Children[0]!;
|
current = current.AsInternal().Children[0]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var leaf = current.AsLeaf<V>();
|
var leaf = current.AsLeaf<TV>();
|
||||||
if (leaf.Header.Count == 0)
|
if (leaf.Header.Count == 0)
|
||||||
{
|
{
|
||||||
key = default!;
|
key = default!;
|
||||||
|
|
@ -727,12 +715,12 @@ namespace PersistentOrderedMap
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = leaf.Keys[0];
|
key = leaf.Keys![0];
|
||||||
value = leaf.Values[0];
|
value = leaf.Values[0];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetMax<K, V>(Node<K> root, out K key, out V value)
|
public static bool TryGetMax<TK, TV>(Node<TK> root, out TK key, out TV value)
|
||||||
{
|
{
|
||||||
var current = root;
|
var current = root;
|
||||||
while (!current.IsLeaf)
|
while (!current.IsLeaf)
|
||||||
|
|
@ -741,7 +729,7 @@ namespace PersistentOrderedMap
|
||||||
current = internalNode.Children[internalNode.Header.Count]!;
|
current = internalNode.Children[internalNode.Header.Count]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var leaf = current.AsLeaf<V>();
|
var leaf = current.AsLeaf<TV>();
|
||||||
if (leaf.Header.Count == 0)
|
if (leaf.Header.Count == 0)
|
||||||
{
|
{
|
||||||
key = default!;
|
key = default!;
|
||||||
|
|
@ -750,15 +738,15 @@ namespace PersistentOrderedMap
|
||||||
}
|
}
|
||||||
|
|
||||||
int last = leaf.Header.Count - 1;
|
int last = leaf.Header.Count - 1;
|
||||||
key = leaf.Keys[last];
|
key = leaf.Keys![last];
|
||||||
value = leaf.Values[last];
|
value = leaf.Values[last];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetSuccessor<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out K nextKey, out V nextValue)
|
public static bool TryGetSuccessor<TK, TV, TStrategy>(Node<TK> root, TK key, TStrategy strategy, out TK nextKey, out TV nextValue)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
InternalNode<K>[] path = new InternalNode<K>[32];
|
InternalNode<TK>[] path = new InternalNode<TK>[32];
|
||||||
int[] indices = new int[32];
|
int[] indices = new int[32];
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||||
|
|
@ -774,14 +762,14 @@ namespace PersistentOrderedMap
|
||||||
current = internalNode.Children[idx]!;
|
current = internalNode.Children[idx]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var leaf = current.AsLeaf<V>();
|
var leaf = current.AsLeaf<TV>();
|
||||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||||
|
|
||||||
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) index++;
|
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) index++;
|
||||||
|
|
||||||
if (index < leaf.Header.Count)
|
if (index < leaf.Header.Count)
|
||||||
{
|
{
|
||||||
nextKey = leaf.Keys[index];
|
nextKey = leaf.Keys![index];
|
||||||
nextValue = leaf.Values[index];
|
nextValue = leaf.Values[index];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -796,8 +784,8 @@ namespace PersistentOrderedMap
|
||||||
current = current.AsInternal().Children[0]!;
|
current = current.AsInternal().Children[0]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetLeaf = current.AsLeaf<V>();
|
var targetLeaf = current.AsLeaf<TV>();
|
||||||
nextKey = targetLeaf.Keys[0];
|
nextKey = targetLeaf.Keys![0];
|
||||||
nextValue = targetLeaf.Values[0];
|
nextValue = targetLeaf.Values[0];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -808,10 +796,10 @@ namespace PersistentOrderedMap
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetPredecessor<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out K prevKey, out V prevValue)
|
public static bool TryGetPredecessor<TK, TV, TStrategy>(Node<TK> root, TK key, TStrategy strategy, out TK prevKey, out TV prevValue)
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
InternalNode<K>[] path = new InternalNode<K>[32];
|
InternalNode<TK>[] path = new InternalNode<TK>[32];
|
||||||
int[] indices = new int[32];
|
int[] indices = new int[32];
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
|
||||||
|
|
@ -827,12 +815,12 @@ namespace PersistentOrderedMap
|
||||||
current = internalNode.Children[idx]!;
|
current = internalNode.Children[idx]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var leaf = current.AsLeaf<V>();
|
var leaf = current.AsLeaf<TV>();
|
||||||
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
int index = FindIndex(leaf, key, keyPrefix, strategy);
|
||||||
|
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
{
|
{
|
||||||
prevKey = leaf.Keys[index - 1];
|
prevKey = leaf.Keys![index - 1];
|
||||||
prevValue = leaf.Values[index - 1];
|
prevValue = leaf.Values[index - 1];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -848,9 +836,9 @@ namespace PersistentOrderedMap
|
||||||
current = internalNode.Children[internalNode.Header.Count]!;
|
current = internalNode.Children[internalNode.Header.Count]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetLeaf = current.AsLeaf<V>();
|
var targetLeaf = current.AsLeaf<TV>();
|
||||||
int last = targetLeaf.Header.Count - 1;
|
int last = targetLeaf.Header.Count - 1;
|
||||||
prevKey = targetLeaf.Keys[last];
|
prevKey = targetLeaf.Keys![last];
|
||||||
prevValue = targetLeaf.Values[last];
|
prevValue = targetLeaf.Values[last];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@ using System.Collections;
|
||||||
|
|
||||||
namespace PersistentOrderedMap;
|
namespace PersistentOrderedMap;
|
||||||
|
|
||||||
public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair<K, V>> where TStrategy : IKeyStrategy<K>
|
public abstract class BaseOrderedMap<TK, TV, TStrategy> : IEnumerable<KeyValuePair<TK, TV>> where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
internal Node<K> _root;
|
internal Node<TK> Root;
|
||||||
internal readonly TStrategy _strategy;
|
internal readonly TStrategy Strategy;
|
||||||
|
|
||||||
public int Count { get; protected set; }
|
public int Count { get; protected set; }
|
||||||
|
|
||||||
protected BaseOrderedMap(Node<K> root, TStrategy strategy, int count)
|
protected BaseOrderedMap(Node<TK> root, TStrategy strategy, int count)
|
||||||
{
|
{
|
||||||
_root = root ?? throw new ArgumentNullException(nameof(root));
|
Root = root ?? throw new ArgumentNullException(nameof(root));
|
||||||
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
|
Strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
|
||||||
Count = count;
|
Count = count;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -21,14 +21,14 @@ public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair
|
||||||
// Read Operations (Shared)
|
// Read Operations (Shared)
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
public bool TryGetValue(K key, out V value)
|
public bool TryGetValue(TK key, out TV value)
|
||||||
{
|
{
|
||||||
return BTreeFunctions.TryGetValue(_root, key, _strategy, out value);
|
return BTreeFunctions.TryGetValue(Root, key, Strategy, out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsKey(K key)
|
public bool ContainsKey(TK key)
|
||||||
{
|
{
|
||||||
return BTreeFunctions.TryGetValue<K,V, TStrategy>(_root, key, _strategy, out _);
|
return BTreeFunctions.TryGetValue<TK,TV, TStrategy>(Root, key, Strategy, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,26 +37,26 @@ public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair
|
||||||
// Bootstrap / Factory Helpers
|
// Bootstrap / Factory Helpers
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
public static PersistentOrderedMap<K, V, TStrategy> Create(TStrategy strategy)
|
public static PersistentOrderedMap<TK, TV, TStrategy> Create(TStrategy strategy)
|
||||||
{
|
{
|
||||||
// Start with an empty leaf owned by None so the first write triggers CoW.
|
// Start with an empty leaf owned by None so the first write triggers CoW.
|
||||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None, strategy.UsesPrefixes);
|
var emptyRoot = new LeafNode<TK, TV>(OwnerId.None, strategy.UsesPrefixes);
|
||||||
return new PersistentOrderedMap<K, V, TStrategy>(emptyRoot, strategy, 0);
|
return new PersistentOrderedMap<TK, TV, TStrategy>(emptyRoot, strategy, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TransientOrderedMap<K, V, TStrategy> CreateTransient(TStrategy strategy)
|
public static TransientOrderedMap<TK, TV, TStrategy> CreateTransient(TStrategy strategy)
|
||||||
{
|
{
|
||||||
var emptyRoot = new LeafNode<K, V>(OwnerId.None, strategy.UsesPrefixes);
|
var emptyRoot = new LeafNode<TK, TV>(OwnerId.None, strategy.UsesPrefixes);
|
||||||
return new TransientOrderedMap<K, V, TStrategy>(emptyRoot, strategy,0);
|
return new TransientOrderedMap<TK, TV, TStrategy>(emptyRoot, strategy,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public BTreeEnumerator<K, V, TStrategy> GetEnumerator()
|
public BTreeEnumerator<TK, TV, TStrategy> GetEnumerator()
|
||||||
{
|
{
|
||||||
return AsEnumerable().GetEnumerator();
|
return AsEnumerable().GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator<KeyValuePair<K, V>> IEnumerable<KeyValuePair<K, V>>.GetEnumerator()
|
IEnumerator<KeyValuePair<TK, TV>> IEnumerable<KeyValuePair<TK, TV>>.GetEnumerator()
|
||||||
{
|
{
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
@ -67,19 +67,19 @@ public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Full Scan
|
// 1. Full Scan
|
||||||
public BTreeEnumerable<K, V, TStrategy> AsEnumerable()
|
public BTreeEnumerable<TK, TV, TStrategy> AsEnumerable()
|
||||||
=> new(_root, _strategy, false, default, false, default);
|
=> new(Root, Strategy, false, default!, false, default!);
|
||||||
|
|
||||||
// 2. Exact Range
|
// 2. Exact Range
|
||||||
public BTreeEnumerable<K, V, TStrategy> Range(K min, K max)
|
public BTreeEnumerable<TK, TV, TStrategy> Range(TK min, TK max)
|
||||||
=> new(_root, _strategy, true, min, true, max);
|
=> new(Root, Strategy, true, min, true, max);
|
||||||
|
|
||||||
// 3. Start From (Open Ended)
|
// 3. Start From (Open Ended)
|
||||||
public BTreeEnumerable<K, V, TStrategy> From(K min) => new(_root, _strategy, true, min, false, default);
|
public BTreeEnumerable<TK, TV, TStrategy> From(TK min) => new(Root, Strategy, true, min, false, default!);
|
||||||
|
|
||||||
// 4. Until (Start at beginning)
|
// 4. Until (Start at beginning)
|
||||||
public BTreeEnumerable<K, V, TStrategy> Until(K max)
|
public BTreeEnumerable<TK, TV, TStrategy> Until(TK max)
|
||||||
=> new(_root, _strategy, false, default, true, max);
|
=> new(Root, Strategy, false, default!, true, max);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -87,19 +87,19 @@ public abstract class BaseOrderedMap<K, V, TStrategy> : IEnumerable<KeyValuePair
|
||||||
// Navigation Operations
|
// Navigation Operations
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
public bool TryGetMin(out K key, out V value) => BTreeFunctions.TryGetMin(_root, out key, out value);
|
public bool TryGetMin(out TK key, out TV value) => BTreeFunctions.TryGetMin(Root, out key, out value);
|
||||||
|
|
||||||
public bool TryGetMax(out K key, out V value) => BTreeFunctions.TryGetMax(_root, out key, out value);
|
public bool TryGetMax(out TK key, out TV value) => BTreeFunctions.TryGetMax(Root, out key, out value);
|
||||||
|
|
||||||
public bool TryGetSuccessor(K key, out K nextKey, out V nextValue) => BTreeFunctions.TryGetSuccessor(_root, key, _strategy, out nextKey, out nextValue);
|
public bool TryGetSuccessor(TK key, out TK nextKey, out TV nextValue) => BTreeFunctions.TryGetSuccessor(Root, key, Strategy, out nextKey, out nextValue);
|
||||||
|
|
||||||
public bool TryGetPredecessor(K key, out K prevKey, out V prevValue) => BTreeFunctions.TryGetPredecessor(_root, key, _strategy, out prevKey, out prevValue);
|
public bool TryGetPredecessor(TK key, out TK prevKey, out TV prevValue) => BTreeFunctions.TryGetPredecessor(Root, key, Strategy, out prevKey, out prevValue);
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// Set Operations (Linear Merge O(N+M))
|
// Set Operations (Linear Merge O(N+M))
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<K, V>> Intersect(BaseOrderedMap<K, V, TStrategy> other)
|
public IEnumerable<KeyValuePair<TK, TV>> Intersect(BaseOrderedMap<TK, TV, TStrategy> other)
|
||||||
{
|
{
|
||||||
using var enum1 = this.GetEnumerator();
|
using var enum1 = this.GetEnumerator();
|
||||||
using var enum2 = other.GetEnumerator();
|
using var enum2 = other.GetEnumerator();
|
||||||
|
|
@ -109,7 +109,7 @@ public IEnumerable<KeyValuePair<K, V>> Intersect(BaseOrderedMap<K, V, TStrategy>
|
||||||
|
|
||||||
while (has1 && has2)
|
while (has1 && has2)
|
||||||
{
|
{
|
||||||
int cmp = _strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
||||||
if (cmp == 0)
|
if (cmp == 0)
|
||||||
{
|
{
|
||||||
yield return enum1.Current;
|
yield return enum1.Current;
|
||||||
|
|
@ -121,7 +121,7 @@ public IEnumerable<KeyValuePair<K, V>> Intersect(BaseOrderedMap<K, V, TStrategy>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<K, V>> Except(BaseOrderedMap<K, V, TStrategy> other)
|
public IEnumerable<KeyValuePair<TK, TV>> Except(BaseOrderedMap<TK, TV, TStrategy> other)
|
||||||
{
|
{
|
||||||
using var enum1 = this.GetEnumerator();
|
using var enum1 = this.GetEnumerator();
|
||||||
using var enum2 = other.GetEnumerator();
|
using var enum2 = other.GetEnumerator();
|
||||||
|
|
@ -131,7 +131,7 @@ public IEnumerable<KeyValuePair<K, V>> Except(BaseOrderedMap<K, V, TStrategy> ot
|
||||||
|
|
||||||
while (has1 && has2)
|
while (has1 && has2)
|
||||||
{
|
{
|
||||||
int cmp = _strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
||||||
if (cmp == 0)
|
if (cmp == 0)
|
||||||
{
|
{
|
||||||
has1 = enum1.MoveNext();
|
has1 = enum1.MoveNext();
|
||||||
|
|
@ -155,7 +155,7 @@ public IEnumerable<KeyValuePair<K, V>> Except(BaseOrderedMap<K, V, TStrategy> ot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<K, V>> SymmetricExcept(BaseOrderedMap<K, V, TStrategy> other)
|
public IEnumerable<KeyValuePair<TK, TV>> SymmetricExcept(BaseOrderedMap<TK, TV, TStrategy> other)
|
||||||
{
|
{
|
||||||
using var enum1 = this.GetEnumerator();
|
using var enum1 = this.GetEnumerator();
|
||||||
using var enum2 = other.GetEnumerator();
|
using var enum2 = other.GetEnumerator();
|
||||||
|
|
@ -165,7 +165,7 @@ public IEnumerable<KeyValuePair<K, V>> SymmetricExcept(BaseOrderedMap<K, V, TStr
|
||||||
|
|
||||||
while (has1 && has2)
|
while (has1 && has2)
|
||||||
{
|
{
|
||||||
int cmp = _strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key);
|
||||||
if (cmp == 0)
|
if (cmp == 0)
|
||||||
{
|
{
|
||||||
has1 = enum1.MoveNext();
|
has1 = enum1.MoveNext();
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,27 @@ namespace PersistentOrderedMap;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public struct BTreeEnumerable<K, V, TStrategy> : IEnumerable<KeyValuePair<K, V>>
|
public struct BTreeEnumerable<TK, TV, TStrategy> : IEnumerable<KeyValuePair<TK, TV>>
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
private readonly Node<K> _root;
|
private readonly Node<TK> _root;
|
||||||
private readonly TStrategy _strategy;
|
private readonly TStrategy _strategy;
|
||||||
private readonly K _min, _max;
|
private readonly TK _min, _max;
|
||||||
private readonly bool _hasMin, _hasMax;
|
private readonly bool _hasMin, _hasMax;
|
||||||
|
|
||||||
public BTreeEnumerable(Node<K> root, TStrategy strategy, bool hasMin, K min, bool hasMax, K max)
|
public BTreeEnumerable(Node<TK> root, TStrategy strategy, bool hasMin, TK min, bool hasMax, TK max)
|
||||||
{
|
{
|
||||||
_root = root; _strategy = strategy;
|
_root = root; _strategy = strategy;
|
||||||
_hasMin = hasMin; _min = min;
|
_hasMin = hasMin; _min = min;
|
||||||
_hasMax = hasMax; _max = max;
|
_hasMax = hasMax; _max = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BTreeEnumerator<K, V, TStrategy> GetEnumerator()
|
public BTreeEnumerator<TK, TV, TStrategy> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new BTreeEnumerator<K, V, TStrategy>(_root, _strategy, _hasMin, _min, _hasMax, _max);
|
return new BTreeEnumerator<TK, TV, TStrategy>(_root, _strategy, _hasMin, _min, _hasMax, _max);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator<KeyValuePair<K, V>> IEnumerable<KeyValuePair<K, V>>.GetEnumerator() => GetEnumerator();
|
IEnumerator<KeyValuePair<TK, TV>> IEnumerable<KeyValuePair<TK, TV>>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,52 +35,52 @@ where TStrategy : IKeyStrategy<K>
|
||||||
// all int-indexed data structures (or bit partitioned ones).
|
// all int-indexed data structures (or bit partitioned ones).
|
||||||
// This should be enough for anyone.
|
// This should be enough for anyone.
|
||||||
[InlineArray(16)]
|
[InlineArray(16)]
|
||||||
internal struct IterNodeBuffer<K>
|
internal struct IterNodeBuffer<TK>
|
||||||
{
|
{
|
||||||
private Node<K> _element0;
|
private Node<TK> _element0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[InlineArray(16)]
|
[InlineArray(16)]
|
||||||
internal struct IterIndexBuffer<K>
|
internal struct IterIndexBuffer
|
||||||
{
|
{
|
||||||
private int _element0;
|
private int _element0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
public struct BTreeEnumerator<TK, TV, TStrategy> : IEnumerator<KeyValuePair<TK, TV>>
|
||||||
where TStrategy : IKeyStrategy<K>
|
where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
private readonly TStrategy _strategy;
|
private readonly TStrategy _strategy;
|
||||||
private readonly Node<K> _root;
|
private readonly Node<TK> _root;
|
||||||
|
|
||||||
// --- BOUNDS ---
|
// --- BOUNDS ---
|
||||||
private readonly bool _hasMax;
|
private readonly bool _hasMax;
|
||||||
private readonly K _maxKey;
|
private readonly TK _maxKey;
|
||||||
private readonly bool _hasMin;
|
private readonly bool _hasMin;
|
||||||
private readonly K _minKey;
|
private readonly TK _minKey;
|
||||||
|
|
||||||
// --- INLINE STACK ---
|
// --- INLINE STACK ---
|
||||||
private IterNodeBuffer<K> _nodeStack;
|
private IterNodeBuffer<TK> _nodeStack;
|
||||||
private IterIndexBuffer<K> _indexStack;
|
private IterIndexBuffer _indexStack;
|
||||||
private int _depth;
|
private int _depth;
|
||||||
|
|
||||||
// --- STATE ---
|
// --- STATE ---
|
||||||
private LeafNode<K, V>? _currentLeaf;
|
private LeafNode<TK, TV>? _currentLeaf;
|
||||||
private int _currentLeafIndex;
|
private int _currentLeafIndex;
|
||||||
private KeyValuePair<K, V> _current;
|
private KeyValuePair<TK, TV> _current;
|
||||||
|
|
||||||
// Unified Constructor
|
// Unified Constructor
|
||||||
// We use boolean flags because 'K' might be a struct where 'null' is impossible.
|
// We use boolean flags because 'K' might be a struct where 'null' is impossible.
|
||||||
public BTreeEnumerator(Node<K> root, TStrategy strategy, bool hasMin, K minKey, bool hasMax, K maxKey)
|
public BTreeEnumerator(Node<TK>? root, TStrategy strategy, bool hasMin, TK minKey, bool hasMax, TK maxKey)
|
||||||
{
|
{
|
||||||
_root = root;
|
_root = root!;
|
||||||
_strategy = strategy;
|
_strategy = strategy;
|
||||||
_hasMax = hasMax;
|
_hasMax = hasMax;
|
||||||
_maxKey = maxKey;
|
_maxKey = maxKey;
|
||||||
_hasMin = hasMin;
|
_hasMin = hasMin;
|
||||||
_minKey = minKey;
|
_minKey = minKey;
|
||||||
|
|
||||||
_nodeStack = new IterNodeBuffer<K>();
|
_nodeStack = new IterNodeBuffer<TK>();
|
||||||
_indexStack = new IterIndexBuffer<K>(); // Explicit struct init
|
_indexStack = new IterIndexBuffer(); // Explicit struct init
|
||||||
_depth = 0;
|
_depth = 0;
|
||||||
_currentLeaf = null;
|
_currentLeaf = null;
|
||||||
_currentLeafIndex = -1;
|
_currentLeafIndex = -1;
|
||||||
|
|
@ -102,7 +102,7 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
// Logic 1: Unbounded Start (Go to very first item)
|
// Logic 1: Unbounded Start (Go to very first item)
|
||||||
private void DiveLeft()
|
private void DiveLeft()
|
||||||
{
|
{
|
||||||
Node<K> node = _root;
|
Node<TK> node = _root;
|
||||||
_depth = 0;
|
_depth = 0;
|
||||||
|
|
||||||
while (!node.IsLeaf)
|
while (!node.IsLeaf)
|
||||||
|
|
@ -114,14 +114,14 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
node = internalNode.Children[0]!;
|
node = internalNode.Children[0]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentLeaf = node.AsLeaf<V>();
|
_currentLeaf = node.AsLeaf<TV>();
|
||||||
_currentLeafIndex = -1; // Position before the first element (0)
|
_currentLeafIndex = -1; // Position before the first element (0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic 2: Bounded Start (Go to specific key)
|
// Logic 2: Bounded Start (Go to specific key)
|
||||||
private void Seek(K key)
|
private void Seek(TK key)
|
||||||
{
|
{
|
||||||
Node<K> node = _root;
|
Node<TK> node = _root;
|
||||||
_depth = 0;
|
_depth = 0;
|
||||||
long keyPrefix = _strategy.UsesPrefixes ? _strategy.GetPrefix(key) : 0;
|
long keyPrefix = _strategy.UsesPrefixes ? _strategy.GetPrefix(key) : 0;
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
while (!node.IsLeaf)
|
while (!node.IsLeaf)
|
||||||
{
|
{
|
||||||
var internalNode = node.AsInternal();
|
var internalNode = node.AsInternal();
|
||||||
int idx = BTreeFunctions.FindRoutingIndex<K, TStrategy>(internalNode, key, keyPrefix, _strategy);
|
int idx = BTreeFunctions.FindRoutingIndex(internalNode, key, keyPrefix, _strategy);
|
||||||
|
|
||||||
_nodeStack[_depth] = internalNode;
|
_nodeStack[_depth] = internalNode;
|
||||||
_indexStack[_depth] = idx;
|
_indexStack[_depth] = idx;
|
||||||
|
|
@ -140,8 +140,8 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find index in Leaf
|
// Find index in Leaf
|
||||||
_currentLeaf = node.AsLeaf<V>();
|
_currentLeaf = node.AsLeaf<TV>();
|
||||||
int index = BTreeFunctions.FindIndex<K,V, TStrategy>(_currentLeaf, key, keyPrefix, _strategy);
|
int index = BTreeFunctions.FindIndex(_currentLeaf, key, keyPrefix, _strategy);
|
||||||
|
|
||||||
// Set position to (index - 1) so that the first MoveNext() lands on 'index'
|
// Set position to (index - 1) so that the first MoveNext() lands on 'index'
|
||||||
_currentLeafIndex = index - 1;
|
_currentLeafIndex = index - 1;
|
||||||
|
|
@ -158,14 +158,14 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
if (_hasMax)
|
if (_hasMax)
|
||||||
{
|
{
|
||||||
// If Current Key > Max Key, we are done.
|
// If Current Key > Max Key, we are done.
|
||||||
if (_strategy.Compare(_currentLeaf.Keys[_currentLeafIndex], _maxKey) > 0)
|
if (_strategy.Compare(_currentLeaf.Keys![_currentLeafIndex], _maxKey) > 0)
|
||||||
{
|
{
|
||||||
_currentLeaf = null; // Close iterator
|
_currentLeaf = null; // Close iterator
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_current = new KeyValuePair<K, V>(_currentLeaf.Keys[_currentLeafIndex], _currentLeaf.Values[_currentLeafIndex]);
|
_current = new KeyValuePair<TK, TV>(_currentLeaf.Keys![_currentLeafIndex], _currentLeaf.Values[_currentLeafIndex]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,14 +176,14 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
// Check Max Bound immediately for the first item
|
// Check Max Bound immediately for the first item
|
||||||
if (_hasMax)
|
if (_hasMax)
|
||||||
{
|
{
|
||||||
if (_strategy.Compare(_currentLeaf!.Keys[0], _maxKey) > 0)
|
if (_strategy.Compare(_currentLeaf.Keys![0], _maxKey) > 0)
|
||||||
{
|
{
|
||||||
_currentLeaf = null;
|
_currentLeaf = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_current = new KeyValuePair<K, V>(_currentLeaf.Keys[0], _currentLeaf.Values[0]);
|
_current = new KeyValuePair<TK, TV>(_currentLeaf.Keys![0], _currentLeaf.Values[0]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +204,7 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
_indexStack[_depth] = nextIndex;
|
_indexStack[_depth] = nextIndex;
|
||||||
_depth++;
|
_depth++;
|
||||||
|
|
||||||
Node<K> node = internalNode.Children[nextIndex]!;
|
Node<TK> node = internalNode.Children[nextIndex]!;
|
||||||
while (!node.IsLeaf)
|
while (!node.IsLeaf)
|
||||||
{
|
{
|
||||||
_nodeStack[_depth] = node;
|
_nodeStack[_depth] = node;
|
||||||
|
|
@ -213,7 +213,7 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
node = node.AsInternal().Children[0]!;
|
node = node.AsInternal().Children[0]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentLeaf = node.AsLeaf<V>();
|
_currentLeaf = node.AsLeaf<TV>();
|
||||||
_currentLeafIndex = 0;
|
_currentLeafIndex = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -221,7 +221,7 @@ public struct BTreeEnumerator<K, V, TStrategy> : IEnumerator<KeyValuePair<K, V>>
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValuePair<K, V> Current => _current;
|
public KeyValuePair<TK, TV> Current => _current;
|
||||||
object IEnumerator.Current => _current;
|
object IEnumerator.Current => _current;
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Intrinsics;
|
|
||||||
using System.Runtime.Intrinsics.X86;
|
|
||||||
|
|
||||||
namespace PersistentOrderedMap;
|
namespace PersistentOrderedMap;
|
||||||
|
|
||||||
|
|
@ -9,10 +5,10 @@ using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
public interface IKeyStrategy<K>
|
public interface IKeyStrategy<in TK>
|
||||||
{
|
{
|
||||||
int Compare(K x, K y);
|
int Compare(TK x, TK y);
|
||||||
long GetPrefix(K key);
|
long GetPrefix(TK key);
|
||||||
|
|
||||||
bool UsesPrefixes => true;
|
bool UsesPrefixes => true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
// This is a comparable strategy that may squeeze some extra time out of value types
|
// This is a comparable strategy that may squeeze some extra time out of value types
|
||||||
|
|
||||||
public readonly struct ComparableStrategy<K> : IKeyStrategy<K> where K : IComparable<K>
|
public readonly struct ComparableStrategy<TK> : IKeyStrategy<TK> where TK : IComparable<TK>
|
||||||
{
|
{
|
||||||
public bool UsesPrefixes => false;
|
public bool UsesPrefixes => false;
|
||||||
public bool UseBinarySearch => true;
|
public bool UseBinarySearch => true;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public long GetPrefix(K key) => 0;
|
public long GetPrefix(TK key) => 0;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Compare(K x, K y) => x.CompareTo(y);
|
public int Compare(TK x, TK y) => x.CompareTo(y);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,21 @@ using System.Runtime.CompilerServices;
|
||||||
/// A universal key strategy for any type that relies on standard comparisons
|
/// A universal key strategy for any type that relies on standard comparisons
|
||||||
/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes.
|
/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct StandardStrategy<K> : IKeyStrategy<K>
|
public readonly struct StandardStrategy<TK> : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
private readonly IComparer<K> _comparer;
|
private readonly IComparer<TK> _comparer;
|
||||||
|
|
||||||
// If no comparer is provided, it defaults to Comparer<K>.Default
|
// If no comparer is provided, it defaults to Comparer<K>.Default
|
||||||
// which automatically uses IComparable<K> if the type implements it.
|
// which automatically uses IComparable<K> if the type implements it.
|
||||||
|
|
||||||
public StandardStrategy()
|
public StandardStrategy()
|
||||||
{
|
{
|
||||||
_comparer = Comparer<K>.Default;
|
_comparer = Comparer<TK>.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StandardStrategy(IComparer<K>? comparer)
|
public StandardStrategy(IComparer<TK>? comparer)
|
||||||
{
|
{
|
||||||
_comparer = comparer ?? Comparer<K>.Default;
|
_comparer = comparer ?? Comparer<TK>.Default;
|
||||||
}
|
}
|
||||||
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
// Tell the B-Tree to skip SIMD routing and just use LinearSearch
|
||||||
public bool UsesPrefixes => false;
|
public bool UsesPrefixes => false;
|
||||||
|
|
@ -27,16 +27,16 @@ public readonly struct StandardStrategy<K> : IKeyStrategy<K>
|
||||||
// This will never be called because UsesPrefixes is false,
|
// This will never be called because UsesPrefixes is false,
|
||||||
// but we must satisfy the interface.
|
// but we must satisfy the interface.
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public long GetPrefix(K key) => 0;
|
public long GetPrefix(TK key) => 0;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Compare(K x, K y)
|
public int Compare(TK x, TK y)
|
||||||
{
|
{
|
||||||
return _comparer.Compare(x, y);
|
return _comparer.Compare(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public readonly struct StandardStrategy2<K, TComparer> : IKeyStrategy<K>
|
public readonly struct StandardStrategy2<TK, TComparer> : IKeyStrategy<TK>
|
||||||
where TComparer : struct, IComparer<K>
|
where TComparer : struct, IComparer<TK>
|
||||||
{
|
{
|
||||||
private readonly TComparer _comparer;
|
private readonly TComparer _comparer;
|
||||||
|
|
||||||
|
|
@ -46,8 +46,8 @@ public readonly struct StandardStrategy2<K, TComparer> : IKeyStrategy<K>
|
||||||
public bool UseBinarySearch => true;
|
public bool UseBinarySearch => true;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int Compare(K x, K y) => _comparer.Compare(x, y);
|
public int Compare(TK x, TK y) => _comparer.Compare(x, y);
|
||||||
|
|
||||||
public long GetPrefix(K key) => 0;
|
public long GetPrefix(TK key) => 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,17 @@ public struct NodeHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
[InlineArray(32)]
|
[InlineArray(32)]
|
||||||
public struct KeyBuffer<K>
|
public struct KeyBuffer<TK>
|
||||||
{
|
{
|
||||||
private K _element0;
|
private TK _element0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constraint: Internal Nodes fixed at 32 children.
|
// Constraint: Internal Nodes fixed at 32 children.
|
||||||
// This removes the need for a separate array allocation for children references.
|
// This removes the need for a separate array allocation for children references.
|
||||||
[InlineArray(32)]
|
[InlineArray(32)]
|
||||||
public struct NodeBuffer<V>
|
public struct NodeBuffer<TV>
|
||||||
{
|
{
|
||||||
private Node<V>? _element0;
|
private Node<TV>? _element0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[InlineArray(32)]
|
[InlineArray(32)]
|
||||||
|
|
@ -52,7 +52,7 @@ internal struct InternalPrefixBuffer
|
||||||
private long _element0;
|
private long _element0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class Node<K>
|
public abstract class Node<TK>
|
||||||
{
|
{
|
||||||
public NodeHeader Header;
|
public NodeHeader Header;
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ public abstract class Node<K>
|
||||||
Header = new NodeHeader(owner, 0, flags);
|
Header = new NodeHeader(owner, 0, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Span<K> GetKeys();
|
public abstract Span<TK> GetKeys();
|
||||||
|
|
||||||
// Abstract access to prefixes regardless of storage backing
|
// Abstract access to prefixes regardless of storage backing
|
||||||
public abstract Span<long> AllPrefixes { get; }
|
public abstract Span<long> AllPrefixes { get; }
|
||||||
|
|
@ -71,47 +71,47 @@ public abstract class Node<K>
|
||||||
|
|
||||||
public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0;
|
public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0;
|
||||||
|
|
||||||
public abstract Node<K> EnsureEditable(OwnerId transactionId);
|
public abstract Node<TK> EnsureEditable(OwnerId transactionId);
|
||||||
|
|
||||||
public void SetCount(int newCount) => Header.Count = (byte)newCount;
|
public void SetCount(int newCount) => Header.Count = (byte)newCount;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public LeafNode<K, V> AsLeaf<V>()
|
public LeafNode<TK, TV> AsLeaf<TV>()
|
||||||
{
|
{
|
||||||
// Zero-overhead cast. Assumes you checked IsLeaf or know logic flow.
|
// Zero-overhead cast. Assumes you checked IsLeaf or know logic flow.
|
||||||
return Unsafe.As<LeafNode<K, V>>(this);
|
return Unsafe.As<LeafNode<TK, TV>>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public InternalNode<K> AsInternal()
|
public InternalNode<TK> AsInternal()
|
||||||
{
|
{
|
||||||
// Zero-overhead cast. Assumes you checked !IsLeaf or know logic flow.
|
// Zero-overhead cast. Assumes you checked !IsLeaf or know logic flow.
|
||||||
return Unsafe.As<InternalNode<K>>(this);
|
return Unsafe.As<InternalNode<TK>>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public PrefixInternalNode<K> AsPrefixInternal()
|
public PrefixInternalNode<TK> AsPrefixInternal()
|
||||||
{
|
{
|
||||||
return Unsafe.As<PrefixInternalNode<K>>(this);
|
return Unsafe.As<PrefixInternalNode<TK>>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LeafNode<K, V> : Node<K>
|
public sealed class LeafNode<TK, TV> : Node<TK>
|
||||||
{
|
{
|
||||||
public const int Capacity = 64;
|
public const int Capacity = 64;
|
||||||
public const int MergeThreshold = 8;
|
public const int MergeThreshold = 8;
|
||||||
|
|
||||||
public K[]? Keys;
|
public TK[]? Keys;
|
||||||
public V[] Values;
|
public TV[] Values;
|
||||||
|
|
||||||
internal long[]? _prefixes;
|
private long[]? _prefixes;
|
||||||
|
|
||||||
public override Span<long> AllPrefixes => _prefixes != null ? _prefixes : Span<long>.Empty;
|
public override Span<long> AllPrefixes => _prefixes != null ? _prefixes : Span<long>.Empty;
|
||||||
|
|
||||||
public LeafNode(OwnerId owner, bool usePrefixes) : base(owner, NodeFlags.IsLeaf | (usePrefixes ? NodeFlags.HasPrefixes : NodeFlags.None))
|
public LeafNode(OwnerId owner, bool usePrefixes) : base(owner, NodeFlags.IsLeaf | (usePrefixes ? NodeFlags.HasPrefixes : NodeFlags.None))
|
||||||
{
|
{
|
||||||
Keys = new K[Capacity];
|
Keys = new TK[Capacity];
|
||||||
Values = new V[Capacity];
|
Values = new TV[Capacity];
|
||||||
if (usePrefixes)
|
if (usePrefixes)
|
||||||
{
|
{
|
||||||
_prefixes = new long[Capacity];
|
_prefixes = new long[Capacity];
|
||||||
|
|
@ -119,21 +119,21 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy Constructor for CoW
|
// Copy Constructor for CoW
|
||||||
private LeafNode(LeafNode<K, V> original, OwnerId newOwner)
|
private LeafNode(LeafNode<TK, TV> original, OwnerId newOwner)
|
||||||
: base(newOwner, original.Header.Flags)
|
: base(newOwner, original.Header.Flags)
|
||||||
{
|
{
|
||||||
Keys = new K[Capacity];
|
Keys = new TK[Capacity];
|
||||||
Values = new V[Capacity];
|
Values = new TV[Capacity];
|
||||||
Header.Count = original.Header.Count; _prefixes = new long[Capacity];
|
Header.Count = original.Header.Count; _prefixes = new long[Capacity];
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
Array.Copy(original.Keys, Keys, original.Header.Count);
|
Array.Copy(original.Keys!, Keys, original.Header.Count);
|
||||||
Array.Copy(original.Values, Values, original.Header.Count);
|
Array.Copy(original.Values, Values, original.Header.Count);
|
||||||
if (original._prefixes != null)
|
if (original._prefixes != null)
|
||||||
Array.Copy(original._prefixes, _prefixes, original.Header.Count);
|
Array.Copy(original._prefixes, _prefixes, original.Header.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Node<K> EnsureEditable(OwnerId transactionId)
|
public override Node<TK> EnsureEditable(OwnerId transactionId)
|
||||||
{
|
{
|
||||||
// CASE 1: Persistent Mode (transactionId is None).
|
// CASE 1: Persistent Mode (transactionId is None).
|
||||||
// We MUST create a copy, because we cannot distinguish "Shared Immutable Node (0)"
|
// We MUST create a copy, because we cannot distinguish "Shared Immutable Node (0)"
|
||||||
|
|
@ -142,7 +142,7 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
// we won't copy the same fresh node twice.
|
// we won't copy the same fresh node twice.
|
||||||
if (transactionId == OwnerId.None)
|
if (transactionId == OwnerId.None)
|
||||||
{
|
{
|
||||||
return new LeafNode<K, V>(this, OwnerId.None);
|
return new LeafNode<TK, TV>(this, OwnerId.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 2: Transient Mode.
|
// CASE 2: Transient Mode.
|
||||||
|
|
@ -153,26 +153,26 @@ public sealed class LeafNode<K, V> : Node<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 3: CoW needed (Ownership mismatch).
|
// CASE 3: CoW needed (Ownership mismatch).
|
||||||
return new LeafNode<K, V>(this, transactionId);
|
return new LeafNode<TK, TV>(this, transactionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Span<K> GetKeys()
|
public override Span<TK> GetKeys()
|
||||||
{
|
{
|
||||||
return Keys.AsSpan(0, Header.Count);
|
return Keys.AsSpan(0, Header.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span<V> GetValues()
|
public Span<TV> GetValues()
|
||||||
{
|
{
|
||||||
return Values.AsSpan(0, Header.Count);
|
return Values.AsSpan(0, Header.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InternalNode<K> : Node<K>
|
public class InternalNode<TK> : Node<TK>
|
||||||
{
|
{
|
||||||
public const int Capacity = 32;
|
public const int Capacity = 32;
|
||||||
|
|
||||||
public KeyBuffer<K> Keys;
|
public KeyBuffer<TK> Keys;
|
||||||
public NodeBuffer<K> Children;
|
public NodeBuffer<TK> Children;
|
||||||
|
|
||||||
public override Span<long> AllPrefixes => Span<long>.Empty;
|
public override Span<long> AllPrefixes => Span<long>.Empty;
|
||||||
|
|
||||||
|
|
@ -182,7 +182,7 @@ public class InternalNode<K> : Node<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed CoW Constructor
|
// Fixed CoW Constructor
|
||||||
protected InternalNode(InternalNode<K> original, OwnerId newOwner, NodeFlags flags)
|
protected InternalNode(InternalNode<TK> original, OwnerId newOwner, NodeFlags flags)
|
||||||
: base(newOwner, flags)
|
: base(newOwner, flags)
|
||||||
{
|
{
|
||||||
Header.Count = original.Header.Count;
|
Header.Count = original.Header.Count;
|
||||||
|
|
@ -195,28 +195,28 @@ public class InternalNode<K> : Node<K>
|
||||||
|
|
||||||
// The missing method needed by BTreeFunctions for routing
|
// The missing method needed by BTreeFunctions for routing
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Span<Node<K>> GetChildren()
|
public Span<Node<TK>> GetChildren()
|
||||||
{
|
{
|
||||||
// An internal node always has (Count + 1) children
|
// An internal node always has (Count + 1) children
|
||||||
return MemoryMarshal.CreateSpan(ref Children[0], Header.Count + 1);
|
return MemoryMarshal.CreateSpan(ref Children[0]!, Header.Count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Span<K> GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count);
|
public override Span<TK> GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count);
|
||||||
|
|
||||||
public override Node<K> EnsureEditable(OwnerId transactionId)
|
public override Node<TK> EnsureEditable(OwnerId transactionId)
|
||||||
{
|
{
|
||||||
if (transactionId == OwnerId.None) return new InternalNode<K>(this, OwnerId.None, Header.Flags);
|
if (transactionId == OwnerId.None) return new InternalNode<TK>(this, OwnerId.None, Header.Flags);
|
||||||
if (Header.Owner == transactionId) return this;
|
if (Header.Owner == transactionId) return this;
|
||||||
return new InternalNode<K>(this, transactionId, Header.Flags);
|
return new InternalNode<TK>(this, transactionId, Header.Flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public sealed class PrefixInternalNode<K> : InternalNode<K>
|
public sealed class PrefixInternalNode<TK> : InternalNode<TK>
|
||||||
{
|
{
|
||||||
internal InternalPrefixBuffer _prefixBuffer;
|
internal InternalPrefixBuffer PrefixBuffer;
|
||||||
|
|
||||||
public override Span<long> AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity);
|
public override Span<long> AllPrefixes => MemoryMarshal.CreateSpan(ref PrefixBuffer[0], Capacity);
|
||||||
|
|
||||||
public PrefixInternalNode(OwnerId owner)
|
public PrefixInternalNode(OwnerId owner)
|
||||||
: base(owner, NodeFlags.HasPrefixes)
|
: base(owner, NodeFlags.HasPrefixes)
|
||||||
|
|
@ -224,18 +224,18 @@ public sealed class PrefixInternalNode<K> : InternalNode<K>
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoW Constructor
|
// CoW Constructor
|
||||||
private PrefixInternalNode(PrefixInternalNode<K> original, OwnerId newOwner)
|
private PrefixInternalNode(PrefixInternalNode<TK> original, OwnerId newOwner)
|
||||||
: base(original, newOwner, original.Header.Flags)
|
: base(original, newOwner, original.Header.Flags)
|
||||||
{
|
{
|
||||||
// Copy the base Keys and Children, then blit the prefix buffer
|
// Copy the base Keys and Children, then blit the prefix buffer
|
||||||
this._prefixBuffer = original._prefixBuffer;
|
this.PrefixBuffer = original.PrefixBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Node<K> EnsureEditable(OwnerId transactionId)
|
public override Node<TK> EnsureEditable(OwnerId transactionId)
|
||||||
{
|
{
|
||||||
if (transactionId == OwnerId.None) return new PrefixInternalNode<K>(this, OwnerId.None);
|
if (transactionId == OwnerId.None) return new PrefixInternalNode<TK>(this, OwnerId.None);
|
||||||
if (Header.Owner == transactionId) return this;
|
if (Header.Owner == transactionId) return this;
|
||||||
return new PrefixInternalNode<K>(this, transactionId);
|
return new PrefixInternalNode<TK>(this, transactionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,43 @@
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace PersistentOrderedMap;
|
namespace PersistentOrderedMap;
|
||||||
|
|
||||||
public sealed class PersistentOrderedMap<K, V, TStrategy> : BaseOrderedMap<K, V, TStrategy>, IEnumerable, IEnumerable<KeyValuePair<K, V>> where TStrategy : IKeyStrategy<K>
|
public sealed class PersistentOrderedMap<TK, TV, TStrategy> : BaseOrderedMap<TK, TV, TStrategy> where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
internal PersistentOrderedMap(Node<K> root, TStrategy strategy, int count)
|
internal PersistentOrderedMap(Node<TK> root, TStrategy strategy, int count)
|
||||||
: base(root, strategy, count) { }
|
: base(root, strategy, count) { }
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// Immutable Write API (Returns new Map)
|
// Immutable Write API (Returns new Map)
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
public PersistentOrderedMap<K, V, TStrategy> Set(K key, V value)
|
public PersistentOrderedMap<TK, TV, TStrategy> Set(TK key, TV value)
|
||||||
{
|
{
|
||||||
// OPTIMIZATION: Use OwnerId.None (0).
|
// OPTIMIZATION: Use OwnerId.None (0).
|
||||||
// This signals EnsureEditable to always copy the root path,
|
// This signals EnsureEditable to always copy the root path,
|
||||||
// producing a new tree of nodes that also have OwnerId.None.
|
// producing a new tree of nodes that also have OwnerId.None.
|
||||||
var newRoot = BTreeFunctions.Set(_root, key, value, _strategy, OwnerId.None, out bool countChanged);
|
var newRoot = BTreeFunctions.Set(Root, key, value, Strategy, OwnerId.None, out bool countChanged);
|
||||||
return new PersistentOrderedMap<K, V, TStrategy>(newRoot, _strategy, countChanged ? Count + 1 : Count);
|
return new PersistentOrderedMap<TK, TV, TStrategy>(newRoot, Strategy, countChanged ? Count + 1 : Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PersistentOrderedMap<K, V, TStrategy> Empty(TStrategy strategy)
|
public static PersistentOrderedMap<TK, TV, TStrategy> Empty(TStrategy strategy)
|
||||||
{
|
{
|
||||||
// Create an empty Leaf Node.
|
// Create an empty Leaf Node.
|
||||||
// 'default(OwnerId)' (usually 0) marks this node as Immutable/Persistent.
|
// 'default(OwnerId)' (usually 0) marks this node as Immutable/Persistent.
|
||||||
// This ensures that any subsequent Set/Remove will clone this node
|
// This ensures that any subsequent Set/Remove will clone this node
|
||||||
// instead of modifying it in place.
|
// instead of modifying it in place.
|
||||||
var emptyRoot = new LeafNode<K, V>(default(OwnerId), strategy.UsesPrefixes);
|
var emptyRoot = new LeafNode<TK, TV>(default(OwnerId), strategy.UsesPrefixes);
|
||||||
|
|
||||||
return new PersistentOrderedMap<K, V, TStrategy>(emptyRoot, strategy, 0);
|
return new PersistentOrderedMap<TK, TV, TStrategy>(emptyRoot, strategy, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersistentOrderedMap<K, V, TStrategy> Remove(K key)
|
public PersistentOrderedMap<TK, TV, TStrategy> Remove(TK key)
|
||||||
{
|
{
|
||||||
var newRoot = BTreeFunctions.Remove<K,V, TStrategy>(_root, key, _strategy, OwnerId.None, out bool removed);
|
var newRoot = BTreeFunctions.Remove<TK,TV, TStrategy>(Root, key, Strategy, OwnerId.None, out bool removed);
|
||||||
if (!removed) return this;
|
if (!removed) return this;
|
||||||
return new PersistentOrderedMap<K, V, TStrategy>(newRoot, _strategy, Count - 1);
|
return new PersistentOrderedMap<TK, TV, TStrategy>(newRoot, Strategy, Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransientOrderedMap<K, V, TStrategy> ToTransient()
|
public TransientOrderedMap<TK, TV, TStrategy> ToTransient()
|
||||||
{
|
{
|
||||||
return new TransientOrderedMap<K, V, TStrategy>(_root, _strategy, Count);
|
return new TransientOrderedMap<TK, TV, TStrategy>(Root, Strategy, Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,34 @@
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace PersistentOrderedMap;
|
namespace PersistentOrderedMap;
|
||||||
|
|
||||||
public sealed class TransientOrderedMap<K, V, TStrategy> : BaseOrderedMap<K, V, TStrategy> where TStrategy : IKeyStrategy<K>
|
public sealed class TransientOrderedMap<TK, TV, TStrategy> : BaseOrderedMap<TK, TV, TStrategy> where TStrategy : IKeyStrategy<TK>
|
||||||
{
|
{
|
||||||
// This is mutable, but we treat it as readonly for the ID generation logic usually.
|
// This is mutable, but we treat it as readonly for the ID generation logic usually.
|
||||||
private OwnerId _transactionId;
|
private OwnerId _transactionId;
|
||||||
|
|
||||||
public TransientOrderedMap(Node<K> root, TStrategy strategy, int count)
|
public TransientOrderedMap(Node<TK> root, TStrategy strategy, int count)
|
||||||
: base(root, strategy, count)
|
: base(root, strategy, count)
|
||||||
{
|
{
|
||||||
_transactionId = OwnerId.Next();
|
_transactionId = OwnerId.Next();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set(K key, V value)
|
public void Set(TK key, TV value)
|
||||||
{
|
{
|
||||||
_root = BTreeFunctions.Set(_root, key, value, _strategy, _transactionId, out bool countChanged);
|
Root = BTreeFunctions.Set(Root, key, value, Strategy, _transactionId, out bool countChanged);
|
||||||
if (countChanged) Count++;
|
if (countChanged) Count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(K key)
|
public void Remove(TK key)
|
||||||
{
|
{
|
||||||
_root = BTreeFunctions.Remove<K,V, TStrategy>(_root, key, _strategy, _transactionId, out bool removed);
|
Root = BTreeFunctions.Remove<TK,TV, TStrategy>(Root, key, Strategy, _transactionId, out bool removed);
|
||||||
if (removed) Count--;
|
if (removed) Count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersistentOrderedMap<K, V, TStrategy> ToPersistent()
|
public PersistentOrderedMap<TK, TV, TStrategy> ToPersistent()
|
||||||
{
|
{
|
||||||
// 1. Create the snapshot by copying all relevant information
|
// 1. Create the snapshot by copying all relevant information
|
||||||
|
|
||||||
var snapshot = new PersistentOrderedMap<K, V, TStrategy>(_root, _strategy, Count);
|
var snapshot = new PersistentOrderedMap<TK, TV, TStrategy>(Root, Strategy, Count);
|
||||||
|
|
||||||
// 2. Protect the snapshot from THIS TransientOrderedMap by getting a new ownerId
|
// 2. Protect the snapshot from THIS TransientOrderedMap by getting a new ownerId
|
||||||
// so that future edits will be done by CoW
|
// so that future edits will be done by CoW
|
||||||
|
|
|
||||||
|
|
@ -19,25 +19,25 @@ public class BTreeFuzzTests
|
||||||
public void Fuzz_Insert_And_Remove_consistency()
|
public void Fuzz_Insert_And_Remove_consistency()
|
||||||
{
|
{
|
||||||
// CONFIGURATION
|
// CONFIGURATION
|
||||||
const int Iterations = 100_000; // High enough to trigger all splits/merges
|
const int iterations = 100_000; // High enough to trigger all splits/merges
|
||||||
const int KeyRange = 5000; // Small enough to cause frequent collisions
|
const int keyRange = 5000; // Small enough to cause frequent collisions
|
||||||
const bool showOps = false;
|
const bool showOps = false;
|
||||||
int Seed = 2135974; // Environment.TickCount;
|
int seed = 2135974; // Environment.TickCount;
|
||||||
|
|
||||||
// ORACLES
|
// ORACLES
|
||||||
var reference = new SortedDictionary<int, int>();
|
var reference = new SortedDictionary<int, int>();
|
||||||
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
||||||
|
|
||||||
var random = new Random(Seed);
|
var random = new Random(seed);
|
||||||
_output.WriteLine($"Starting Fuzz Test with Seed: {Seed}");
|
_output.WriteLine($"Starting Fuzz Test with Seed: {seed}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Iterations; i++)
|
for (int i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
||||||
bool isInsert = random.NextDouble() < 0.7;
|
bool isInsert = random.NextDouble() < 0.7;
|
||||||
int key = random.Next(KeyRange);
|
int key = random.Next(keyRange);
|
||||||
int val = key * 100;
|
int val = key * 100;
|
||||||
|
|
||||||
if (isInsert)
|
if (isInsert)
|
||||||
|
|
@ -82,7 +82,7 @@ public class BTreeFuzzTests
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_output.WriteLine($"FAILED at iteration with SEED: {Seed}");
|
_output.WriteLine($"FAILED at iteration with SEED: {seed}");
|
||||||
throw; // Re-throw to fail the test
|
throw; // Re-throw to fail the test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,28 +91,28 @@ public class BTreeFuzzTests
|
||||||
public void Fuzz_Range_Queries()
|
public void Fuzz_Range_Queries()
|
||||||
{
|
{
|
||||||
// Validates that your Range Enumerator matches LINQ on the reference
|
// Validates that your Range Enumerator matches LINQ on the reference
|
||||||
const int Iterations = 1000;
|
const int iterations = 1000;
|
||||||
const int KeyRange = 2000;
|
const int keyRange = 2000;
|
||||||
int Seed = Environment.TickCount;
|
int seed = Environment.TickCount;
|
||||||
|
|
||||||
var reference = new SortedDictionary<int, int>();
|
var reference = new SortedDictionary<int, int>();
|
||||||
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
var subject = BaseOrderedMap<int, int, IntStrategy>.CreateTransient(_strategy);
|
||||||
var random = new Random(Seed);
|
var random = new Random(seed);
|
||||||
|
|
||||||
// Fill Data
|
// Fill Data
|
||||||
for(int i=0; i<KeyRange; i++)
|
for(int i=0; i<keyRange; i++)
|
||||||
{
|
{
|
||||||
int k = random.Next(KeyRange);
|
int k = random.Next(keyRange);
|
||||||
reference[k] = k;
|
reference[k] = k;
|
||||||
subject.Set(k, k);
|
subject.Set(k, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
var persistent = subject.ToPersistent();
|
var persistent = subject.ToPersistent();
|
||||||
|
|
||||||
for (int i = 0; i < Iterations; i++)
|
for (int i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
int min = random.Next(KeyRange);
|
int min = random.Next(keyRange);
|
||||||
int max = min + random.Next(KeyRange - min); // Ensure max >= min
|
int max = min + random.Next(keyRange - min); // Ensure max >= min
|
||||||
|
|
||||||
// 1. Reference Result (LINQ)
|
// 1. Reference Result (LINQ)
|
||||||
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
||||||
|
|
|
||||||
|
|
@ -19,25 +19,25 @@ public class BTreeFuzzTestStandardStrategy
|
||||||
public void Fuzz_Insert_And_Remove_consistency()
|
public void Fuzz_Insert_And_Remove_consistency()
|
||||||
{
|
{
|
||||||
// CONFIGURATION
|
// CONFIGURATION
|
||||||
const int Iterations = 100_000; // High enough to trigger all splits/merges
|
const int iterations = 100_000; // High enough to trigger all splits/merges
|
||||||
const int KeyRange = 5000; // Small enough to cause frequent collisions
|
const int keyRange = 5000; // Small enough to cause frequent collisions
|
||||||
const bool showOps = false;
|
const bool showOps = false;
|
||||||
int Seed = 2135974; // Environment.TickCount;
|
int seed = 2135974; // Environment.TickCount;
|
||||||
|
|
||||||
// ORACLES
|
// ORACLES
|
||||||
var reference = new SortedDictionary<int, int>();
|
var reference = new SortedDictionary<int, int>();
|
||||||
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
||||||
|
|
||||||
var random = new Random(Seed);
|
var random = new Random(seed);
|
||||||
_output.WriteLine($"Starting Fuzz Test with Seed: {Seed}");
|
_output.WriteLine($"Starting Fuzz Test with Seed: {seed}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Iterations; i++)
|
for (int i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
// 1. Pick an Action: 70% Insert/Update, 30% Remove
|
||||||
bool isInsert = random.NextDouble() < 0.7;
|
bool isInsert = random.NextDouble() < 0.7;
|
||||||
int key = random.Next(KeyRange);
|
int key = random.Next(keyRange);
|
||||||
int val = key * 100;
|
int val = key * 100;
|
||||||
|
|
||||||
if (isInsert)
|
if (isInsert)
|
||||||
|
|
@ -82,7 +82,7 @@ public class BTreeFuzzTestStandardStrategy
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_output.WriteLine($"FAILED at iteration with SEED: {Seed}");
|
_output.WriteLine($"FAILED at iteration with SEED: {seed}");
|
||||||
throw; // Re-throw to fail the test
|
throw; // Re-throw to fail the test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,28 +91,28 @@ public class BTreeFuzzTestStandardStrategy
|
||||||
public void Fuzz_Range_Queries()
|
public void Fuzz_Range_Queries()
|
||||||
{
|
{
|
||||||
// Validates that your Range Enumerator matches LINQ on the reference
|
// Validates that your Range Enumerator matches LINQ on the reference
|
||||||
const int Iterations = 1000;
|
const int iterations = 1000;
|
||||||
const int KeyRange = 2000;
|
const int keyRange = 2000;
|
||||||
int Seed = Environment.TickCount;
|
int seed = Environment.TickCount;
|
||||||
|
|
||||||
var reference = new SortedDictionary<int, int>();
|
var reference = new SortedDictionary<int, int>();
|
||||||
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
var subject = BaseOrderedMap<int, int, StandardStrategy<int>>.CreateTransient(_strategy);
|
||||||
var random = new Random(Seed);
|
var random = new Random(seed);
|
||||||
|
|
||||||
// Fill Data
|
// Fill Data
|
||||||
for(int i=0; i<KeyRange; i++)
|
for(int i=0; i<keyRange; i++)
|
||||||
{
|
{
|
||||||
int k = random.Next(KeyRange);
|
int k = random.Next(keyRange);
|
||||||
reference[k] = k;
|
reference[k] = k;
|
||||||
subject.Set(k, k);
|
subject.Set(k, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
var persistent = subject.ToPersistent();
|
var persistent = subject.ToPersistent();
|
||||||
|
|
||||||
for (int i = 0; i < Iterations; i++)
|
for (int i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
int min = random.Next(KeyRange);
|
int min = random.Next(keyRange);
|
||||||
int max = min + random.Next(KeyRange - min); // Ensure max >= min
|
int max = min + random.Next(keyRange - min); // Ensure max >= min
|
||||||
|
|
||||||
// 1. Reference Result (LINQ)
|
// 1. Reference Result (LINQ)
|
||||||
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
// Note: SortedDictionary doesn't have a direct Range query, so we filter memory.
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,26 @@ public class StandardStrategy
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
var N = 1000;
|
var n = 1000;
|
||||||
var _stdStrategy = new StandardStrategy<string>();
|
var stdStrategy = new StandardStrategy<string>();
|
||||||
var _uniStrategy = new UnicodeStrategy();
|
var uniStrategy = new UnicodeStrategy();
|
||||||
var rnd = new Random(42);
|
var rnd = new Random(42);
|
||||||
var StringLength = 10;
|
var stringLength = 10;
|
||||||
// Build random strings
|
// Build random strings
|
||||||
var _allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray();
|
var allKeys = Enumerable.Range(0, n).Select(_ => GenerateRandomString(stringLength, rnd)).Distinct().ToArray();
|
||||||
|
|
||||||
// Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe)
|
// Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe)
|
||||||
while (_allKeys.Length < N)
|
while (allKeys.Length < n)
|
||||||
{
|
{
|
||||||
_allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray();
|
allKeys = allKeys.Concat(new[] { GenerateRandomString(stringLength, rnd) }).Distinct().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var transStd = BaseOrderedMap<string, int, StandardStrategy<string>>.CreateTransient(_stdStrategy);
|
var transStd = BaseOrderedMap<string, int, StandardStrategy<string>>.CreateTransient(stdStrategy);
|
||||||
var transUni = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(_uniStrategy);
|
var transUni = BaseOrderedMap<string, int, UnicodeStrategy>.CreateTransient(uniStrategy);
|
||||||
for (int i = 0; i < _allKeys.Length; i++)
|
for (int i = 0; i < allKeys.Length; i++)
|
||||||
{
|
{
|
||||||
transStd.Set(_allKeys[i], i);
|
transStd.Set(allKeys[i], i);
|
||||||
transUni.Set(_allKeys[i], i);
|
transUni.Set(allKeys[i], i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue