├── .gitignore ├── LICENSE ├── README.md ├── build.sbt └── src ├── main └── java │ └── com │ └── ankurdave │ └── part │ ├── ArrayChildPtr.java │ ├── ArtIterator.java │ ├── ArtNode.java │ ├── ArtNode16.java │ ├── ArtNode256.java │ ├── ArtNode4.java │ ├── ArtNode48.java │ ├── ArtTree.java │ ├── ChildPtr.java │ ├── IterCallback.java │ ├── Leaf.java │ └── Node.java └── test └── scala └── com └── ankurdave └── part └── PartSuite.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | *.class 31 | 32 | # Mobile Tools for Java (J2ME) 33 | .mtj.tmp/ 34 | 35 | # Package Files # 36 | *.jar 37 | *.war 38 | *.ear 39 | 40 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 41 | hs_err_pid* 42 | 43 | *.class 44 | *.log 45 | 46 | # sbt specific 47 | .cache 48 | .history 49 | .lib/ 50 | dist/* 51 | target/ 52 | lib_managed/ 53 | src_managed/ 54 | project/boot/ 55 | project/plugins/project/ 56 | 57 | # Scala-IDE specific 58 | .scala_dependencies 59 | .worksheet 60 | .idea 61 | 62 | *.dSYM 63 | 64 | ArtCorrectnessTest 65 | ArtMicrobenchmark 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Armon Dadgar, Ankur Dave 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the organization nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL ARMON DADGAR BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Persistent Adaptive Radix Tree (PART) for Java 2 | 3 | The Persistent Adaptive Radix Tree (PART) is a trie with a high branching factor and adaptively-sized nodes based on [ART](http://www3.informatik.tu-muenchen.de/~leis/papers/ART.pdf). It provides efficient persistence using path copying and reference counting. In microbenchmarks, PART achieves throughput and space efficiency comparable to a mutable hash table while providing persistence, lower variance in operation latency, and efficient union, intersection, and range scan operations. 4 | 5 | This repository contains a Java implementation of PART based on [libart](https://github.com/armon/libart). 6 | 7 | # Usage 8 | 9 | Add the dependency to your SBT project by adding the following to `build.sbt`: 10 | 11 | ```scala 12 | resolvers += "Repo at github.com/ankurdave/maven-repo" at "https://github.com/ankurdave/maven-repo/raw/master" 13 | 14 | libraryDependencies += "com.ankurdave" %% "part" % "0.1" 15 | ``` 16 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "part" 2 | 3 | version := "0.2" 4 | 5 | organization := "com.ankurdave" 6 | 7 | crossScalaVersions := Seq("2.10.6", "2.11.8") 8 | scalaVersion := "2.11.8" 9 | 10 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.0" % "test" 11 | 12 | javaOptions += "-Xmx10G" 13 | 14 | fork := true 15 | 16 | // Enable assertions 17 | javaOptions in Test += "-ea" 18 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArrayChildPtr.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class ArrayChildPtr extends ChildPtr { 4 | public ArrayChildPtr(Node[] children, int i) { 5 | this.children = children; 6 | this.i = i; 7 | } 8 | 9 | @Override public Node get() { 10 | return children[i]; 11 | } 12 | 13 | @Override public void set(Node n) { 14 | children[i] = n; 15 | } 16 | 17 | private Node[] children; 18 | private final int i; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtIterator.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Deque; 5 | import java.util.Iterator; 6 | import java.util.NoSuchElementException; 7 | 8 | import scala.Tuple2; 9 | 10 | class ArtIterator implements Iterator> { 11 | private Deque elemStack = new ArrayDeque(); 12 | private Deque idxStack = new ArrayDeque(); 13 | 14 | public ArtIterator(Node root) { 15 | if (root != null) { 16 | elemStack.push(root); 17 | idxStack.push(0); 18 | maybeAdvance(); 19 | } 20 | } 21 | 22 | @Override public boolean hasNext() { 23 | return !elemStack.isEmpty(); 24 | } 25 | 26 | @Override public Tuple2 next() { 27 | if (hasNext()) { 28 | Leaf leaf = (Leaf)elemStack.peek(); 29 | byte[] key = leaf.key; 30 | Object value = leaf.value; 31 | 32 | // Mark the leaf as consumed 33 | idxStack.push(idxStack.pop() + 1); 34 | 35 | maybeAdvance(); 36 | return new Tuple2(key, value); 37 | } else { 38 | throw new NoSuchElementException("end of iterator"); 39 | } 40 | } 41 | 42 | @Override public void remove() { 43 | throw new UnsupportedOperationException(); 44 | } 45 | 46 | // Postcondition: if the stack is nonempty, the top of the stack must contain a leaf 47 | private void maybeAdvance() { 48 | // Pop exhausted nodes 49 | while (!elemStack.isEmpty() && elemStack.peek().exhausted(idxStack.peek())) { 50 | elemStack.pop(); 51 | idxStack.pop(); 52 | 53 | if (!elemStack.isEmpty()) { 54 | // Move on by advancing the exhausted node's parent 55 | idxStack.push(idxStack.pop() + 1); 56 | } 57 | } 58 | 59 | if (!elemStack.isEmpty()) { 60 | // Descend to the next leaf node element 61 | while (true) { 62 | if (elemStack.peek() instanceof Leaf) { 63 | // Done - reached the next element 64 | break; 65 | } else { 66 | // Advance to the next child of this node 67 | ArtNode cur = (ArtNode)elemStack.peek(); 68 | idxStack.push(cur.nextChildAtOrAfter(idxStack.pop())); 69 | Node child = cur.childAt(idxStack.peek()); 70 | 71 | // Push it onto the stack 72 | elemStack.push(child); 73 | idxStack.push(0); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtNode.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | abstract class ArtNode extends Node { 4 | public ArtNode() { 5 | super(); 6 | } 7 | 8 | public ArtNode(final ArtNode other) { 9 | super(other); 10 | this.num_children = other.num_children; 11 | this.partial_len = other.partial_len; 12 | System.arraycopy(other.partial, 0, 13 | partial, 0, 14 | Math.min(Node.MAX_PREFIX_LEN, partial_len)); 15 | } 16 | 17 | /** 18 | * Returns the number of prefix characters shared between 19 | * the key and node. 20 | */ 21 | public int check_prefix(final byte[] key, int depth) { 22 | int max_cmp = Math.min(Math.min(partial_len, Node.MAX_PREFIX_LEN), key.length - depth); 23 | int idx; 24 | for (idx = 0; idx < max_cmp; idx++) { 25 | if (partial[idx] != key[depth + idx]) 26 | return idx; 27 | } 28 | return idx; 29 | } 30 | 31 | /** 32 | * Calculates the index at which the prefixes mismatch 33 | */ 34 | public int prefix_mismatch(final byte[] key, int depth) { 35 | int max_cmp = Math.min(Math.min(Node.MAX_PREFIX_LEN, partial_len), key.length - depth); 36 | int idx; 37 | for (idx = 0; idx < max_cmp; idx++) { 38 | if (partial[idx] != key[depth + idx]) 39 | return idx; 40 | } 41 | 42 | // If the prefix is short we can avoid finding a leaf 43 | if (partial_len > Node.MAX_PREFIX_LEN) { 44 | // Prefix is longer than what we've checked, find a leaf 45 | final Leaf l = this.minimum(); 46 | max_cmp = Math.min(l.key.length, key.length) - depth; 47 | for (; idx < max_cmp; idx++) { 48 | if (l.key[idx + depth] != key[depth + idx]) 49 | return idx; 50 | } 51 | } 52 | return idx; 53 | } 54 | 55 | public abstract ChildPtr find_child(byte c); 56 | 57 | public abstract void add_child(ChildPtr ref, byte c, Node child); 58 | 59 | public abstract void remove_child(ChildPtr ref, byte c); 60 | 61 | // Precondition: isLastChild(i) == false 62 | public abstract int nextChildAtOrAfter(int i); 63 | 64 | public abstract Node childAt(int i); 65 | 66 | @Override public boolean insert(ChildPtr ref, final byte[] key, Object value, 67 | int depth, boolean force_clone) { 68 | boolean do_clone = force_clone || this.refcount > 1; 69 | 70 | // Check if given node has a prefix 71 | if (partial_len > 0) { 72 | // Determine if the prefixes differ, since we need to split 73 | int prefix_diff = prefix_mismatch(key, depth); 74 | if (prefix_diff >= partial_len) { 75 | depth += partial_len; 76 | } else { 77 | // Create a new node 78 | ArtNode4 result = new ArtNode4(); 79 | Node ref_old = ref.get(); 80 | ref.change_no_decrement(result); // don't decrement yet, because doing so might destroy self 81 | result.partial_len = prefix_diff; 82 | System.arraycopy(partial, 0, 83 | result.partial, 0, 84 | Math.min(Node.MAX_PREFIX_LEN, prefix_diff)); 85 | 86 | // Adjust the prefix of the old node 87 | ArtNode this_writable = do_clone ? (ArtNode)this.n_clone() : this; 88 | if (partial_len <= Node.MAX_PREFIX_LEN) { 89 | result.add_child(ref, this_writable.partial[prefix_diff], this_writable); 90 | this_writable.partial_len -= (prefix_diff + 1); 91 | System.arraycopy(this_writable.partial, prefix_diff + 1, 92 | this_writable.partial, 0, 93 | Math.min(Node.MAX_PREFIX_LEN, this_writable.partial_len)); 94 | } else { 95 | this_writable.partial_len -= (prefix_diff+1); 96 | final Leaf l = this.minimum(); 97 | result.add_child(ref, l.key[depth + prefix_diff], this_writable); 98 | System.arraycopy(l.key, depth + prefix_diff + 1, 99 | this_writable.partial, 0, 100 | Math.min(Node.MAX_PREFIX_LEN, this_writable.partial_len)); 101 | } 102 | 103 | // Insert the new leaf 104 | Leaf l = new Leaf(key, value); 105 | result.add_child(ref, key[depth + prefix_diff], l); 106 | 107 | ref_old.decrement_refcount(); 108 | 109 | return true; 110 | } 111 | } 112 | 113 | // Clone self if necessary 114 | ArtNode this_writable = do_clone ? (ArtNode)this.n_clone() : this; 115 | if (do_clone) { 116 | ref.change(this_writable); 117 | } 118 | // Do the insert, either in a child (if a matching child already exists) or in self 119 | ChildPtr child = this_writable.find_child(key[depth]); 120 | if (child != null) { 121 | return Node.insert(child.get(), child, key, value, depth+1, force_clone); 122 | } else { 123 | // No child, node goes within us 124 | Leaf l = new Leaf(key, value); 125 | this_writable.add_child(ref, key[depth], l); 126 | // If `this` was full and `do_clone` is true, we will clone a full node 127 | // and then immediately delete the clone in favor of a larger node. 128 | // TODO: avoid this 129 | return true; 130 | } 131 | } 132 | 133 | @Override public boolean delete(ChildPtr ref, final byte[] key, int depth, 134 | boolean force_clone) { 135 | // Bail if the prefix does not match 136 | if (partial_len > 0) { 137 | int prefix_len = check_prefix(key, depth); 138 | if (prefix_len != Math.min(MAX_PREFIX_LEN, partial_len)) { 139 | return false; 140 | } 141 | depth += partial_len; 142 | } 143 | 144 | boolean do_clone = force_clone || this.refcount > 1; 145 | 146 | // Clone self if necessary. Note: this allocation will be wasted if the 147 | // key does not exist in the child's subtree 148 | ArtNode this_writable = do_clone ? (ArtNode)this.n_clone() : this; 149 | 150 | // Find child node 151 | ChildPtr child = this_writable.find_child(key[depth]); 152 | if (child == null) return false; // when translating to C++, make sure to delete this_writable 153 | 154 | if (do_clone) { 155 | ref.change(this_writable); 156 | } 157 | 158 | boolean child_is_leaf = child.get() instanceof Leaf; 159 | boolean do_delete = child.get().delete(child, key, depth + 1, do_clone); 160 | 161 | if (do_delete && child_is_leaf) { 162 | // The leaf to delete is our child, so we must remove it 163 | this_writable.remove_child(ref, key[depth]); 164 | } 165 | 166 | return do_delete; 167 | } 168 | 169 | int num_children = 0; 170 | int partial_len = 0; 171 | final byte[] partial = new byte[Node.MAX_PREFIX_LEN]; 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtNode16.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class ArtNode16 extends ArtNode { 4 | public static int count; 5 | 6 | public ArtNode16() { 7 | super(); 8 | count++; 9 | } 10 | 11 | public ArtNode16(final ArtNode16 other) { 12 | super(other); 13 | System.arraycopy(other.keys, 0, keys, 0, other.num_children); 14 | for (int i = 0; i < other.num_children; i++) { 15 | children[i] = other.children[i]; 16 | children[i].refcount++; 17 | } 18 | count++; 19 | } 20 | 21 | public ArtNode16(final ArtNode4 other) { 22 | this(); 23 | // ArtNode 24 | this.num_children = other.num_children; 25 | this.partial_len = other.partial_len; 26 | System.arraycopy(other.partial, 0, 27 | this.partial, 0, 28 | Math.min(MAX_PREFIX_LEN, this.partial_len)); 29 | // ArtNode16 from ArtNode4 30 | System.arraycopy(other.keys, 0, keys, 0, this.num_children); 31 | for (int i = 0; i < this.num_children; i++) { 32 | children[i] = other.children[i]; 33 | children[i].refcount++; 34 | } 35 | } 36 | 37 | public ArtNode16(final ArtNode48 other) { 38 | this(); 39 | assert(other.num_children <= 16); 40 | // ArtNode 41 | this.num_children = other.num_children; 42 | this.partial_len = other.partial_len; 43 | System.arraycopy(other.partial, 0, 44 | this.partial, 0, 45 | Math.min(MAX_PREFIX_LEN, this.partial_len)); 46 | // ArtNode16 from ArtNode48 47 | int child = 0; 48 | for (int i = 0; i < 256; i++) { 49 | int pos = to_uint(other.keys[i]); 50 | if (pos != 0) { 51 | keys[child] = (byte)i; 52 | children[child] = other.children[pos - 1]; 53 | children[child].refcount++; 54 | child++; 55 | } 56 | } 57 | } 58 | 59 | @Override public Node n_clone() { 60 | return new ArtNode16(this); 61 | } 62 | 63 | @Override public ChildPtr find_child(byte c) { 64 | // TODO: avoid linear search using intrinsics if available 65 | for (int i = 0; i < this.num_children; i++) { 66 | if (keys[i] == c) { 67 | return new ArrayChildPtr(children, i); 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | @Override public Leaf minimum() { 74 | return Node.minimum(children[0]); 75 | } 76 | 77 | @Override public void add_child(ChildPtr ref, byte c, Node child) { 78 | assert(refcount <= 1); 79 | 80 | if (this.num_children < 16) { 81 | // TODO: avoid linear search using intrinsics if available 82 | int idx; 83 | for (idx = 0; idx < this.num_children; idx++) { 84 | if (to_uint(c) < to_uint(keys[idx])) break; 85 | } 86 | 87 | // Shift to make room 88 | System.arraycopy(this.keys, idx, this.keys, idx + 1, this.num_children - idx); 89 | System.arraycopy(this.children, idx, this.children, idx + 1, this.num_children - idx); 90 | 91 | // Insert element 92 | this.keys[idx] = c; 93 | this.children[idx] = child; 94 | child.refcount++; 95 | this.num_children++; 96 | } else { 97 | // Copy the node16 into a new node48 98 | ArtNode48 result = new ArtNode48(this); 99 | // Update the parent pointer to the node48 100 | ref.change(result); 101 | // Insert the element into the node48 instead 102 | result.add_child(ref, c, child); 103 | } 104 | } 105 | 106 | @Override public void remove_child(ChildPtr ref, byte c) { 107 | assert(refcount <= 1); 108 | 109 | int idx; 110 | for (idx = 0; idx < this.num_children; idx++) { 111 | if (c == keys[idx]) break; 112 | } 113 | if (idx == this.num_children) return; 114 | 115 | children[idx].decrement_refcount(); 116 | 117 | // Shift to fill the hole 118 | System.arraycopy(this.keys, idx + 1, this.keys, idx, this.num_children - idx - 1); 119 | System.arraycopy(this.children, idx + 1, this.children, idx, this.num_children - idx - 1); 120 | this.num_children--; 121 | 122 | if (num_children == 3) { 123 | ArtNode4 result = new ArtNode4(this); 124 | ref.change(result); 125 | } 126 | } 127 | 128 | @Override public boolean exhausted(int i) { 129 | return i >= num_children; 130 | } 131 | 132 | @Override public int nextChildAtOrAfter(int i) { 133 | return i; 134 | } 135 | 136 | @Override public Node childAt(int i) { 137 | return children[i]; 138 | } 139 | 140 | @Override public int decrement_refcount() { 141 | if (--this.refcount <= 0) { 142 | int freed = 0; 143 | for (int i = 0; i < this.num_children; i++) { 144 | freed += children[i].decrement_refcount(); 145 | } 146 | count--; 147 | // delete this; 148 | return freed + 232; 149 | // object size (8) + refcount (4) + 150 | // num_children int (4) + partial_len int (4) + 151 | // pointer to partial array (8) + partial array size (8+4+1*MAX_PREFIX_LEN) 152 | // pointer to key array (8) + key array size (8+4+1*16) + 153 | // pointer to children array (8) + children array size (8+4+8*16) 154 | } 155 | return 0; 156 | } 157 | 158 | byte[] keys = new byte[16]; 159 | Node[] children = new Node[16]; 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtNode256.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class ArtNode256 extends ArtNode { 4 | public static int count; 5 | 6 | public ArtNode256() { 7 | super(); 8 | count++; 9 | } 10 | 11 | public ArtNode256(final ArtNode256 other) { 12 | super(other); 13 | for (int i = 0; i < 256; i++) { 14 | children[i] = other.children[i]; 15 | if (children[i] != null) { 16 | children[i].refcount++; 17 | } 18 | } 19 | count++; 20 | } 21 | 22 | public ArtNode256(final ArtNode48 other) { 23 | this(); 24 | // ArtNode 25 | this.num_children = other.num_children; 26 | this.partial_len = other.partial_len; 27 | System.arraycopy(other.partial, 0, this.partial, 0, Math.min(MAX_PREFIX_LEN, this.partial_len)); 28 | // ArtNode256 from ArtNode48 29 | for (int i = 0; i < 256; i++) { 30 | if (other.keys[i] != 0) { 31 | children[i] = other.children[to_uint(other.keys[i]) - 1]; 32 | children[i].refcount++; 33 | } 34 | } 35 | } 36 | 37 | @Override public Node n_clone() { 38 | return new ArtNode256(this); 39 | } 40 | 41 | @Override public ChildPtr find_child(byte c) { 42 | if (children[to_uint(c)] != null) return new ArrayChildPtr(children, to_uint(c)); 43 | return null; 44 | } 45 | 46 | @Override public Leaf minimum() { 47 | int idx = 0; 48 | while (children[idx] == null) idx++; 49 | return Node.minimum(children[idx]); 50 | } 51 | 52 | @Override public void add_child(ChildPtr ref, byte c, Node child) { 53 | assert(refcount <= 1); 54 | 55 | this.num_children++; 56 | this.children[to_uint(c)] = child; 57 | child.refcount++; 58 | } 59 | 60 | @Override public void remove_child(ChildPtr ref, byte c) { 61 | assert(refcount <= 1); 62 | 63 | children[to_uint(c)].decrement_refcount(); 64 | children[to_uint(c)] = null; 65 | num_children--; 66 | 67 | if (num_children == 37) { 68 | ArtNode48 result = new ArtNode48(this); 69 | ref.change(result); 70 | } 71 | } 72 | 73 | @Override public boolean exhausted(int c) { 74 | for (int i = c; i < 256; i++) { 75 | if (children[i] != null) { 76 | return false; 77 | } 78 | } 79 | return true; 80 | } 81 | 82 | @Override public int nextChildAtOrAfter(int c) { 83 | int pos = c; 84 | for (; pos < 256; pos++) { 85 | if (children[pos] != null) { 86 | break; 87 | } 88 | } 89 | return pos; 90 | } 91 | 92 | @Override public Node childAt(int pos) { 93 | return children[pos]; 94 | } 95 | 96 | @Override public int decrement_refcount() { 97 | if (--this.refcount <= 0) { 98 | int freed = 0; 99 | for (int i = 0; i < 256; i++) { 100 | if (children[i] != null) { 101 | freed += children[i].decrement_refcount(); 102 | } 103 | } 104 | count--; 105 | // delete this; 106 | return freed + 2120; 107 | // object size (8) + refcount (4) + 108 | // num_children int (4) + partial_len int (4) + 109 | // pointer to partial array (8) + partial array size (8+4+1*MAX_PREFIX_LEN) 110 | // pointer to children array (8) + children array size (8+4+8*256) + 111 | // padding (4) 112 | } 113 | return 0; 114 | } 115 | 116 | Node[] children = new Node[256]; 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtNode4.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class ArtNode4 extends ArtNode { 4 | public static int count; 5 | 6 | public ArtNode4() { 7 | super(); 8 | count++; 9 | } 10 | 11 | public ArtNode4(final ArtNode4 other) { 12 | super(other); 13 | System.arraycopy(other.keys, 0, keys, 0, other.num_children); 14 | for (int i = 0; i < other.num_children; i++) { 15 | children[i] = other.children[i]; 16 | children[i].refcount++; 17 | } 18 | count++; 19 | } 20 | 21 | public ArtNode4(final ArtNode16 other) { 22 | this(); 23 | assert(other.num_children <= 4); 24 | // ArtNode 25 | this.num_children = other.num_children; 26 | this.partial_len = other.partial_len; 27 | System.arraycopy(other.partial, 0, 28 | this.partial, 0, 29 | Math.min(MAX_PREFIX_LEN, this.partial_len)); 30 | // ArtNode4 from ArtNode16 31 | System.arraycopy(other.keys, 0, keys, 0, this.num_children); 32 | for (int i = 0; i < this.num_children; i++) { 33 | children[i] = other.children[i]; 34 | children[i].refcount++; 35 | } 36 | } 37 | 38 | @Override public Node n_clone() { 39 | return new ArtNode4(this); 40 | } 41 | 42 | @Override public ChildPtr find_child(byte c) { 43 | for (int i = 0; i < this.num_children; i++) { 44 | if (keys[i] == c) { 45 | return new ArrayChildPtr(children, i); 46 | } 47 | } 48 | return null; 49 | } 50 | 51 | @Override public Leaf minimum() { 52 | return Node.minimum(children[0]); 53 | } 54 | 55 | @Override public void add_child(ChildPtr ref, byte c, Node child) { 56 | assert(refcount <= 1); 57 | 58 | if (this.num_children < 4) { 59 | int idx; 60 | for (idx = 0; idx < this.num_children; idx++) { 61 | if (to_uint(c) < to_uint(keys[idx])) break; 62 | } 63 | 64 | // Shift to make room 65 | System.arraycopy(this.keys, idx, this.keys, idx + 1, this.num_children - idx); 66 | System.arraycopy(this.children, idx, this.children, idx + 1, this.num_children - idx); 67 | 68 | // Insert element 69 | this.keys[idx] = c; 70 | this.children[idx] = child; 71 | child.refcount++; 72 | this.num_children++; 73 | } else { 74 | // Copy the node4 into a new node16 75 | ArtNode16 result = new ArtNode16(this); 76 | // Update the parent pointer to the node16 77 | ref.change(result); 78 | // Insert the element into the node16 instead 79 | result.add_child(ref, c, child); 80 | } 81 | } 82 | 83 | @Override public void remove_child(ChildPtr ref, byte c) { 84 | assert(refcount <= 1); 85 | 86 | int idx; 87 | for (idx = 0; idx < this.num_children; idx++) { 88 | if (c == keys[idx]) break; 89 | } 90 | if (idx == this.num_children) return; 91 | 92 | assert(children[idx] instanceof Leaf); 93 | children[idx].decrement_refcount(); 94 | 95 | // Shift to fill the hole 96 | System.arraycopy(this.keys, idx + 1, this.keys, idx, this.num_children - idx - 1); 97 | System.arraycopy(this.children, idx + 1, this.children, idx, this.num_children - idx - 1); 98 | this.num_children--; 99 | 100 | // Remove nodes with only a single child 101 | if (num_children == 1) { 102 | Node child = children[0]; 103 | if (!(child instanceof Leaf)) { 104 | if (((ArtNode)child).refcount > 1) { 105 | child = child.n_clone(); 106 | } 107 | ArtNode an_child = (ArtNode)child; 108 | // Concatenate the prefixes 109 | int prefix = partial_len; 110 | if (prefix < MAX_PREFIX_LEN) { 111 | partial[prefix] = keys[0]; 112 | prefix++; 113 | } 114 | if (prefix < MAX_PREFIX_LEN) { 115 | int sub_prefix = Math.min(an_child.partial_len, MAX_PREFIX_LEN - prefix); 116 | System.arraycopy(an_child.partial, 0, partial, prefix, sub_prefix); 117 | prefix += sub_prefix; 118 | } 119 | 120 | // Store the prefix in the child 121 | System.arraycopy(partial, 0, an_child.partial, 0, Math.min(prefix, MAX_PREFIX_LEN)); 122 | an_child.partial_len += partial_len + 1; 123 | } 124 | ref.change(child); 125 | } 126 | } 127 | 128 | @Override public boolean exhausted(int i) { 129 | return i >= num_children; 130 | } 131 | 132 | @Override public int nextChildAtOrAfter(int i) { 133 | return i; 134 | } 135 | 136 | @Override public Node childAt(int i) { 137 | return children[i]; 138 | } 139 | 140 | @Override public int decrement_refcount() { 141 | if (--this.refcount <= 0) { 142 | int freed = 0; 143 | for (int i = 0; i < this.num_children; i++) { 144 | freed += children[i].decrement_refcount(); 145 | } 146 | count--; 147 | // delete this; 148 | return freed + 128; 149 | // object size (8) + refcount (4) + 150 | // num_children int (4) + partial_len int (4) + 151 | // pointer to partial array (8) + partial array size (8+4+1*MAX_PREFIX_LEN) 152 | // pointer to key array (8) + key array size (8+4+1*4) + 153 | // pointer to children array (8) + children array size (8+4+8*4) + 154 | // padding (4) 155 | } 156 | return 0; 157 | } 158 | 159 | byte[] keys = new byte[4]; 160 | Node[] children = new Node[4]; 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtNode48.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class ArtNode48 extends ArtNode { 4 | public static int count; 5 | 6 | public ArtNode48() { 7 | super(); 8 | count++; 9 | } 10 | 11 | public ArtNode48(final ArtNode48 other) { 12 | super(other); 13 | System.arraycopy(other.keys, 0, keys, 0, 256); 14 | // Copy the children. We have to look at all elements of `children` 15 | // rather than just the first num_children elements because `children` 16 | // may not be contiguous due to deletion 17 | for (int i = 0; i < 48; i++) { 18 | children[i] = other.children[i]; 19 | if (children[i] != null) { 20 | children[i].refcount++; 21 | } 22 | } 23 | count++; 24 | } 25 | 26 | public ArtNode48(final ArtNode16 other) { 27 | this(); 28 | // ArtNode 29 | this.num_children = other.num_children; 30 | this.partial_len = other.partial_len; 31 | System.arraycopy(other.partial, 0, this.partial, 0, 32 | Math.min(MAX_PREFIX_LEN, this.partial_len)); 33 | 34 | // ArtNode48 from ArtNode16 35 | for (int i = 0; i < this.num_children; i++) { 36 | keys[to_uint(other.keys[i])] = (byte)(i + 1); 37 | children[i] = other.children[i]; 38 | children[i].refcount++; 39 | } 40 | } 41 | 42 | public ArtNode48(final ArtNode256 other) { 43 | this(); 44 | assert(other.num_children <= 48); 45 | // ArtNode 46 | this.num_children = other.num_children; 47 | this.partial_len = other.partial_len; 48 | System.arraycopy(other.partial, 0, this.partial, 0, 49 | Math.min(MAX_PREFIX_LEN, this.partial_len)); 50 | 51 | // ArtNode48 from ArtNode256 52 | int pos = 0; 53 | for (int i = 0; i < 256; i++) { 54 | if (other.children[i] != null) { 55 | keys[i] = (byte)(pos + 1); 56 | children[pos] = other.children[i]; 57 | children[pos].refcount++; 58 | pos++; 59 | } 60 | } 61 | } 62 | 63 | @Override public Node n_clone() { 64 | return new ArtNode48(this); 65 | } 66 | 67 | @Override public ChildPtr find_child(byte c) { 68 | int idx = to_uint(keys[to_uint(c)]); 69 | if (idx != 0) return new ArrayChildPtr(children, idx - 1); 70 | return null; 71 | } 72 | 73 | @Override public Leaf minimum() { 74 | int idx = 0; 75 | while (keys[idx] == 0) idx++; 76 | Node child = children[to_uint(keys[idx]) - 1]; 77 | return Node.minimum(child); 78 | } 79 | 80 | @Override public void add_child(ChildPtr ref, byte c, Node child) { 81 | assert(refcount <= 1); 82 | 83 | if (this.num_children < 48) { 84 | // Have to do a linear scan because deletion may create holes in 85 | // children array 86 | int pos = 0; 87 | while (children[pos] != null) pos++; 88 | 89 | this.children[pos] = child; 90 | child.refcount++; 91 | this.keys[to_uint(c)] = (byte)(pos + 1); 92 | this.num_children++; 93 | } else { 94 | // Copy the node48 into a new node256 95 | ArtNode256 result = new ArtNode256(this); 96 | // Update the parent pointer to the node256 97 | ref.change(result); 98 | // Insert the element into the node256 instead 99 | result.add_child(ref, c, child); 100 | } 101 | } 102 | 103 | @Override public void remove_child(ChildPtr ref, byte c) { 104 | assert(refcount <= 1); 105 | 106 | // Delete the child, leaving a hole in children. We can't shift children 107 | // because that would require decrementing many elements of keys 108 | int pos = to_uint(keys[to_uint(c)]); 109 | keys[to_uint(c)] = 0; 110 | children[pos - 1].decrement_refcount(); 111 | children[pos - 1] = null; 112 | num_children--; 113 | 114 | if (num_children == 12) { 115 | ArtNode16 result = new ArtNode16(this); 116 | ref.change(result); 117 | } 118 | } 119 | 120 | @Override public boolean exhausted(int c) { 121 | for (int i = c; i < 256; i++) { 122 | if (keys[i] != 0) { 123 | return false; 124 | } 125 | } 126 | return true; 127 | } 128 | 129 | @Override public int nextChildAtOrAfter(int c) { 130 | int pos = c; 131 | for (; pos < 256; pos++) { 132 | if (keys[pos] != 0) { 133 | break; 134 | } 135 | } 136 | return pos; 137 | } 138 | 139 | @Override public Node childAt(int c) { 140 | return children[to_uint(keys[c]) - 1]; 141 | } 142 | 143 | @Override public int decrement_refcount() { 144 | if (--this.refcount <= 0) { 145 | int freed = 0; 146 | for (int i = 0; i < this.num_children; i++) { 147 | if (children[i] != null) { 148 | freed += children[i].decrement_refcount(); 149 | } 150 | } 151 | count--; 152 | // delete this; 153 | return freed + 728; 154 | // object size (8) + refcount (4) + 155 | // num_children int (4) + partial_len int (4) + 156 | // pointer to partial array (8) + partial array size (8+4+1*MAX_PREFIX_LEN) 157 | // pointer to key array (8) + key array size (8+4+1*256) + 158 | // pointer to children array (8) + children array size (8+4+8*48) 159 | } 160 | return 0; 161 | } 162 | 163 | byte[] keys = new byte[256]; 164 | Node[] children = new Node[48]; 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ArtTree.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | import java.io.Serializable; 4 | import java.util.Iterator; 5 | 6 | import scala.Tuple2; 7 | 8 | public class ArtTree extends ChildPtr implements Serializable { 9 | public ArtTree() { } 10 | 11 | public ArtTree(final ArtTree other) { 12 | root = other.root; 13 | num_elements = other.num_elements; 14 | } 15 | 16 | public ArtTree snapshot() { 17 | ArtTree b = new ArtTree(); 18 | if (root != null) { 19 | b.root = Node.n_clone(root); 20 | b.root.refcount++; 21 | } 22 | b.num_elements = num_elements; 23 | return b; 24 | } 25 | 26 | @Override Node get() { 27 | return root; 28 | } 29 | 30 | @Override void set(Node n) { 31 | root = n; 32 | } 33 | 34 | public Object search(final byte[] key) { 35 | Node n = root; 36 | int prefix_len, depth = 0; 37 | while (n != null) { 38 | if (n instanceof Leaf) { 39 | Leaf l = (Leaf)n; 40 | // Check if the expanded path matches 41 | if (l.matches(key)) { 42 | return l.value; 43 | } else { 44 | return null; 45 | } 46 | } else { 47 | ArtNode an = (ArtNode)(n); 48 | 49 | // Bail if the prefix does not match 50 | if (an.partial_len > 0) { 51 | prefix_len = an.check_prefix(key, depth); 52 | if (prefix_len != Math.min(Node.MAX_PREFIX_LEN, an.partial_len)) { 53 | return null; 54 | } 55 | depth += an.partial_len; 56 | } 57 | 58 | if (depth >= key.length) return null; 59 | 60 | // Recursively search 61 | ChildPtr child = an.find_child(key[depth]); 62 | n = (child != null) ? child.get() : null; 63 | depth++; 64 | } 65 | } 66 | return null; 67 | } 68 | 69 | public void insert(final byte[] key, Object value) throws UnsupportedOperationException { 70 | if (Node.insert(root, this, key, value, 0, false)) num_elements++; 71 | } 72 | 73 | public void delete(final byte[] key) { 74 | if (root != null) { 75 | boolean child_is_leaf = root instanceof Leaf; 76 | boolean do_delete = root.delete(this, key, 0, false); 77 | if (do_delete) { 78 | num_elements--; 79 | if (child_is_leaf) { 80 | // The leaf to delete is the root, so we must remove it 81 | root = null; 82 | } 83 | } 84 | } 85 | } 86 | 87 | public Iterator> iterator() { 88 | return new ArtIterator(root); 89 | } 90 | 91 | public Iterator> prefixIterator(final byte[] prefix) { 92 | // Find the root node for the prefix 93 | Node n = root; 94 | int prefix_len, depth = 0; 95 | while (n != null) { 96 | if (n instanceof Leaf) { 97 | Leaf l = (Leaf)n; 98 | // Check if the expanded path matches 99 | if (l.prefix_matches(prefix)) { 100 | return new ArtIterator(l); 101 | } else { 102 | return new ArtIterator(null); 103 | } 104 | } else { 105 | if (depth == prefix.length) { 106 | // If we have reached appropriate depth, return the iterator 107 | if (n.minimum().prefix_matches(prefix)) { 108 | return new ArtIterator(n); 109 | } else { 110 | return new ArtIterator(null); 111 | } 112 | } else { 113 | ArtNode an = (ArtNode)(n); 114 | 115 | // Bail if the prefix does not match 116 | if (an.partial_len > 0) { 117 | prefix_len = an.prefix_mismatch(prefix, depth); 118 | if (prefix_len == 0) { 119 | // No match, return empty 120 | return new ArtIterator(null); 121 | } else if (depth + prefix_len == prefix.length) { 122 | // Prefix match, return iterator 123 | return new ArtIterator(n); 124 | } else { 125 | // Full match, go deeper 126 | depth += an.partial_len; 127 | } 128 | } 129 | 130 | // Recursively search 131 | ChildPtr child = an.find_child(prefix[depth]); 132 | n = (child != null) ? child.get() : null; 133 | depth++; 134 | } 135 | } 136 | } 137 | return new ArtIterator(null); 138 | } 139 | 140 | public long size() { 141 | return num_elements; 142 | } 143 | 144 | public int destroy() { 145 | if (root != null) { 146 | int result = root.decrement_refcount(); 147 | root = null; 148 | return result; 149 | } else { 150 | return 0; 151 | } 152 | } 153 | 154 | Node root = null; 155 | long num_elements = 0; 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/ChildPtr.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | abstract class ChildPtr { 4 | abstract Node get(); 5 | abstract void set(Node n); 6 | 7 | void change(Node n) { 8 | // First increment the refcount of the new node, in case it would 9 | // otherwise have been deleted by the decrement of the old node 10 | n.refcount++; 11 | if (get() != null) { 12 | get().decrement_refcount(); 13 | } 14 | set(n); 15 | } 16 | 17 | void change_no_decrement(Node n) { 18 | n.refcount++; 19 | set(n); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/IterCallback.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | public interface IterCallback { 4 | void apply(final byte[] key, Object value); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/Leaf.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | class Leaf extends Node { 4 | public static int count; 5 | 6 | public Leaf(final byte[] key, Object value) { 7 | super(); 8 | this.key = key; 9 | this.value = value; 10 | count++; 11 | } 12 | 13 | public Leaf(final Leaf other) { 14 | super(other); 15 | this.key = other.key; 16 | this.value = other.value; 17 | count++; 18 | } 19 | 20 | @Override public Node n_clone() { 21 | return new Leaf(this); 22 | } 23 | 24 | public boolean matches(final byte[] key) { 25 | if (this.key.length != key.length) return false; 26 | for (int i = 0; i < key.length; i++) { 27 | if (this.key[i] != key[i]) { 28 | return false; 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | public boolean prefix_matches(final byte[] prefix) { 35 | if (this.key.length < prefix.length) return false; 36 | for (int i = 0; i < prefix.length; i++) { 37 | if (this.key[i] != prefix[i]) { 38 | return false; 39 | } 40 | } 41 | return true; 42 | } 43 | 44 | @Override public Leaf minimum() { 45 | return this; 46 | } 47 | 48 | public int longest_common_prefix(Leaf other, int depth) { 49 | int max_cmp = Math.min(key.length, other.key.length) - depth; 50 | int idx; 51 | for (idx = 0; idx < max_cmp; idx++) { 52 | if (key[depth + idx] != other.key[depth + idx]) { 53 | return idx; 54 | } 55 | } 56 | return idx; 57 | } 58 | 59 | @Override public boolean insert(ChildPtr ref, final byte[] key, Object value, 60 | int depth, boolean force_clone) throws UnsupportedOperationException { 61 | boolean clone = force_clone || this.refcount > 1; 62 | if (matches(key)) { 63 | if (clone) { 64 | // Updating an existing value, but need to create a new leaf to 65 | // reflect the change 66 | ref.change(new Leaf(key, value)); 67 | } else { 68 | // Updating an existing value, and safe to make the change in 69 | // place 70 | this.value = value; 71 | } 72 | return false; 73 | } else { 74 | // New value 75 | 76 | // Create a new leaf 77 | Leaf l2 = new Leaf(key, value); 78 | 79 | // Determine longest prefix 80 | int longest_prefix = longest_common_prefix(l2, depth); 81 | if (depth + longest_prefix >= this.key.length || 82 | depth + longest_prefix >= key.length) { 83 | throw new UnsupportedOperationException("keys cannot be prefixes of other keys"); 84 | } 85 | 86 | // Split the current leaf into a node4 87 | ArtNode4 result = new ArtNode4(); 88 | result.partial_len = longest_prefix; 89 | Node ref_old = ref.get(); 90 | ref.change_no_decrement(result); 91 | 92 | System.arraycopy(key, depth, 93 | result.partial, 0, 94 | Math.min(Node.MAX_PREFIX_LEN, longest_prefix)); 95 | // Add the leafs to the new node4 96 | result.add_child(ref, this.key[depth + longest_prefix], this); 97 | result.add_child(ref, l2.key[depth + longest_prefix], l2); 98 | 99 | ref_old.decrement_refcount(); 100 | 101 | // TODO: avoid the increment to self immediately followed by decrement 102 | 103 | return true; 104 | } 105 | } 106 | 107 | @Override public boolean delete(ChildPtr ref, final byte[] key, int depth, 108 | boolean force_clone) { 109 | return matches(key); 110 | } 111 | 112 | @Override public boolean exhausted(int i) { 113 | return i > 0; 114 | } 115 | 116 | @Override public int decrement_refcount() { 117 | if (--this.refcount <= 0) { 118 | count--; 119 | // delete this; 120 | // Don't delete the actual key or value because they may be used 121 | // elsewhere 122 | return 32; 123 | // object size (8) + refcount (4) + pointer to key array (8) + 124 | // pointer to value (8) + padding (4) 125 | } 126 | return 0; 127 | } 128 | 129 | Object value; 130 | final byte[] key; 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/ankurdave/part/Node.java: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part; 2 | 3 | import java.io.Serializable; 4 | 5 | abstract class Node implements Serializable { 6 | static final int MAX_PREFIX_LEN = 8; 7 | 8 | public Node() { 9 | refcount = 0; 10 | } 11 | 12 | public Node(final Node other) { 13 | refcount = 0; 14 | } 15 | 16 | public abstract Node n_clone(); 17 | public static Node n_clone(Node n) { 18 | if (n == null) return null; 19 | else return n.n_clone(); 20 | } 21 | 22 | public abstract Leaf minimum(); 23 | public static Leaf minimum(Node n) { 24 | if (n == null) return null; 25 | else return n.minimum(); 26 | } 27 | 28 | public abstract boolean insert(ChildPtr ref, final byte[] key, Object value, int depth, 29 | boolean force_clone) throws UnsupportedOperationException; 30 | public static boolean insert(Node n, ChildPtr ref, final byte[] key, Object value, int depth, 31 | boolean force_clone) { 32 | // If we are at a NULL node, inject a leaf 33 | if (n == null) { 34 | ref.change(new Leaf(key, value)); 35 | return true; 36 | } else { 37 | return n.insert(ref, key, value, depth, force_clone); 38 | } 39 | } 40 | 41 | public abstract boolean delete(ChildPtr ref, final byte[] key, int depth, 42 | boolean force_clone); 43 | 44 | public abstract int decrement_refcount(); 45 | 46 | public abstract boolean exhausted(int i); 47 | public static boolean exhausted(Node n, int i) { 48 | if (n == null) return true; 49 | else return n.exhausted(i); 50 | } 51 | 52 | static int to_uint(byte b) { 53 | return ((int)b) & 0xFF; 54 | } 55 | 56 | int refcount; 57 | } 58 | -------------------------------------------------------------------------------- /src/test/scala/com/ankurdave/part/PartSuite.scala: -------------------------------------------------------------------------------- 1 | package com.ankurdave.part 2 | 3 | import scala.util.Random 4 | import scala.collection.mutable.WrappedArray 5 | import scala.collection.JavaConversions._ 6 | 7 | import org.scalatest.FunSuite 8 | 9 | class PartSuite extends FunSuite { 10 | 11 | val max_n = 10000; 12 | val max_key_len = 10; 13 | val r = new Random 14 | 15 | // For sorting Seq[Seq[Byte]] lexicographically and bitwise 16 | import scala.math.Ordering.Implicits._ 17 | implicit val byteOrdering = Ordering.by[Byte, Int](b => Node.to_uint(b)) 18 | 19 | // For sorting the output of ArtTree#iterator 20 | def toSortable[V](iter: Iterator[(Array[Byte], Object)]): Seq[(Seq[Byte], V)] = 21 | iter.map { case (k, v) => (k.toSeq, v.asInstanceOf[V]) }.toSeq 22 | 23 | /** Generates random null-terminated keys. */ 24 | def key_list(): Seq[Seq[Byte]] = { 25 | val n = r.nextInt(max_n) 26 | Seq.fill(n) { 27 | val key_len = r.nextInt(max_key_len) 28 | Seq.fill(key_len) { (r.nextInt(255) + 1).toByte } :+ 0.toByte 29 | } 30 | } 31 | 32 | test("create, destroy") { 33 | val t = new ArtTree 34 | assert(t.size == 0) 35 | t.destroy() 36 | assert(t.size == 0) 37 | } 38 | 39 | test("insert, search") { 40 | val t = new ArtTree 41 | val keys = key_list().toSet.toSeq 42 | val holdout = 10 43 | for (i <- 0 until keys.size - holdout) { 44 | val k = keys(i).toArray 45 | assert(t.search(k) == null) 46 | t.insert(k, i) 47 | assert(t.search(k) == i) 48 | } 49 | assert(t.size == keys.size - holdout); 50 | for (i <- keys.size - holdout until keys.size) { 51 | assert(t.search(keys(i).toArray) == null) 52 | } 53 | } 54 | 55 | test("insert, delete") { 56 | val t = new ArtTree 57 | val keys = key_list().toSet.toSeq 58 | for (i <- 0 until keys.size) { 59 | val k = keys(i).toArray 60 | assert(t.search(k) == null) 61 | t.insert(k, i) 62 | assert(t.search(k) == i) 63 | } 64 | assert(t.size == keys.size); 65 | for (i <- 0 until keys.size) { 66 | val k = keys(i).toArray 67 | assert(t.search(k) == i) 68 | t.delete(k) 69 | assert(t.search(k) == null) 70 | } 71 | assert(t.size == 0); 72 | } 73 | 74 | test("iterator") { 75 | val t = new ArtTree 76 | val keys = key_list().toSet.toSeq 77 | for (i <- 0 until keys.size) { 78 | t.insert(keys(i).toArray, i) 79 | } 80 | assert(toSortable[Int](t.iterator) == keys.zipWithIndex.sorted) 81 | } 82 | 83 | test("prefix iterator") { 84 | val t = new ArtTree 85 | val keys = key_list().toSet.toSeq 86 | for (i <- 0 until keys.size) { 87 | t.insert(keys(i).toArray, i) 88 | } 89 | // Whole tree 90 | assert(toSortable[Int](t.prefixIterator(Array.empty[Byte])) == keys.zipWithIndex.sorted) 91 | // Each key 92 | for (i <- 0 until keys.size) { 93 | assert(toSortable[Int](t.prefixIterator(keys(i).toArray)) == Seq((keys(i), i))) 94 | } 95 | // Random prefixes 96 | for (trial <- 0 until 100) { 97 | val prefix_len = r.nextInt(max_key_len) 98 | val prefix = Seq.fill(prefix_len) { (r.nextInt(255) + 1).toByte } 99 | assert(toSortable[Int](t.prefixIterator(prefix.toArray)) == 100 | keys.zipWithIndex.sorted.filter { case (k, i) => k.startsWith(prefix) }) 101 | } 102 | } 103 | 104 | test("snapshot") { 105 | val a = new ArtTree 106 | val aKeys = key_list().toSet.toSeq 107 | for (k <- aKeys) { 108 | a.insert(k.toArray, 1) 109 | } 110 | 111 | val b = a.snapshot() 112 | assert(toSortable[Int](b.iterator) == toSortable[Int](a.iterator)) 113 | 114 | val bKeys = key_list().toSet.toSeq 115 | for (k <- bKeys) { 116 | b.insert(k.toArray, 2) 117 | } 118 | def checkB() { 119 | var i = 0 120 | for (k <- aKeys ++ bKeys) { 121 | val bVal = b.search(k.toArray).asInstanceOf[Int] 122 | assert(bVal == 1 || bVal == 2) 123 | i += 1 124 | } 125 | } 126 | checkB() 127 | 128 | val c = b.snapshot() 129 | for (k <- aKeys) { 130 | c.delete(k.toArray) 131 | } 132 | def checkC() { 133 | assert(toSortable[Int](c.iterator) == 134 | (bKeys.toSet -- aKeys.toSet).toSeq.map(x => (x, 2)).sorted) 135 | } 136 | checkB(); checkC() 137 | 138 | val d = c.snapshot() 139 | for (k <- bKeys) { 140 | d.delete(k.toArray) 141 | } 142 | def checkD() { 143 | assert(d.size == 0) 144 | assert(d.iterator.toSeq == Seq.empty) 145 | } 146 | checkB(); checkC(); checkD() 147 | } 148 | 149 | test("keys cannot be prefixes of other keys") { 150 | val t = new ArtTree 151 | val a = Array[Byte]() 152 | val b = Array[Byte](0) 153 | val c = Array[Byte](0, 0) 154 | 155 | t.insert(a, 1) 156 | assert(t.search(a) == 1) 157 | assert(t.search(b) == null) 158 | 159 | intercept[UnsupportedOperationException] { 160 | t.insert(b, 1) 161 | } 162 | assert(t.search(a) == 1) 163 | assert(t.search(b) == null) 164 | 165 | t.delete(a) 166 | t.insert(c, 1) 167 | assert(t.search(a) == null) 168 | assert(t.search(b) == null) 169 | assert(t.search(c) == 1) 170 | 171 | intercept[UnsupportedOperationException] { 172 | t.insert(a, 1) 173 | } 174 | intercept[UnsupportedOperationException] { 175 | t.insert(b, 1) 176 | } 177 | assert(t.search(a) == null) 178 | assert(t.search(b) == null) 179 | assert(t.search(c) == 1) 180 | } 181 | 182 | } 183 | --------------------------------------------------------------------------------