using System; using System.Linq; using System.Collections.Generic; using System.Collections; namespace Advanced.Algorithms.DataStructures { /// /// A multi-dimentional range tree implementation. /// public class RangeTree : IEnumerable where T : IComparable { private readonly int dimensions; private OneDimentionalRangeTree tree = new OneDimentionalRangeTree(); private HashSet items = new HashSet(new ArrayComparer()); public int Count { get; private set; } public RangeTree(int dimensions) { if (dimensions <= 0) { throw new Exception("Dimension should be greater than 0."); } this.dimensions = dimensions; } /// /// Time complexity: O(n). /// public void Insert(T[] value) { validateDimensions(value); if (items.Contains(value)) { throw new Exception("value exists."); } var currentTree = tree; //get all overlaps //and insert next dimension value to each overlapping node for (var i = 0; i < dimensions; i++) { currentTree = currentTree.Insert(value[i]).tree; } items.Add(value); Count++; } /// /// Time complexity: O(n). /// public void Delete(T[] value) { validateDimensions(value); if (!items.Contains(value)) { throw new Exception("Item not found."); } var found = false; deleteRecursive(tree, value, 0, ref found); items.Remove(value); Count--; } /// /// Recursively move until last dimension and then delete if found. /// private void deleteRecursive(OneDimentionalRangeTree tree, T[] value, int currentDimension, ref bool found) { var node = tree.Find(value[currentDimension]); if (node != null) { if (currentDimension + 1 == dimensions) { found = true; } else { deleteRecursive(node.tree, value, currentDimension + 1, ref found); } } //delete node if next dimension has no elements //or when this is the last dimension and we found element if (node != null && found && (currentDimension + 1 == dimensions || node.tree.Count == 0 && currentDimension + 1 < dimensions)) { tree.Delete(value[currentDimension]); } } /// /// Get all points within given range. /// Time complexity: O(n). /// public List RangeSearch(T[] start, T[] end) { validateDimensions(start); validateDimensions(end); return rangeSearch(tree, start, end, 0); } /// /// Recursively visit node and return points within given range. /// private List rangeSearch( OneDimentionalRangeTree currentTree, T[] start, T[] end, int dimension) { var nodes = currentTree.RangeSearch(start[dimension], end[dimension]); if (dimension + 1 == dimensions) { var result = new List(); foreach (var value in nodes.SelectMany(x => x.Values)) { var thisDimResult = new T[dimensions]; thisDimResult[dimension] = value; result.Add(thisDimResult); } return result; } else { var result = new List(); foreach (var node in nodes) { var nextDimResult = rangeSearch(node.tree, start, end, dimension + 1); foreach (var value in node.Values) { foreach (var nextResult in nextDimResult) { nextResult[dimension] = value; result.Add(nextResult); } } } return result; } } /// /// Validate dimensions for point length. /// private void validateDimensions(T[] start) { if (start == null) { throw new ArgumentNullException(nameof(start)); } if (start.Length != dimensions) { throw new Exception($"Expecting {dimensions} points."); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return items.GetEnumerator(); } } /// /// One dimensional range tree /// by nesting node with r-b tree for next dimension. /// internal class OneDimentionalRangeTree where T : IComparable { internal RedBlackTree> tree = new RedBlackTree>(); internal int Count => tree.Count; internal RangeTreeNode Find(T value) { var result = tree.FindNode(new RangeTreeNode(value)); if (result == null) { throw new Exception("Item not found in this tree."); } return result.Value; } internal RangeTreeNode Insert(T value) { var newNode = new RangeTreeNode(value); var existing = tree.FindNode(newNode); if (existing != null) { existing.Value.Values.Add(value); return existing.Value; } tree.Insert(newNode); return newNode; } internal void Delete(T value) { var existing = tree.FindNode(new RangeTreeNode(value)); if (existing.Value.Values.Count == 1) { tree.Delete(new RangeTreeNode(value)); return; } //remove last existing.Value.Values.RemoveAt(existing.Value.Values.Count - 1); } internal List> RangeSearch(T start, T end) { return getInRange(new List>(), new Dictionary>, bool>(), tree.Root, start, end); } private List> getInRange(List> result, Dictionary>, bool> visited, RedBlackTreeNode> currentNode, T start, T end) { if (currentNode.IsLeaf) { //start is less than current node if (!inRange(currentNode, start, end)) { return result; } result.Add(currentNode.Value); } //if start is less than current //move left else { if (start.CompareTo(currentNode.Value.Value) <= 0) { if (currentNode.Left != null) { getInRange(result, visited, currentNode.Left, start, end); } //start is less than current node if (!visited.ContainsKey(currentNode) && inRange(currentNode, start, end)) { result.Add(currentNode.Value); visited.Add(currentNode, false); } } //if start is greater than current //and end is greater than current //move right if (end.CompareTo(currentNode.Value.Value) < 0) { return result; } { if (currentNode.Right != null) { getInRange(result, visited, currentNode.Right, start, end); } //start is less than current node if (visited.ContainsKey(currentNode) || !inRange(currentNode, start, end)) { return result; } result.Add(currentNode.Value); visited.Add(currentNode, false); } } return result; } /// /// Checks if current node is in search range. /// private bool inRange(RedBlackTreeNode> currentNode, T start, T end) { //start is less than current and end is greater than current return start.CompareTo(currentNode.Value.Value) <= 0 && end.CompareTo(currentNode.Value.Value) >= 0; } } /// /// Range tree node. /// internal class RangeTreeNode : IComparable where T : IComparable { internal T Value => Values[0]; internal List Values { get; set; } internal OneDimentionalRangeTree tree { get; set; } internal RangeTreeNode(T value) { Values = new List(new T[] { value }); tree = new OneDimentionalRangeTree(); } public int CompareTo(object obj) { return Value.CompareTo(((RangeTreeNode)obj).Value); } } }