Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1853)

Unified Diff: editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/index/structure/btree/BPlusTree.java

Issue 338503002: Backport B+ tree implementation from Dart to Java. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixes for review comments. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/index/structure/btree/BPlusTree.java
diff --git a/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/index/structure/btree/BPlusTree.java b/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/index/structure/btree/BPlusTree.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2113ca3977ed23c8902548f86efa83ce4559ea3
--- /dev/null
+++ b/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/index/structure/btree/BPlusTree.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (c) 2014, the Dart project authors.
+ *
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.dart.engine.internal.index.structure.btree;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A simple B+ tree (http://en.wikipedia.org/wiki/B+_tree) implementation.
+ *
+ * <pre>
+ * [K] is the keys type.
+ * [V] is the values type.
+ * [N] is the type of node identifiers using by the [NodeManager].
+ * </pre>
+ *
+ * @coverage dart.engine.index.structure
+ */
+public class BPlusTree<K, V, N> {
+ /**
+ * An internal node with keys and children references.
+ */
+ private class InternalNode extends Node {
+ List<N> children = new ArrayList<N>();
+ final int maxKeys;
+ final int minKeys;
+
+ InternalNode(N id, int maxKeys) {
+ super(id);
+ this.maxKeys = maxKeys;
+ minKeys = maxKeys / 2;
+ }
+
+ @Override
+ V find(K key) {
+ int index = findChildIndex(key);
+ Node child = readNode(children.get(index));
+ return child.find(key);
+ }
+
+ @Override
+ Split insert(K key, V value) {
+ // Early split.
+ if (keys.size() == maxKeys) {
+ int middle = (maxKeys + 1) / 2;
+ K splitKey = keys.get(middle);
+ // Overflow into a new sibling.
+ InternalNode sibling = newInternalNode();
+ sibling.keys.addAll(keys.subList(middle + 1, keys.size()));
+ sibling.children.addAll(children.subList(middle + 1, children.size()));
+ setListLength(keys, middle);
+ setListLength(children, middle + 1);
+ // Insert into this node or sibling.
+ if (comparator.compare(key, splitKey) < 0) {
+ insertNotFull(key, value);
+ } else {
+ sibling.insertNotFull(key, value);
+ }
+ // Prepare split.
+ writeInternalNode(this);
+ writeInternalNode(sibling);
+ return new Split(splitKey, id, sibling.id);
+ }
+ // No split.
+ insertNotFull(key, value);
+ return null;
+ }
+
+ @Override
+ Remove remove(K key, Node left, K anchor, Node right) {
+ int index = findChildIndex(key);
+ K thisAnchor = index == 0 ? keys.get(0) : keys.get(index - 1);
+ // Prepare children.
+ Node child0 = readNode(children.get(index));
+ Node leftChild;
+ Node rightChild;
+ if (index != 0) {
+ leftChild = readNode(children.get(index - 1));
+ } else {
+ leftChild = null;
+ }
+ if (index < children.size() - 1) {
+ rightChild = readNode(children.get(index + 1));
+ } else {
+ rightChild = null;
+ }
+ // Ask child to remove.
+ Remove result = child0.remove(key, leftChild, thisAnchor, rightChild);
+ V value = result.value;
+ if (value == null) {
+ return new Remove(value);
+ }
+ // Do keys / children updates
+ boolean hasUpdates = false;
+ {
+ // Update anchor if borrowed.
+ if (result.leftAnchor != null) {
+ keys.set(index - 1, result.leftAnchor);
+ hasUpdates = true;
+ }
+ if (result.rightAnchor != null) {
+ keys.set(index, result.rightAnchor);
+ hasUpdates = true;
+ }
+ // Update keys / children if merged.
+ if (result.mergedLeft) {
+ keys.remove(index - 1);
+ N child = children.remove(index);
+ manager.delete(child);
+ hasUpdates = true;
+ }
+ if (result.mergedRight) {
+ keys.remove(index);
+ N child = children.remove(index);
+ manager.delete(child);
+ hasUpdates = true;
+ }
+ }
+ // Write if updated.
+ if (!hasUpdates) {
+ return new Remove(value);
+ }
+ writeInternalNode(this);
+ // Perform balancing.
+ if (keys.size() < minKeys) {
+ // Try left sibling.
+ if (left instanceof BPlusTree.InternalNode) {
+ @SuppressWarnings("unchecked")
+ InternalNode leftInternal = (InternalNode) left;
+ // Try to redistribute.
+ int leftLength = left.keys.size();
+ if (leftLength > minKeys) {
+ int halfExcess = (leftLength - minKeys + 1) / 2;
+ int newLeftLength = leftLength - halfExcess;
+ keys.add(0, anchor);
+ keys.addAll(0, left.keys.subList(newLeftLength, leftLength));
+ children.addAll(0, leftInternal.children.subList(newLeftLength, leftLength + 1));
+ K newAnchor = left.keys.get(newLeftLength - 1);
+ setListLength(left.keys, newLeftLength - 1);
+ setListLength(leftInternal.children, newLeftLength);
+ writeInternalNode(this);
+ writeInternalNode(leftInternal);
+ return new Remove(value).borrowLeft(newAnchor);
+ }
+ // Do merge.
+ left.keys.add(anchor);
+ left.keys.addAll(keys);
+ leftInternal.children.addAll(children);
+ writeInternalNode(this);
+ writeInternalNode(leftInternal);
+ return new Remove(value).mergeLeft();
+ }
+ // Try right sibling.
+ if (right instanceof BPlusTree.InternalNode) {
+ @SuppressWarnings("unchecked")
+ InternalNode rightInternal = (InternalNode) right;
+ // Try to redistribute.
+ int rightLength = right.keys.size();
+ if (rightLength > minKeys) {
+ int halfExcess = (rightLength - minKeys + 1) / 2;
+ keys.add(anchor);
+ keys.addAll(right.keys.subList(0, halfExcess - 1));
+ children.addAll(rightInternal.children.subList(0, halfExcess));
+ K newAnchor = right.keys.get(halfExcess - 1);
+ removeListRange(right.keys, 0, halfExcess);
+ removeListRange(rightInternal.children, 0, halfExcess);
+ writeInternalNode(this);
+ writeInternalNode(rightInternal);
+ return new Remove(value).borrowRight(newAnchor);
+ }
+ // Do merge.
+ right.keys.add(0, anchor);
+ right.keys.addAll(0, keys);
+ rightInternal.children.addAll(0, children);
+ writeInternalNode(this);
+ writeInternalNode(rightInternal);
+ return new Remove(value).mergeRight();
+ }
+ }
+ // No balancing required.
+ return new Remove(value);
+ }
+
+ @Override
+ void writeOn(StringBuilder buffer, String indent) {
+ buffer.append(indent);
+ buffer.append("INode {\n");
+ for (int i = 0; i < keys.size(); i++) {
+ Node child = readNode(children.get(i));
+ child.writeOn(buffer, indent + " ");
+ buffer.append(indent);
+ buffer.append(" ");
+ buffer.append(keys.get(i));
+ buffer.append("\n");
+ }
+ Node child = readNode(children.get(keys.size()));
+ child.writeOn(buffer, indent + " ");
+ buffer.append(indent);
+ buffer.append("}\n");
+ }
+
+ /**
+ * Returns the index of the child into which [key] should be inserted.
+ */
+ private int findChildIndex(K key) {
+ int lo = 0;
+ int hi = keys.size() - 1;
+ while (lo <= hi) {
+ int mid = lo + (hi - lo) / 2;
+ int compare = comparator.compare(key, keys.get(mid));
+ if (compare < 0) {
+ hi = mid - 1;
+ } else if (compare > 0) {
+ lo = mid + 1;
+ } else {
+ return mid + 1;
+ }
+ }
+ return lo;
+ }
+
+ private void insertNotFull(K key, V value) {
+ int index = findChildIndex(key);
+ Node child = readNode(children.get(index));
+ Split result = child.insert(key, value);
+ if (result != null) {
+ keys.add(index, result.key);
+ children.set(index, result.left);
+ children.add(index + 1, result.right);
+ writeInternalNode(this);
+ }
+ }
+ }
+
+ /**
+ * A leaf node with keys and values.
+ */
+ private class LeafNode extends Node {
+ final int maxKeys;
+ final int minKeys;
+ List<V> values = new ArrayList<V>();
+
+ LeafNode(N id, int maxKeys) {
+ super(id);
+ this.maxKeys = maxKeys;
+ this.minKeys = maxKeys / 2;
+ }
+
+ @Override
+ V find(K key) {
+ int index = findKeyIndex(key);
+ if (index >= keys.size()) {
+ return null;
+ }
+ if (comparator.compare(keys.get(index), key) != 0) {
+ return null;
+ }
+ return values.get(index);
+ }
+
+ @Override
+ Split insert(K key, V value) {
+ int index = findKeyIndex(key);
+ // The node is full.
+ if (keys.size() == maxKeys) {
+ int middle = (maxKeys + 1) / 2;
+ LeafNode sibling = newLeafNode();
+ sibling.keys.addAll(keys.subList(middle, keys.size()));
+ sibling.values.addAll(values.subList(middle, values.size()));
+ setListLength(keys, middle);
+ setListLength(values, middle);
+ // Insert into the left / right sibling.
+ if (index < middle) {
+ insertNotFull(key, value, index);
+ } else {
+ sibling.insertNotFull(key, value, index - middle);
+ }
+ // Notify the parent about the split.
+ writeLeafNode(this);
+ writeLeafNode(sibling);
+ return new Split(sibling.keys.get(0), id, sibling.id);
+ }
+ // The node was not full.
+ insertNotFull(key, value, index);
+ return null;
+ }
+
+ @Override
+ Remove remove(K key, Node left, K anchor, Node right) {
+ // Find the key.
+ int index = keys.indexOf(key);
+ if (index == -1) {
+ return new Remove(null);
+ }
+ // Remove key / value.
+ keys.remove(index);
+ V value = values.remove(index);
+ writeLeafNode(this);
+ // Perform balancing.
+ if (keys.size() < minKeys) {
+ // Try left sibling.
+ if (left instanceof BPlusTree.LeafNode) {
+ @SuppressWarnings("unchecked")
+ LeafNode leftLeaf = (LeafNode) left;
+ // Try to redistribute.
+ int leftLength = left.keys.size();
+ if (leftLength > minKeys) {
+ int halfExcess = (leftLength - minKeys + 1) / 2;
+ int newLeftLength = leftLength - halfExcess;
+ keys.addAll(0, left.keys.subList(newLeftLength, leftLength));
+ values.addAll(0, leftLeaf.values.subList(newLeftLength, leftLength));
+ setListLength(left.keys, newLeftLength);
+ setListLength(leftLeaf.values, newLeftLength);
+ writeLeafNode(this);
+ writeLeafNode(leftLeaf);
+ return new Remove(value).borrowLeft(keys.get(0));
+ }
+ // Do merge.
+ left.keys.addAll(keys);
+ leftLeaf.values.addAll(values);
+ writeLeafNode(this);
+ writeLeafNode(leftLeaf);
+ return new Remove(value).mergeLeft();
+ }
+ // Try right sibling.
+ if (right instanceof BPlusTree.LeafNode) {
+ @SuppressWarnings("unchecked")
+ LeafNode rightLeaf = (LeafNode) right;
+ // Try to redistribute.
+ int rightLength = right.keys.size();
+ if (rightLength > minKeys) {
+ int halfExcess = (rightLength - minKeys + 1) / 2;
+ keys.addAll(right.keys.subList(0, halfExcess));
+ values.addAll(rightLeaf.values.subList(0, halfExcess));
+ removeListRange(right.keys, 0, halfExcess);
+ removeListRange(rightLeaf.values, 0, halfExcess);
+ writeLeafNode(this);
+ writeLeafNode(rightLeaf);
+ return new Remove(value).borrowRight(right.keys.get(0));
+ }
+ // Do merge.
+ right.keys.addAll(0, keys);
+ rightLeaf.values.addAll(0, values);
+ writeLeafNode(this);
+ writeLeafNode(rightLeaf);
+ return new Remove(value).mergeRight();
+ }
+ }
+ // No balancing required.
+ return new Remove(value);
+ }
+
+ @Override
+ void writeOn(StringBuilder buffer, String indent) {
+ buffer.append(indent);
+ buffer.append("LNode {");
+ for (int i = 0; i < keys.size(); i++) {
+ if (i != 0) {
+ buffer.append(", ");
+ }
+ buffer.append(keys.get(i));
+ buffer.append(": ");
+ buffer.append(values.get(i));
+ }
+ buffer.append("}\n");
+ }
+
+ /**
+ * Returns the index where [key] should be inserted.
+ */
+ private int findKeyIndex(K key) {
+ int lo = 0;
+ int hi = keys.size() - 1;
+ while (lo <= hi) {
+ int mid = lo + (hi - lo) / 2;
+ int compare = comparator.compare(key, keys.get(mid));
+ if (compare < 0) {
+ hi = mid - 1;
+ } else if (compare > 0) {
+ lo = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+ return lo;
+ }
+
+ private void insertNotFull(K key, V value, int index) {
+ if (index < keys.size() && comparator.compare(keys.get(index), key) == 0) {
+ values.set(index, value);
+ } else {
+ keys.add(index, key);
+ values.add(index, value);
+ }
+ writeLeafNode(this);
+ }
+ }
+
+ /**
+ * An internal or leaf node.
+ */
+ private abstract class Node {
+ /**
+ * The identifier of this node.
+ */
+ final N id;
+
+ /**
+ * The list of keys.
+ */
+ List<K> keys = new ArrayList<K>();
+
+ Node(N id) {
+ this.id = id;
+ }
+
+ /**
+ * Looks for [key]. Returns the associated value if found. Returns `null` if not found.
+ */
+ abstract V find(K key);
+
+ /**
+ * Inserts the [key] / [value] pair into this [Node]. Returns a [Split] object if split happens,
+ * or `null` otherwise.
+ */
+ abstract Split insert(K key, V value);
+
+ /**
+ * Removes the association for the given [key]. Returns the [Remove] information about an
+ * operation performed. It may be restructuring or merging, with [left] or [left] siblings.
+ */
+ abstract Remove remove(K key, Node left, K anchor, Node right);
+
+ /**
+ * Writes a textual presentation of the tree into [buffer].
+ */
+ abstract void writeOn(StringBuilder buffer, String indent);
+ }
+
+ /**
+ * A container with information about redistribute / merge.
+ */
+ private class Remove {
+ K leftAnchor;
+ boolean mergedLeft = false;
+ boolean mergedRight = false;
+ K rightAnchor;
+ final V value;
+
+ Remove(V value) {
+ this.value = value;
+ }
+
+ Remove borrowLeft(K leftAnchor) {
+ this.leftAnchor = leftAnchor;
+ return this;
+ }
+
+ Remove borrowRight(K rightAnchor) {
+ this.rightAnchor = rightAnchor;
+ return this;
+ }
+
+ Remove mergeLeft() {
+ this.mergedLeft = true;
+ return this;
+ }
+
+ Remove mergeRight() {
+ this.mergedRight = true;
+ return this;
+ }
+ }
+
+ /**
+ * A container with information about split during insert.
+ */
+ private class Split {
+ final K key;
+ final N left;
+ final N right;
+
+ Split(K key, N left, N right) {
+ this.key = key;
+ this.left = left;
+ this.right = right;
+ }
+ }
+
+ /**
+ * The [Comparator] to compare keys.
+ */
+ final Comparator<K> comparator;
+
+ /**
+ * The [NodeManager] to manage nodes.
+ */
+ final NodeManager<K, V, N> manager;
+
+ /**
+ * The maximum number of keys in an index node.
+ */
+ final int maxInternalKeys;
+
+ /**
+ * The maximum number of keys in a leaf node.
+ */
+ final int maxLeafKeys;
+
+ /**
+ * The root node.
+ */
+ Node root;
+
+ /**
+ * Creates a new [BPlusTree] instance.
+ */
+ @SuppressWarnings("unchecked")
+ public BPlusTree(Comparator<K> _comparator, NodeManager<K, V, N> manager) {
+ this.comparator = _comparator;
+ this.manager = manager;
+ this.maxInternalKeys = manager.getMaxInternalKeys();
+ this.maxLeafKeys = manager.getMaxLeafKeys();
+ root = newLeafNode();
+ writeLeafNode((LeafNode) root);
+ }
+
+ /**
+ * Returns the value for [key] or `null` if [key] is not in the
+ */
+ public V find(K key) {
+ return root.find(key);
+ }
+
+ /**
+ * Associates the [key] with the given [value]. If the key was already in the tree, its associated
+ * value is changed. Otherwise the key-value pair is added to the
+ */
+ public void insert(K key, V value) {
+ Split result = root.insert(key, value);
+ if (result != null) {
+ InternalNode newRoot = newInternalNode();
+ newRoot.keys.add(result.key);
+ newRoot.children.add(result.left);
+ newRoot.children.add(result.right);
+ root = newRoot;
+ writeInternalNode(newRoot);
+ }
+ }
+
+ /**
+ * Removes the association for the given [key]. Returns the value associated with [key] in the
+ * tree or `null` if [key] is not in the tree.
+ */
+ @SuppressWarnings("unchecked")
+ public V remove(K key) {
+ Remove result = root.remove(key, null, null, null);
+ if (root instanceof BPlusTree.InternalNode) {
+ List<N> children = ((InternalNode) root).children;
+ if (children.size() == 1) {
+ manager.delete(root.id);
+ root = readNode(children.get(0));
+ }
+ }
+ return result.value;
+ }
+
+ /**
+ * Writes a textual presentation of the tree into [buffer].
+ */
+ public void writeOn(StringBuilder buffer) {
+ root.writeOn(buffer, "");
+ }
+
+ /**
+ * Creates a new [InternalNode] instance.
+ */
+ private InternalNode newInternalNode() {
+ N id = manager.createInternal();
+ return new InternalNode(id, maxInternalKeys);
+ }
+
+ /**
+ * Creates a new [LeafNode] instance.
+ */
+ private LeafNode newLeafNode() {
+ N id = manager.createLeaf();
+ return new LeafNode(id, maxLeafKeys);
+ }
+
+ /**
+ * Reads the [InternalNode] with [id] from the manager.
+ */
+ private InternalNode readInternalNode(N id) {
+ InternalNodeData<K, N> data = manager.readInternal(id);
+ InternalNode node = new InternalNode(id, maxInternalKeys);
+ node.keys = data.keys;
+ node.children = data.children;
+ return node;
+ }
+
+ /**
+ * Reads the [LeafNode] with [id] from the manager.
+ */
+ private LeafNode readLeafNode(N id) {
+ LeafNode node = new LeafNode(id, maxLeafKeys);
+ LeafNodeData<K, V> data = manager.readLeaf(id);
+ node.keys = data.keys;
+ node.values = data.values;
+ return node;
+ }
+
+ /**
+ * Reads the [InternalNode] or [LeafNode] with [id] from the manager.
+ */
+ private Node readNode(N id) {
+ if (manager.isInternal(id)) {
+ return readInternalNode(id);
+ } else {
+ return readLeafNode(id);
+ }
+ }
+
+ private void removeListRange(List<?> list, int offset, int length) {
+ list.subList(offset, length).clear();
+ }
+
+ private void setListLength(List<?> list, int newLength) {
+ list.subList(newLength, list.size()).clear();
+ }
+
+ /**
+ * Writes [node] into the manager.
+ */
+ private void writeInternalNode(InternalNode node) {
+ manager.writeInternal(node.id, new InternalNodeData<K, N>(node.keys, node.children));
+ }
+
+ /**
+ * Writes [node] into the manager.
+ */
+ private void writeLeafNode(LeafNode node) {
+ manager.writeLeaf(node.id, new LeafNodeData<K, V>(node.keys, node.values));
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698