diff --git a/Quadratic.Carto.Craft2/Containers/WBTree.cs b/Quadratic.Carto.Craft2/Containers/WBTree.cs new file mode 100644 index 0000000..3a7ea82 --- /dev/null +++ b/Quadratic.Carto.Craft2/Containers/WBTree.cs @@ -0,0 +1,164 @@ +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace Quadratic.Carto.Craft2.Containers; + +/// +/// Weight-balanced tree. Immutable. +/// +/// Also see: Wikipedia, +/// the original paper, +/// and the paper by Hirai about selecting the right balance factor. +/// +public struct WBTree where T : IComparable +{ + Node? root; + + class Node(T value, Node? left = null, Node? right = null) + { + public int Size { get; } = NSize(left) + NSize(right) + 1; + public T Value { get; set; } = value; + public Node? Left { get; } = left; + public Node? Right { get; } = right; + + public void Deconstruct(out T value, out Node? left, out Node? right) + { + value = this.Value; + left = this.Left; + right = this.Right; + } + + public bool Balanced() + { + return IsBalanced(Left, Right) && IsBalanced(Right, Left) && + (Left?.Balanced() ?? true) && (Right?.Balanced() ?? true); + } + + private static int NSize(Node? n) => n?.Size ?? 0; + + private static bool IsBalanced(Node? l, Node? r) => + DELTA * (NSize(l) + 1) >= (NSize(r) + 1); + + private static bool IsSingle(Node? l, Node? r) => + (NSize(l) + 1) < GAMMA * (NSize(r) + 1); + + private const int DELTA = 2; + private const int GAMMA = 3; + + public static Node Insert(Node? node, T value) + { + if (node == null) return new Node(value); + var cmp = value.CompareTo(node.Value); + if (cmp < 0) + return BalanceR(node.Value, Insert(node.Left, value), node.Right); + else if (cmp > 0) + return BalanceL(node.Value, node.Left, Insert(node.Right, value)); + else + return new Node(value, node.Left, node.Right); + } + + private static Node BalanceL(T v, Node? l, Node r) + { + if (IsBalanced(l, r)) return new Node(v, l, r); + // rotateL + else if (IsSingle(l, r)) + { + var (k2, t2, t3) = r; + return new Node(k2, new Node(v, l, t2), t3); + } + else + { + var (k2, rl, t4) = r; + if (rl is null) + throw new ArgumentException("Unable to perform double rotation; r.Left is null"); + var (k3, t2, t3) = rl; + return new Node(k3, new Node(v, l, t2), new Node(k2, t3, t4)); + } + } + + private static Node BalanceR(T cVal, Node l, Node? r) + { + if (IsBalanced(r, l)) return new Node(cVal, l, r); + // rotateR + else if (IsSingle(r, l)) + { + var (k2, t2, t3) = l; + return new Node(k2, t2, new Node(cVal, t3, r)); + } + else + { + var (k2, t2, lr) = l; + if (lr is null) + throw new ArgumentException("Unable to perform double rotation; l.Right is null"); + var (k3, t3, t4) = lr; + return new Node(k3, new Node(k2, t2, t3), new Node(cVal, t4, r)); + } + } + } + + private WBTree(Node? node) + { + this.root = node; + } + + public static WBTree Empty => new WBTree(null); + + public static WBTree Singleton(T value) => new WBTree(new Node(value)); + + public readonly int Count => root is null ? 0 : root.Size; + + public WBTree Add(T value) => new WBTree(Node.Insert(root, value)); + + public bool TryGet(T value, [NotNullWhen(true)] out T? result) + { + var node = root; + while (node != null) + { + var cmp = value.CompareTo(node.Value); + if (cmp < 0) + node = node.Left; + else if (cmp > 0) + node = node.Right; + else + { + result = node.Value; + return true; + } + } + result = default; + return false; + } + + public bool Contains(T value) => TryGet(value, out _); + + public T Get(T value) => TryGet(value, out var result) ? result : throw new KeyNotFoundException(); +} + +/// +/// A mutable reference to a weight-balanced tree. Might be preferable to use in some cases. +/// +/// +public class WBTreeRef where T : IComparable +{ + WBTree impl; + + public WBTreeRef() => impl = WBTree.Empty; + + public WBTreeRef(T value) => impl = WBTree.Singleton(value); + + public int Count => impl.Count; + + public void Add(T value) => impl = impl.Add(value); + + public bool TryGet(T value, [NotNullWhen(true)] out T? result) => impl.TryGet(value, out result); + + public bool Contains(T value) => impl.Contains(value); + + public T Get(T value) => impl.Get(value); + + public WBTree ToImmutable() => impl; + + public static implicit operator WBTreeRef(WBTree tree) => new() { impl = tree }; + + public static implicit operator WBTree(WBTreeRef tree) => tree.impl; +}