public String toGraphviz() { StringBuilder builder = new StringBuilder(); builder.append("digraph QuantileDigest {\n").append("\tgraph [ordering=\"out\"];"); final List<Node> nodes = new ArrayList<>(); postOrderTraversal( root, new Callback() { @Override public boolean process(Node node) { nodes.add(node); return true; } }); Multimap<Integer, Node> nodesByLevel = Multimaps.index( nodes, new Function<Node, Integer>() { @Override public Integer apply(Node input) { return input.level; } }); for (Map.Entry<Integer, Collection<Node>> entry : nodesByLevel.asMap().entrySet()) { builder.append("\tsubgraph level_" + entry.getKey() + " {\n").append("\t\trank = same;\n"); for (Node node : entry.getValue()) { builder.append( String.format( "\t\t%s [label=\"[%s..%s]@%s\\n%s\", shape=rect, style=filled,color=%s];\n", idFor(node), node.getLowerBound(), node.getUpperBound(), node.level, node.weightedCount, node.weightedCount > 0 ? "salmon2" : "white")); } builder.append("\t}\n"); } for (Node node : nodes) { if (node.left != null) { builder.append(format("\t%s -> %s;\n", idFor(node), idFor(node.left))); } if (node.right != null) { builder.append(format("\t%s -> %s;\n", idFor(node), idFor(node.right))); } } builder.append("}\n"); return builder.toString(); }
private void setChild(Node parent, long branch, Node child) { if (parent == null) { root = child; } else if (branch == 0) { parent.left = child; } else { parent.right = child; } }
private Node copyRecursive(Node node) { Node result = null; if (node != null) { result = createNode(node.bits, node.level, node.weightedCount); result.left = copyRecursive(node.left); result.right = copyRecursive(node.right); } return result; }
public static QuantileDigest deserialize(DataInput input) { try { double maxError = input.readDouble(); double alpha = input.readDouble(); QuantileDigest result = new QuantileDigest(maxError, alpha); result.landmarkInSeconds = input.readLong(); result.min = input.readLong(); result.max = input.readLong(); result.totalNodeCount = input.readInt(); Deque<Node> stack = new ArrayDeque<>(); for (int i = 0; i < result.totalNodeCount; i++) { int flags = input.readByte(); Node node = deserializeNode(input); if ((flags & Flags.HAS_RIGHT) != 0) { node.right = stack.pop(); } if ((flags & Flags.HAS_LEFT) != 0) { node.left = stack.pop(); } stack.push(node); result.weightedCount += node.weightedCount; if (node.weightedCount >= ZERO_WEIGHT_THRESHOLD) { result.nonZeroNodeCount++; } } if (!stack.isEmpty()) { Preconditions.checkArgument( stack.size() == 1, "Tree is corrupted. Expected a single root node"); result.root = stack.pop(); } return result; } catch (IOException e) { throw Throwables.propagate(e); } }
private void insert(long bits, double weight) { long lastBranch = 0; Node parent = null; Node current = root; while (true) { if (current == null) { setChild(parent, lastBranch, createLeaf(bits, weight)); return; } else if (!inSameSubtree(bits, current.bits, current.level)) { // if bits and node.bits are not in the same branch given node's level, // insert a parent above them at the point at which branches diverge setChild(parent, lastBranch, makeSiblings(current, createLeaf(bits, weight))); return; } else if (current.level == 0 && current.bits == bits) { // found the node double oldWeight = current.weightedCount; current.weightedCount += weight; if (current.weightedCount >= ZERO_WEIGHT_THRESHOLD && oldWeight < ZERO_WEIGHT_THRESHOLD) { ++nonZeroNodeCount; } weightedCount += weight; return; } // we're on the correct branch of the tree and we haven't reached a leaf, so keep going down long branch = bits & current.getBranchMask(); parent = current; lastBranch = branch; if (branch == 0) { current = current.left; } else { current = current.right; } } }
/** * Remove the node if possible or set its count to 0 if it has children and it needs to be kept * around */ private Node tryRemove(Node node) { if (node == null) { return null; } if (node.weightedCount >= ZERO_WEIGHT_THRESHOLD) { --nonZeroNodeCount; } weightedCount -= node.weightedCount; Node result = null; if (node.isLeaf()) { --totalNodeCount; } else if (node.hasSingleChild()) { result = node.getSingleChild(); --totalNodeCount; } else { node.weightedCount = 0; result = node; } return result; }
private Node makeSiblings(Node node, Node sibling) { int parentLevel = MAX_BITS - Long.numberOfLeadingZeros(node.bits ^ sibling.bits); Node parent = createNode(node.bits, parentLevel, 0); // the branch is given by the bit at the level one below parent long branch = sibling.bits & parent.getBranchMask(); if (branch == 0) { parent.left = sibling; parent.right = node; } else { parent.left = node; parent.right = sibling; } return parent; }
private Node merge(Node node, Node other) { if (node == null) { return copyRecursive(other); } else if (other == null) { return node; } else if (!inSameSubtree(node.bits, other.bits, Math.max(node.level, other.level))) { return makeSiblings(node, copyRecursive(other)); } else if (node.level > other.level) { long branch = other.bits & node.getBranchMask(); if (branch == 0) { node.left = merge(node.left, other); } else { node.right = merge(node.right, other); } return node; } else if (node.level < other.level) { Node result = createNode(other.bits, other.level, other.weightedCount); long branch = node.bits & other.getBranchMask(); if (branch == 0) { result.left = merge(node, other.left); result.right = copyRecursive(other.right); } else { result.left = copyRecursive(other.left); result.right = merge(node, other.right); } return result; } // else, they must be at the same level and on the same path, so just bump the counts double oldWeight = node.weightedCount; weightedCount += other.weightedCount; node.weightedCount = node.weightedCount + other.weightedCount; node.left = merge(node.left, other.left); node.right = merge(node.right, other.right); if (oldWeight < ZERO_WEIGHT_THRESHOLD && node.weightedCount >= ZERO_WEIGHT_THRESHOLD) { nonZeroNodeCount++; } return node; }