1
0
mirror of https://github.com/chylex/Brotli-Builder.git synced 2024-12-22 16:42:46 +01:00
Brotli-Builder/BrotliLib/Collections/PatriciaTree.cs

171 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
namespace BrotliLib.Collections{
/// <summary>
/// Compact tree structure that maps <c>byte[]</c> keys to one or more values of type <typeparamref name="V"/>.
/// Each node splits the path at a specific bit into 2 branches. The search ends when a branch leads back into its owner or one of the parents.
/// </summary>
public sealed class PatriciaTree<V>{
/// <summary>
/// Returns the value of the bit at position <paramref name="bit"/> in the provided <paramref name="key"/>.
/// </summary>
private static bool Bit(in ArraySegment<byte> key, short bit){
int index = bit / 8;
return index < key.Count && (key[index] & (1 << (bit & 0b111))) != 0;
}
private readonly Node root = new Node();
public void Insert(byte[] key, V value){
// the tree is traversed in an attempt to find an existing node
var parentBit = root.bit;
var found = root.left;
while(found.bit > parentBit){
parentBit = found.bit;
found = Bit(key, found.bit) ? found.right : found.left;
}
// if the key matches, the array of values is updated (and it turns out to be a lot faster and more memory-efficient than a List)
var foundKey = found.key;
if (CollectionHelper.Equal(key, foundKey)){
Array.Resize(ref found.values, found.values!.Length + 1);
found.values[^1] = value;
return;
}
// if the key does not match, the common length (in bits) is found with the found key
int maxLength = 8 * Math.Max(key.Length, foundKey.Length);
short commonLength = 0;
while(commonLength < maxLength && Bit(key, commonLength) == Bit(foundKey, commonLength)){
++commonLength;
}
// the tree is traversed again to find the node to split
bool lastPath = false;
var parent = root;
var current = parent.left;
while(true){
var bit = current.bit;
if (bit >= commonLength || bit <= parent.bit){
break;
}
lastPath = Bit(key, bit);
parent = current;
current = lastPath ? current.right : current.left;
}
// new node containing the inserted key and value, and its paths are setup for insertion above the split node
var newNode = new Node(commonLength, key, value);
if (Bit(key, commonLength)){
newNode.left = current;
newNode.right = newNode;
}
else{
newNode.left = newNode;
newNode.right = current;
}
// the new node is inserted above the split node
if (lastPath){
parent.right = newNode;
}
else{
parent.left = newNode;
}
}
public IReadOnlyList<V> FindAll(in ArraySegment<byte> key, int minLength){
var results = new List<V>();
var parentBit = root.bit;
var node = root.left;
while(node.bit > parentBit){
if (node.CheckMatch(key) && node.key.Length >= minLength){
results.AddRange(node.values);
}
node = Bit(key, parentBit = node.bit) ? node.right : node.left;
}
return results;
}
public IReadOnlyList<V> FindLongest(in ArraySegment<byte> key){
var parentBit = root.bit;
var node = root.left;
Node best = root; // root has an empty key & null value array
while(node.bit > parentBit){
if (node.key.Length > best.key.Length && node.CheckMatch(key)){
best = node;
}
node = Bit(key, parentBit = node.bit) ? node.right : node.left;
}
return best.values ?? Array.Empty<V>();
}
private sealed class Node{
public readonly short bit;
public Node left;
public Node right;
public readonly byte[] key;
public V[]? values;
/// <summary>
/// Constructs the root node of a tree.
/// </summary>
public Node(){
this.bit = -1;
this.left = this;
this.right = this;
this.key = Array.Empty<byte>();
this.values = null;
}
/// <summary>
/// Constructs a node with the specified bit position, key, and the first value.
/// </summary>
public Node(short bit, byte[] key, V value){
this.bit = bit;
this.left = this;
this.right = this;
this.key = CollectionHelper.Clone(key);
this.values = new V[]{ value };
}
/// <summary>
/// Returns true if the <see cref="key"/> in this node is a prefix to the provided <paramref name="segment"/>.
/// </summary>
public bool CheckMatch(in ArraySegment<byte> segment){
if (segment.Count < key.Length){
return false;
}
for(int index = 0; index < key.Length; index++){
if (segment[index] != key[index]){
return false;
}
}
return true;
}
}
}
}