public RangedArray(int numberOfItems, TheFanciestMemory mem) { if (mem == null) throw new ArgumentNullException("mem"); _mem = mem; if (numberOfItems == 0) { _refNode = null; return; } _memInterval = mem.Malloc(numberOfItems); var root = NestingDepthTreeNode.Include(null, _memInterval.Offset, +1, +1).NewRoot; _refNode = NestingDepthTreeNode.Include(root, _memInterval.Offset + _memInterval.Length, -1, +1).AdjustedNode; }
public RangedArray(RangedArray other, Interval interval, TheFanciestMemory mem) { if (mem == null) throw new ArgumentNullException("mem"); _mem = mem; if (other._disposed) throw new ObjectDisposedException("other"); if (interval.Offset < 0) throw new ArgumentOutOfRangeException(); if (interval.Length < 0) throw new ArgumentOutOfRangeException(); if (interval.Offset + interval.Length > other._memInterval.Length) throw new ArgumentOutOfRangeException(); if (interval.Length == 0) { _refNode = null; return; } _memInterval = new Interval(other._memInterval.Offset + interval.Offset, interval.Length); var root = NestingDepthTreeNode.RootOf(other._refNode); root = NestingDepthTreeNode.Include(root, _memInterval.Offset, +1, +1).NewRoot; _refNode = NestingDepthTreeNode.Include(root, _memInterval.Offset + _memInterval.Length, -1, +1).AdjustedNode; }
///<summary>Returns the areas that the tree has covered; that aren't holes in the nesting depth.</summary> public static IReadOnlyList<Interval> FindCoveredIntervals(NestingDepthTreeNode root) { var coveredStart = (int?)null; var results = new List<Interval>(); FindAndCallbackHoleTransitionsInOrder( root, 0, (node, isIntoHole) => { if (coveredStart.HasValue != isIntoHole) { throw new InvalidOperationException("Invariant violated: in-out-repeat"); } if (coveredStart.HasValue) { results.Add(new Interval(coveredStart.Value, node._offset - coveredStart.Value)); coveredStart = null; } else { coveredStart = node._offset; } }); return results; }
/// <summary> /// Numbers with more power-of-2-ness are superior. /// Also, power-of-2-ness makes a nice balanced binary structure: 1317131F1317131... /// This is not optimal, because nodes may be sparse w.r.t. the range, but it's good enough for an experiment. /// </summary> private bool ShouldBeParentOf(NestingDepthTreeNode other) { if (other == null) return false; return PowerOf2Ness(other._offset) < PowerOf2Ness(this._offset); }
///<summary>Sets the child in the given direction (negative -> lesser child, positive -> larger child).</summary> private void SetChild(int childSign, NestingDepthTreeNode value) { if (childSign < 0) _less = value; else if (childSign > 0) _more = value; else throw new ArgumentException("childSign == 0"); }
/// <summary>Continues cutting the tree in two, assuming that child nodes have been handled.</summary> private static void PartitionUpwardToParentSideOf(NestingDepthTreeNode n, int d, NestingDepthTreeNode orphan) { if (n == null) return; n.RecomputeAggregates(); var p = n._parent; if (n.DirToParent() == d) { // going from n to p crosses the cut line, so we must disconnect them n._parent = null; // the orphan node takes n's place p.SetChild(-d, orphan); if (orphan != null) orphan._parent = p; // switch directions back towards the cut, and onward with our new orphan n! PartitionUpwardToParentSideOf(p, -d, n); } else { // didn't pass over the cut line, keep going upwards PartitionUpwardToParentSideOf(p, d, orphan); } }
/// <summary>Cuts the tree in two, to the left of the given node if d is negative and to the right if d is positive.</summary> private static void PartitionToSideOfNode(NestingDepthTreeNode n, int d) { var orphan = n.Child(d); if (orphan != null) orphan._parent = null; n.SetChild(d, null); PartitionUpwardToParentSideOf(n, d, orphan); }
/// <summary>Scans the tree for transitions into and out of holes, running a callback for each one.</summary> private static void FindAndCallbackHoleTransitionsInOrder(NestingDepthTreeNode node, int initialNestingDepth, Action<NestingDepthTreeNode, bool> callback) { if (node == null) return; // we can skip this whole subtree if it's guaranteed to stay above water the whole time var lowestNestingDepth = node._subTreeRelativeMinimum + initialNestingDepth; if (initialNestingDepth > 0 && lowestNestingDepth > 0) return; // scan left subtree FindAndCallbackHoleTransitionsInOrder(node._less, initialNestingDepth, callback); // check for transition here var nestingDepthJustBeforeNode = initialNestingDepth + GetTotalAdjust(node._less); var nestingDepthJustAfterNode = nestingDepthJustBeforeNode + node._adjust; var wasInHole = nestingDepthJustBeforeNode <= 0; var nowInHole = nestingDepthJustAfterNode <= 0; if (wasInHole != nowInHole) { callback(node, nowInHole); } // scan right subtree FindAndCallbackHoleTransitionsInOrder(node._more, nestingDepthJustAfterNode, callback); }
///<summary>Returns the nesting depth at a particular index, as determined by the tree rooted at the given node.</summary> public static int QueryNestingDepthAt(NestingDepthTreeNode root, int index) { if (root == null) return 0; var pre = QueryNestingDepthAt(root._less, index); if (root._offset > index) return pre; var on = pre + root._adjust; if (root._offset == index) return on; var post = on + QueryNestingDepthAt(root._more, index); return post; }
/// <summary> /// When the nesting depth hits zero, we can actually split the tree in two around that hole and make future queries more efficient. /// This only works because arrays only care about the segment they're in, as opposed to the global state. /// </summary> public static void PartitionAroundHoles(NestingDepthTreeNode root) { var partitionsToPerform = new List<Action>(); // find the nodes that have a hole to their left or right, so we can cut there FindAndCallbackHoleTransitionsInOrder( root, 0, (node, isIntoHole) => partitionsToPerform.Add(() => PartitionToSideOfNode(node, isIntoHole ? +1 : -1))); // only do the partitioning after the search is done, so we don't interfere with it foreach (var e in partitionsToPerform) { e.Invoke(); } }
public static IncludeResult IncludeHelper(NestingDepthTreeNode root, int index, int adjust, int refAdjust) { // Do we need to create a new node? if (root == null) { return new IncludeResult(new NestingDepthTreeNode(index, adjust, refAdjust)); } // It is understood that the caller will fixup our parent afterwards; we must act independently of the tree above us root._parent = null; // Is this node the one we need to adjust? if (index == root._offset) { root._adjust += adjust; root._refCount += refAdjust; root.RecomputeAggregates(); // nodes can be removed when they are not referenced and have no effect on the totals if (root._adjust == 0 && root._refCount == 0) { return new IncludeResult(null, root.Implode()); } return new IncludeResult(root); } // Pick the subtree the node has to end up and recurse the inclusion that-a-way var d = index.CompareTo(root._offset); var subtree = root.Child(d); var preTotal = GetTotalAdjust(subtree); var subResult = IncludeHelper(subtree, index, adjust, refAdjust); var postTotal = GetTotalAdjust(subResult.NewRoot); if (preTotal + adjust != postTotal) { throw new InvalidOperationException("Invariant violated: total adjustment did not shift by the included adjustment."); } // Great! Now we just need to fixup so the new subtree is linked in var c = subResult.NewRoot; root.SetChild(d, c); if (c != null) c._parent = root; root.RecomputeAggregates(); // Oh, and do a token effort to keep things balanced using our hacky hierarchical ordering over the indexes // Can we get away with not rotating the new child above ourselves to keep things sorta balanced? if (c == null || !c.ShouldBeParentOf(root)) { return new IncludeResult(subResult.AdjustedNode, root); } // darn, need to rotate var s = c.Child(-d); c.SetChild(-d, root); root.SetChild(d, s); // fixup c._parent = null; root._parent = c; if (s != null) s._parent = root; root.RecomputeAggregates(); c.RecomputeAggregates(); // finally return new IncludeResult(subResult.AdjustedNode, c); }
/// <summary> /// Adds an adjustment to the tree rooted at the given node. /// The nesting depth will be perceived as the given adjust higher after the given index. /// Can also adjust reference counts. /// This can create, modify, or remove a node in the tree. /// </summary> public static IncludeResult Include(NestingDepthTreeNode root, int index, int adjust, int refAdjust) { if (root != null && root._parent != null) throw new ArgumentException("root.Parent != null"); var preTotal = GetTotalAdjust(root); var result = IncludeHelper(root, index, adjust, refAdjust); var postTotal = GetTotalAdjust(result.NewRoot); if (preTotal + adjust != postTotal) { throw new InvalidOperationException("Invariant violated: total adjustment did not shift by the included adjustment."); } return result; }
///<summary>Determines how much the nesting depth changes as you cross the tree rooted at the given node.</summary> public static int GetTotalAdjust(NestingDepthTreeNode root) { return root == null ? 0 : root._subTreeTotalAdjust; }
/// <summary>Determines the range of indexes spanned by the sub tree rooted at the given node.</summary> public static Interval GetInterval(NestingDepthTreeNode root) { if (root == null) return default(Interval); var min = root; var max = root; while (min._less != null) min = min._less; while (max._more != null) max = max._more; return new Interval(min._offset, max._offset - min._offset); }
public IncludeResult(NestingDepthTreeNode newRootAndAdjustedNode) { AdjustedNode = NewRoot = newRootAndAdjustedNode; }
///<summary>Travels upwards from the given node until the root of the tree containing it is found.</summary> public static NestingDepthTreeNode RootOf(NestingDepthTreeNode node) { if (node == null) return null; if (node._parent == null) return node; return RootOf(node._parent); }
public IncludeResult(NestingDepthTreeNode adjustedNode, NestingDepthTreeNode newRoot) { AdjustedNode = adjustedNode; NewRoot = newRoot; }
public List<Tuple<NestingDepthTreeNode, int>> GetCuts(NestingDepthTreeNode root) { var partitionsToPerform = new List<Tuple<NestingDepthTreeNode, int>>(); // find the nodes that have a hole to their left or right, so we can cut there FindAndCallbackHoleTransitionsInOrder( root, 0, (node, isIntoHole) => partitionsToPerform.Add(Tuple.Create(node, isIntoHole ? +1 : -1))); return partitionsToPerform; }
///<summary>Returns all of the areas in the given interval where the tree would return zero if you queried the nesting depth there.</summary> public static IReadOnlyList<Interval> FindHolesIn(Interval interval, NestingDepthTreeNode root) { var relevantSegments = FindCoveredIntervals(root) .SkipWhile(e => !e.Overlaps(interval)) .TakeWhile(e => e.Overlaps(interval)) .ToArray(); var holeStarts = new[] {interval.Offset} .Concat(relevantSegments .Select(e => e.Offset + e.Length)); var holeEnds = relevantSegments .Select(e => e.Offset) .Concat(new[] {interval.Offset + interval.Length}); return holeStarts .Zip(holeEnds, (start, end) => new Interval(start, end - start)) .Where(e => e.Length > 0) .ToArray(); }
private static Animation RenderTree(Interval renderInterval, NestingDepthTreeNode root, HashSet<int> dealloced) { var v = renderInterval; var ani = new Animation(); var r = 10; var query = new Dictionary<int, int>(); var x = 100; var y = 350; for (var i = 0; i < v.Length; i++) { var h = NestingDepthTreeNode.QueryNestingDepthAt(root, i+v.Offset); query[i+v.Offset] = h; if (h== -1) throw new Exception(); ani.Add(new RectDesc(new Rect(x + i * r, y - h * r - 1, r, h * r + 1), fill: Brushes.Black, stroke: Brushes.Black, strokeThickness: 0)); if (dealloced.Contains(i+v.Offset)) { ani.Add(new RectDesc(new Rect(x + i * r, y - 0.5 * r - 1, r, 1 * r + 1), fill: Brushes.Red, stroke: Brushes.Orange, strokeThickness: 1)); } } var y2 = 125; Action<NestingDepthTreeNode, int> paintNode = null; paintNode = (n, l) => { if (n == null) return; var p = new Point(x + (n._offset-v.Offset)*r, y2 + l*r*5); if (n._parent != null) { var q = new Point(x + (n._parent._offset - v.Offset) * r, y2 + (l - 1) * r * 5); var b1 = false; var b2 = false; for (var i = Math.Min(n._offset, n._parent._offset); i < Math.Max(n._offset, n._parent._offset); i++) { b1 |= query[i] != 0; b2 |= query[i] == 0; } ani.Add(new LineSegmentDesc(new LineSegment(p, q), b1 && b2 ? Brushes.Red : b2 ? Brushes.Red : Brushes.Black, 1)); } if (n._less != null) paintNode(n._less, l + 1); if (n._more != null) paintNode(n._more, l + 1); ani.Add(new RectDesc(new Rect(new Point(p.X - 18, p.Y - 22), new Size(36, 44)), n._fakeRefCount > 1 ? Brushes.Black : Brushes.Red, n._fakeRefCount > 0 ? Brushes.Gray : Brushes.Red, 1)); var s = "d=" + n._adjust +Environment.NewLine + "t=" + n._subTreeTotalAdjust + Environment.NewLine + "m=" + n._subTreeRelativeMinimum; ani.Add(new TextDesc(s, new Point(p.X - 16, p.Y - 20), new Point(0, 0))); //var s = (n._adjust > 0 ? "+" : "") + n._adjust; //ani.Add(new PointDesc(p, n._fakeRefCount > 1 ? Brushes.Black : Brushes.Red, n._fakeRefCount > 0 ? Brushes.Gray : Brushes.Red, 15, 1)); //ani.Add(new TextDesc(s, new Point(p.X - 8, p.Y - 8), new Point(0, 0))); }; paintNode(root, 0); return ani; }