From 6f5635164d5af4bf37074e6036414007b10b22c9 Mon Sep 17 00:00:00 2001 From: Jack Dermody Date: Fri, 2 Aug 2024 10:02:27 +1000 Subject: [PATCH] added indexed sorted array --- BrightData/BrightData.xml | 14 +++-- BrightData/Interfaces.cs | 11 ++-- .../Types/Graph/FixedSizeWeightedGraph.cs | 60 ++++++++++++------- .../Types/Graph/FixedSizeWeightedGraphNode.cs | 12 ++-- .../HierarchicalNavigationSmallWorldGraph.cs | 33 +++++----- BrightData/Types/Helper/SortedArrayHelper.cs | 39 ++++++++++++ BrightData/Types/IndexedSortedArray.cs | 47 +++++++++++++++ BrightData/Types/SortedArray.cs | 19 +++--- 8 files changed, 175 insertions(+), 60 deletions(-) create mode 100644 BrightData/Types/IndexedSortedArray.cs diff --git a/BrightData/BrightData.xml b/BrightData/BrightData.xml index 18c57f58..f6d066ee 100644 --- a/BrightData/BrightData.xml +++ b/BrightData/BrightData.xml @@ -17767,16 +17767,22 @@ - + - + - + - + + + + + + + diff --git a/BrightData/Interfaces.cs b/BrightData/Interfaces.cs index d42f627e..5cd82836 100644 --- a/BrightData/Interfaces.cs +++ b/BrightData/Interfaces.cs @@ -667,15 +667,18 @@ public interface IWeightedGraph : IHaveSize where T: IHaveSingleIndex where W : unmanaged, INumber, IMinMaxValue { - IWeightedGraphNode Create(T value, bool addToGraph = true); + void Add(T value); + void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours); - void Add(IWeightedGraphNode node); - - IWeightedGraphNode Get(uint index); + T Get(uint index); RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) where RAT : struct, IFixedSizeSortedArray where CAT : struct, IFixedSizeSortedArray ; + + ReadOnlySpan GetNeighbours(uint nodeIndex); + + bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight); } } diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs index ef2614fa..619e0221 100644 --- a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs +++ b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -14,40 +15,59 @@ namespace BrightData.Types.Graph /// /// public class FixedSizeWeightedGraph : IWeightedGraph - where T : IHaveSingleIndex + where T : unmanaged, IHaveSingleIndex where W : unmanaged, INumber, IMinMaxValue - where AT : struct, IFixedSizeSortedArray + where AT : unmanaged, IFixedSizeSortedArray { - readonly SortedArray, uint> _nodes = new(); + readonly IndexedSortedArray> _nodes = new(); /// - public IWeightedGraphNode Create(T value, bool addToGraph = true) + public void Add(T value) { - var ret = new FixedSizeWeightedGraphNode(value); - if(addToGraph) - _nodes.Add(value.Index, ret); - return ret; + _nodes.Add(new FixedSizeWeightedGraphNode(value)); } /// - public void Add(IWeightedGraphNode node) + public void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours) { - if(node is FixedSizeWeightedGraphNode same) - _nodes.Add(same.Index, same); - else { - var ret = new FixedSizeWeightedGraphNode(node.Value); - foreach (var (neighbour, weight) in node.WeightedNeighbours) - ret.AddNeighbour(neighbour, weight); - _nodes.Add(ret.Index, ret); - } + var node = new FixedSizeWeightedGraphNode(value); + foreach (var (index, weight) in neighbours) + node.AddNeighbour(index, weight); + _nodes.Add(node); } /// - public uint Size => _nodes.Size; + public T Get(uint nodeIndex) + { + ref var node = ref _nodes.Get(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) + return node.Value; + throw new ArgumentException($"Node with index {nodeIndex} was not found"); + } + + /// + public ReadOnlySpan GetNeighbours(uint nodeIndex) + { + ref var node = ref _nodes.Get(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) + return node.NeighbourSpan; + return ReadOnlySpan.Empty; + } + + /// + public bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight) + { + ref var node = ref _nodes.Get(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) { + return node.AddNeighbour(neighbourIndex, weight); + } + + return false; + } /// - public IWeightedGraphNode Get(uint index) => _nodes.TryGet(index, out var ret) ? ret : throw new ArgumentException("Index not found"); + public uint Size => _nodes.Size; /// public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) @@ -67,7 +87,7 @@ public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights di if (distanceCalculator.GetWeight(c, q) > distanceCalculator.GetWeight(f, q)) break; - foreach (var neighbour in Get(c).NeighbourSpan) { + foreach (var neighbour in GetNeighbours(c)) { if(!visited.Add(neighbour)) continue; diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs index 85cbd546..c03b561e 100644 --- a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs +++ b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace BrightData.Types.Graph { /// /// A graph node with a (fixed size) maximum number of neighbours /// - public record FixedSizeWeightedGraphNode(T Value) : IWeightedGraphNode, IComparable> - where T : IHaveSingleIndex + public record struct FixedSizeWeightedGraphNode(T Value) : IWeightedGraphNode, IComparable>, IHaveSingleIndex + where T : unmanaged, IHaveSingleIndex where W : unmanaged, INumber, IMinMaxValue - where AT : struct, IFixedSizeSortedArray + where AT : unmanaged, IFixedSizeSortedArray { AT _neighbours = new(); @@ -54,7 +51,8 @@ public IEnumerable Neighbours } /// - public int CompareTo(FixedSizeWeightedGraphNode? other) => Value.Index.CompareTo(other?.Value.Index); + //public int CompareTo(FixedSizeWeightedGraphNode? other) => Value.Index.CompareTo(other?.Value.Index); + public int CompareTo(FixedSizeWeightedGraphNode other) => Value.Index.CompareTo(other.Value.Index); /// public override string ToString() => $"{Value}: {_neighbours}"; diff --git a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs index 4882fb85..425763d6 100644 --- a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs +++ b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs @@ -9,12 +9,12 @@ namespace BrightData.Types.Graph { public class HierarchicalNavigationSmallWorldGraph(BrightDataContext context, int maxLayers) - where T : IHaveSingleIndex + where T : unmanaged, IHaveSingleIndex where W : unmanaged, INumber, IMinMaxValue, IBinaryFloatingPointIeee754 - where AT : struct, IFixedSizeSortedArray - where BLAT : struct, IFixedSizeSortedArray + where AT : unmanaged, IFixedSizeSortedArray + where BLAT : unmanaged, IFixedSizeSortedArray { - public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex + public record struct NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex { public uint Index => Value.Index; } @@ -23,10 +23,12 @@ public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex .Select(i => (IWeightedGraph)(i == 0 ? new FixedSizeWeightedGraph() : new FixedSizeWeightedGraph())) .ToArray() ; - IWeightedGraphNode? _entryPoint = null; + NodeIndex? _entryPoint = null; public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculator) { + Span<(uint, W)> newNodeNeighbours = stackalloc (uint, W)[32]; + foreach (var value in values) { var entryPoint = _entryPoint; var level = GetRandomLevel(); @@ -37,26 +39,26 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat entryPointLevel = entryPoint.Value.LayerIndex; for (var i = entryPointLevel.Value; i > level; i--) { var layer = _layers[i]; - var w = layer.Search, AT>(value.Index, entryPoint.Index, distanceCalculator); + var w = layer.Search, AT>(value.Index, entryPoint.Value.Index, distanceCalculator); entryPoint = layer.Get(w.MinValue); } } // add to levels - var from = Math.Min(level, entryPoint?.Value.LayerIndex ?? int.MaxValue); + var from = Math.Min(level, entryPoint?.LayerIndex ?? int.MaxValue); for(var i = level; i > from; i--) - _layers[i].Create(new NodeIndex(value, (uint)i)); + _layers[i].Add(new NodeIndex(value, (uint)i)); for (var i = from; i >= 0; i--) { var layer = _layers[i]; - var newNode = layer.Create(new NodeIndex(value, (uint)i), false); + var newNodeIndex = 0; if (entryPoint is not null) { - var w = layer.Search(value.Index, entryPoint.Index, distanceCalculator); + var w = layer.Search(value.Index, entryPoint.Value.Index, distanceCalculator); foreach (var (ni, nw) in w.Elements) { - layer.Get(ni).AddNeighbour(value.Index, nw); - newNode.AddNeighbour(ni, nw); + layer.AddNeighbour(ni, value.Index, nw); + newNodeNeighbours[newNodeIndex++] = (ni, nw); } } - layer.Add(newNode); + layer.Add(new NodeIndex(value, (uint)i), newNodeNeighbours[..newNodeIndex]); } if(!entryPointLevel.HasValue || level > entryPointLevel.Value) @@ -67,7 +69,7 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat public AT KnnSearch(uint q, ICalculateNodeWeights distanceCalculator) { var entryPoint = _entryPoint ?? throw new Exception("No nodes in graph"); - for (var i = (int)entryPoint.Value.LayerIndex; i > 0; i--) { + for (var i = (int)entryPoint.LayerIndex; i > 0; i--) { var layer = _layers[i]; var w = layer.Search(q, entryPoint.Index, distanceCalculator); entryPoint = layer.Get(w.MinValue); @@ -83,8 +85,7 @@ public IEnumerable BreadthFirstSearch(uint index) queue.Enqueue(index); while (queue.Count > 0) { - var node = layer.Get(queue.Dequeue()); - foreach (var neighbour in node.Neighbours) { + foreach (var neighbour in layer.GetNeighbours(queue.Dequeue()).ToArray()) { if(!visited.Add(neighbour)) continue; yield return neighbour; diff --git a/BrightData/Types/Helper/SortedArrayHelper.cs b/BrightData/Types/Helper/SortedArrayHelper.cs index c1dab234..10348215 100644 --- a/BrightData/Types/Helper/SortedArrayHelper.cs +++ b/BrightData/Types/Helper/SortedArrayHelper.cs @@ -8,6 +8,45 @@ namespace BrightData.Types.Helper /// public class SortedArrayHelper { + internal static bool InsertIndexed(uint currSize, V value, Span values) + where V : unmanaged, IHaveSingleIndex + { + var size = (int)currSize; + var index = value.Index; + + // use binary search to find the insertion position + int left = 0, + right = size - 1, + insertPosition = size + ; + while (left <= right) + { + var mid = left + (right - left) / 2; + if (values[mid].Index > index) + { + insertPosition = mid; + right = mid - 1; + } + else + { + left = mid + 1; + } + } + + if (insertPosition != size) + { + // shuffle to make room + for (var i = size - 1; i >= insertPosition; i--) + { + values[i + 1] = values[i]; + } + } + + // insert the item + values[insertPosition] = value; + return true; + } + internal static bool InsertIntoAscending(bool enforceUnique, uint currSize, uint maxSize, V value, W weight, Span values, Span weights) where V : IComparable where W : unmanaged, INumber, IMinMaxValue diff --git a/BrightData/Types/IndexedSortedArray.cs b/BrightData/Types/IndexedSortedArray.cs new file mode 100644 index 00000000..e65ae2ba --- /dev/null +++ b/BrightData/Types/IndexedSortedArray.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BrightData.Types.Helper; + +namespace BrightData.Types +{ + public class IndexedSortedArray(int? capacity = null) : IHaveSize + where T: unmanaged, IHaveSingleIndex + { + class Comparer(uint index) : IComparable + { + public int CompareTo(T other) => index.CompareTo(other.Index); + } + readonly List _values = capacity.HasValue ? new(capacity.Value) : new(); + + public Span Values => CollectionsMarshal.AsSpan(_values); + public uint Size => (uint)Values.Length; + + public bool Add(T value) + { + _values.Add(value); + return SortedArrayHelper.InsertIndexed(Size - 1, value, Values); + } + + public ref T Get(uint itemIndex) + { + var arrayIndex = Values.BinarySearch(new Comparer(itemIndex)); + if (arrayIndex >= 0) + return ref Values[arrayIndex]; + return ref Unsafe.NullRef(); + } + + public bool TryGet(uint itemIndex, [NotNullWhen(true)]out T? value) + { + var arrayIndex = Values.BinarySearch(new Comparer(itemIndex)); + if (arrayIndex >= 0) { + value = _values[arrayIndex]; + return true; + } + value = default; + return false; + } + } +} diff --git a/BrightData/Types/SortedArray.cs b/BrightData/Types/SortedArray.cs index 60b3ddd9..3f6bf71f 100644 --- a/BrightData/Types/SortedArray.cs +++ b/BrightData/Types/SortedArray.cs @@ -2,13 +2,15 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BrightData.Types.Helper; namespace BrightData.Types { - public class SortedArray(int? capacity = null, bool isAscending = true) : IHaveSize - where V : IComparable + public class SortedArray(int? capacity = null) : + IHaveSize + where V : unmanaged, IComparable where W : unmanaged, INumber, IMinMaxValue { readonly List _values = capacity.HasValue ? new(capacity.Value) : new(); @@ -22,16 +24,15 @@ public bool Add(W weight, in V item) { _values.Add(item); _weights.Add(weight); - return isAscending - ? SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights) - : SortedArrayHelper.InsertIntoDescending(false, Size-1, uint.MaxValue, item, weight, Values, Weights) - ; + return SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights); } - public void RemoveAt(uint index) + public ref V Get(W weight) { - _values.RemoveAt((int)index); - _weights.RemoveAt((int)index); + var index = Weights.BinarySearch(weight); + if (index >= 0) + return ref Values[index]; + return ref Unsafe.NullRef(); } public bool TryGet(W weight, [NotNullWhen(true)]out V? value)