added immutable persistent Map and Vector

This commit is contained in:
Jonas Boner 2009-04-03 23:54:46 +02:00
parent 0f6ca61cb2
commit 7abd91785c
2 changed files with 712 additions and 0 deletions

View file

@ -0,0 +1,361 @@
/**
Copyright (c) 2007-2008, Rich Hickey
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Clojure nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
**/
package se.scalablesolutions.akka.collection
/**
* A clean-room port of Rich Hickey's persistent hash trie implementation from
* Clojure (http://clojure.org). Originally presented as a mutable structure in
* a paper by Phil Bagwell.
*
* @author Daniel Spiewak
* @author Rich Hickey
*/
final class HashTrie[K, +V] private (root: Node[K, V]) extends Map[K, V] {
lazy val size = root.size
def this() = this(new EmptyNode[K])
def get(key: K) = root(key, key.hashCode)
override def +[A >: V](pair: (K, A)) = pair match {
case (k, v) => update(k, v)
}
def update[A >: V](key: K, value: A) = new HashTrie(root(0, key, key.hashCode) = value)
def -(key: K) = new HashTrie(root.remove(key, key.hashCode))
def elements = root.elements
def empty[A]: HashTrie[K, A] = new HashTrie(new EmptyNode[K])
def diagnose = root.toString
}
object HashTrie {
def apply[K, V](pairs: (K, V)*) = pairs.foldLeft(new HashTrie[K, V]) { _ + _ }
def unapplySeq[K, V](map: HashTrie[K, V]) = map.toSeq
}
// ============================================================================
// nodes
private[collection] sealed trait Node[K, +V] {
val size: Int
def apply(key: K, hash: Int): Option[V]
def update[A >: V](shift: Int, key: K, hash: Int, value: A): Node[K, A]
def remove(key: K, hash: Int): Node[K, V]
def elements: Iterator[(K, V)]
}
private[collection] class EmptyNode[K] extends Node[K, Nothing] {
val size = 0
def apply(key: K, hash: Int) = None
def update[V](shift: Int, key: K, hash: Int, value: V) = new LeafNode(key, hash, value)
def remove(key: K, hash: Int) = this
lazy val elements = new Iterator[(K, Nothing)] {
val hasNext = false
val next = null
}
}
private[collection] abstract class SingleNode[K, +V] extends Node[K, V] {
val hash: Int
}
private[collection] class LeafNode[K, +V](key: K, val hash: Int, value: V) extends SingleNode[K, V] {
val size = 1
def apply(key: K, hash: Int) = if (this.key == key) Some(value) else None
def update[A >: V](shift: Int, key: K, hash: Int, value: A) = {
if (this.key == key) {
if (this.value == value) this else new LeafNode(key, hash, value)
} else if (this.hash == hash) {
new CollisionNode(hash, this.key -> this.value, key -> value)
} else {
BitmappedNode(shift)(this, key, hash, value)
}
}
def remove(key: K, hash: Int) = if (this.key == key) new EmptyNode[K] else this
def elements = new Iterator[(K, V)] {
var hasNext = true
def next = {
hasNext = false
(key, value)
}
}
override def toString = "LeafNode(" + key + " -> " + value + ")"
}
private[collection] class CollisionNode[K, +V](val hash: Int, bucket: List[(K, V)]) extends SingleNode[K, V] {
lazy val size = bucket.length
def this(hash: Int, pairs: (K, V)*) = this(hash, pairs.toList)
def apply(key: K, hash: Int) = {
for {
(_, v) <- bucket find { case (k, _) => k == key }
} yield v
}
def update[A >: V](shift: Int, key: K, hash: Int, value: A): Node[K, A] = {
if (this.hash == hash) {
var found = false
val newBucket = for ((k, v) <- bucket) yield {
if (k == key) {
found = true
(key, value)
} else (k, v)
}
new CollisionNode(hash, if (found) newBucket else (key, value) :: bucket)
} else {
BitmappedNode(shift)(this, key, hash, value)
}
}
def remove(key: K, hash: Int) = {
val newBucket = bucket filter { case (k, _) => k != key }
if (newBucket.length == bucket.length) this else {
if (newBucket.length == 1) {
val (key, value) = newBucket.head
new LeafNode(key, hash, value)
} else new CollisionNode(hash, newBucket)
}
}
def elements = bucket.elements
override def toString = "CollisionNode(" + bucket.toString + ")"
}
private[collection] class BitmappedNode[K, +V](shift: Int)(table: Array[Node[K, V]], bits: Int) extends Node[K, V] {
lazy val size = {
val sizes = for {
n <- table
if n != null
} yield n.size
sizes.foldLeft(0) { _ + _ }
}
def apply(key: K, hash: Int) = {
val i = (hash >>> shift) & 0x01f
val mask = 1 << i
if ((bits & mask) == mask) table(i)(key, hash) else None
}
def update[A >: V](levelShift: Int, key: K, hash: Int, value: A): Node[K, A] = {
val i = (hash >>> shift) & 0x01f
val mask = 1 << i
if ((bits & mask) == mask) {
val node = (table(i)(shift + 5, key, hash) = value)
if (node == table(i)) this else {
val newTable = new Array[Node[K, A]](table.length)
Array.copy(table, 0, newTable, 0, table.length)
newTable(i) = node
new BitmappedNode(shift)(newTable, bits)
}
} else {
val newTable = new Array[Node[K, A]](Math.max(table.length, i + 1))
Array.copy(table, 0, newTable, 0, table.length)
newTable(i) = new LeafNode(key, hash, value)
val newBits = bits | mask
if (newBits == ~0) {
new FullNode(shift)(newTable)
} else {
new BitmappedNode(shift)(newTable, newBits)
}
}
}
def remove(key: K, hash: Int) = {
val i = (hash >>> shift) & 0x01f
val mask = 1 << i
if ((bits & mask) == mask) {
val node = table(i).remove(key, hash)
if (node == table(i)) {
this
} else if (node.isInstanceOf[EmptyNode[_]]) {
if (size == 1) new EmptyNode[K] else {
val adjustedBits = bits ^ mask
val log = Math.log(adjustedBits) / Math.log(2)
if (log.toInt.toDouble == log) { // last one
table(log.toInt)
} else {
val newTable = new Array[Node[K, V]](table.length)
Array.copy(table, 0, newTable, 0, newTable.length)
newTable(i) = null
new BitmappedNode(shift)(newTable, adjustedBits)
}
}
} else {
val newTable = new Array[Node[K, V]](table.length)
Array.copy(table, 0, newTable, 0, table.length)
newTable(i) = node
new BitmappedNode(shift)(newTable, bits)
}
} else this
}
def elements = {
table.foldLeft(emptyElements) { (it, e) =>
if (e == null) it else it ++ e.elements
}
}
override def toString = "BitmappedNode(" + size + "," + table.filter(_ != null).toList.toString + ")"
private lazy val emptyElements: Iterator[(K, V)] = new Iterator[(K, V)] {
val hasNext = false
val next = null
}
}
private[collection] object BitmappedNode {
def apply[K, V](shift: Int)(node: SingleNode[K, V], key: K, hash: Int, value: V) = {
val table = new Array[Node[K, V]](Math.max((hash >>> shift) & 0x01f, (node.hash >>> shift) & 0x01f) + 1)
val preBits = {
val i = (node.hash >>> shift) & 0x01f
table(i) = node
1 << i
}
val bits = {
val i = (hash >>> shift) & 0x01f
val mask = 1 << i
if ((preBits & mask) == mask) {
table(i) = (table(i)(shift + 5, key, hash) = value)
} else {
table(i) = new LeafNode(key, hash, value)
}
preBits | mask
}
new BitmappedNode(shift)(table, bits)
}
}
private[collection] class FullNode[K, +V](shift: Int)(table: Array[Node[K, V]]) extends Node[K, V] {
lazy val size = table.foldLeft(0) { _ + _.size }
def apply(key: K, hash: Int) = table((hash >>> shift) & 0x01f)(key, hash)
def update[A >: V](levelShift: Int, key: K, hash: Int, value: A) = {
val i = (hash >>> shift) & 0x01f
val node = (table(i)(shift + 5, key, hash) = value)
if (node == table(i)) this else {
val newTable = new Array[Node[K, A]](32)
Array.copy(table, 0, newTable, 0, 32)
newTable(i) = node
new FullNode(shift)(newTable)
}
}
def remove(key: K, hash: Int) = {
val i = (hash >>> shift) & 0x01f
val mask = 1 << i
val node = table(i).remove(key, hash)
if (node == table(i)) this else {
val newTable = new Array[Node[K, V]](32)
Array.copy(table, 0, newTable, 0, 32)
if (node.isInstanceOf[EmptyNode[_]]) {
newTable(i) = null
new BitmappedNode(shift)(newTable, ~mask)
} else {
newTable(i) = node
new FullNode(shift)(newTable)
}
}
}
def elements = table.foldLeft(emptyElements) { _ ++ _.elements }
override def toString = "FullNode(" + table.foldLeft("") { _.toString + ", " + _.toString } + ")"
private lazy val emptyElements: Iterator[(K, V)] = new Iterator[(K, V)] {
val hasNext = false
val next = null
}
}

View file

@ -0,0 +1,351 @@
/**
Copyright (c) 2007-2008, Rich Hickey
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Clojure nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
**/
package se.scalablesolutions.akka.collection
import Vector._
/**
* A straight port of Clojure's <code>PersistentVector</code> class.
*
* @author Daniel Spiewak
* @author Rich Hickey
*/
class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail: Array[AnyRef]) extends RandomAccessSeq[T] { outer =>
private val tailOff = length - tail.length
/*
* The design of this data structure inherantly requires heterogenous arrays.
* It is *possible* to design around this, but the result is comparatively
* quite inefficient. With respect to this fact, I have left the original
* (somewhat dynamically-typed) implementation in place.
*/
private[collection] def this() = this(0, 5, EmptyArray, EmptyArray)
def apply(i: Int): T = {
if (i >= 0 && i < length) {
if (i >= tailOff) {
tail(i & 0x01f).asInstanceOf[T]
} else {
var arr = root
var level = shift
while (level > 0) {
arr = arr((i >>> level) & 0x01f).asInstanceOf[Array[AnyRef]]
level -= 5
}
arr(i & 0x01f).asInstanceOf[T]
}
} else throw new IndexOutOfBoundsException(i.toString)
}
def update[A >: T](i: Int, obj: A): Vector[A] = {
if (i >= 0 && i < length) {
if (i >= tailOff) {
val newTail = new Array[AnyRef](tail.length)
Array.copy(tail, 0, newTail, 0, tail.length)
newTail(i & 0x01f) = obj.asInstanceOf[AnyRef]
new Vector[A](length, shift, root, newTail)
} else {
new Vector[A](length, shift, doAssoc(shift, root, i, obj), tail)
}
} else if (i == length) {
this + obj
} else throw new IndexOutOfBoundsException(i.toString)
}
private def doAssoc[A >: T](level: Int, arr: Array[AnyRef], i: Int, obj: A): Array[AnyRef] = {
val ret = new Array[AnyRef](arr.length)
Array.copy(arr, 0, ret, 0, arr.length)
if (level == 0) {
ret(i & 0x01f) = obj.asInstanceOf[AnyRef]
} else {
val subidx = (i >>> level) & 0x01f
ret(subidx) = doAssoc(level - 5, arr(subidx).asInstanceOf[Array[AnyRef]], i, obj)
}
ret
}
override def ++[A >: T](other: Iterable[A]) = other.foldLeft(this:Vector[A]) { _ + _ }
def +[A >: T](obj: A): Vector[A] = {
if (tail.length < 32) {
val newTail = new Array[AnyRef](tail.length + 1)
Array.copy(tail, 0, newTail, 0, tail.length)
newTail(tail.length) = obj.asInstanceOf[AnyRef]
new Vector[A](length + 1, shift, root, newTail)
} else {
var (newRoot, expansion) = pushTail(shift - 5, root, tail, null)
var newShift = shift
if (expansion != null) {
newRoot = array(newRoot, expansion)
newShift += 5
}
new Vector[A](length + 1, newShift, newRoot, array(obj.asInstanceOf[AnyRef]))
}
}
private def pushTail(level: Int, arr: Array[AnyRef], tailNode: Array[AnyRef], expansion: AnyRef): (Array[AnyRef], AnyRef) = {
val newChild = if (level == 0) tailNode else {
val (newChild, subExpansion) = pushTail(level - 5, arr(arr.length - 1).asInstanceOf[Array[AnyRef]], tailNode, expansion)
if (subExpansion == null) {
val ret = new Array[AnyRef](arr.length)
Array.copy(arr, 0, ret, 0, arr.length)
ret(arr.length - 1) = newChild
return (ret, null)
} else subExpansion
}
// expansion
if (arr.length == 32) {
(arr, array(newChild))
} else {
val ret = new Array[AnyRef](arr.length + 1)
Array.copy(arr, 0, ret, 0, arr.length)
ret(arr.length) = newChild
(ret, null)
}
}
/**
* Removes the <i>tail</i> element of this vector.
*/
def pop: Vector[T] = {
if (length == 0) {
throw new IllegalStateException("Can't pop empty vector")
} else if (length == 1) {
EmptyVector
} else if (tail.length > 1) {
val newTail = new Array[AnyRef](tail.length - 1)
Array.copy(tail, 0, newTail, 0, newTail.length)
new Vector[T](length - 1, shift, root, newTail)
} else {
var (newRoot, pTail) = popTail(shift - 5, root, null)
var newShift = shift
if (newRoot == null) {
newRoot = EmptyArray
}
if (shift > 5 && newRoot.length == 1) {
newRoot = newRoot(0).asInstanceOf[Array[AnyRef]]
newShift -= 5
}
new Vector[T](length - 1, newShift, newRoot, pTail.asInstanceOf[Array[AnyRef]])
}
}
private def popTail(shift: Int, arr: Array[AnyRef], pTail: AnyRef): (Array[AnyRef], AnyRef) = {
val newPTail = if (shift > 0) {
val (newChild, subPTail) = popTail(shift - 5, arr(arr.length - 1).asInstanceOf[Array[AnyRef]], pTail)
if (newChild != null) {
val ret = new Array[AnyRef](arr.length)
Array.copy(arr, 0, ret, 0, arr.length)
ret(arr.length - 1) = newChild
return (ret, subPTail)
}
subPTail
} else if (shift == 0) {
arr(arr.length - 1)
} else pTail
// contraction
if (arr.length == 1) {
(null, newPTail)
} else {
val ret = new Array[AnyRef](arr.length - 1)
Array.copy(arr, 0, ret, 0, ret.length)
(ret, newPTail)
}
}
override def filter(p: (T)=>Boolean) = {
var back = new Vector[T]
var i = 0
while (i < length) {
val e = apply(i)
if (p(e)) back += e
i += 1
}
back
}
override def flatMap[A](f: (T)=>Iterable[A]) = {
var back = new Vector[A]
var i = 0
while (i < length) {
f(apply(i)) foreach { back += _ }
i += 1
}
back
}
override def map[A](f: (T)=>A) = {
var back = new Vector[A]
var i = 0
while (i < length) {
back += f(apply(i))
i += 1
}
back
}
override def reverse: Vector[T] = new VectorProjection[T] {
override val length = outer.length
override def apply(i: Int) = outer.apply(length - i - 1)
}
override def subseq(from: Int, end: Int) = subVector(from, end)
def subVector(from: Int, end: Int): Vector[T] = {
if (from < 0) {
throw new IndexOutOfBoundsException(from.toString)
} else if (end >= length) {
throw new IndexOutOfBoundsException(end.toString)
} else if (end <= from) {
throw new IllegalArgumentException("Invalid range: " + from + ".." + end)
} else {
new VectorProjection[T] {
override val length = end - from
override def apply(i: Int) = outer.apply(i + from)
}
}
}
def zip[A](that: Vector[A]) = {
var back = new Vector[(T, A)]
var i = 0
val limit = Math.min(length, that.length)
while (i < limit) {
back += (apply(i), that(i))
i += 1
}
back
}
def zipWithIndex = {
var back = new Vector[(T, Int)]
var i = 0
while (i < length) {
back += (apply(i), i)
i += 1
}
back
}
override def equals(other: Any) = other match {
case vec:Vector[T] => {
var back = length == vec.length
var i = 0
while (i < length) {
back &&= apply(i) == vec.apply(i)
i += 1
}
back
}
case _ => false
}
override def hashCode = foldLeft(0) { _ ^ _.hashCode }
}
object Vector {
private[collection] val EmptyArray = new Array[AnyRef](0)
def apply[T](elems: T*) = elems.foldLeft(EmptyVector:Vector[T]) { _ + _ }
def unapplySeq[T](vec: Vector[T]): Option[Seq[T]] = Some(vec)
@inline
private[collection] def array(elems: AnyRef*) = {
val back = new Array[AnyRef](elems.length)
Array.copy(elems, 0, back, 0, back.length)
back
}
}
object EmptyVector extends Vector[Nothing]
private[collection] abstract class VectorProjection[+T] extends Vector[T] {
override val length: Int
override def apply(i: Int): T
override def +[A >: T](e: A) = innerCopy + e
override def update[A >: T](i: Int, e: A) = {
if (i < 0) {
throw new IndexOutOfBoundsException(i.toString)
} else if (i > length) {
throw new IndexOutOfBoundsException(i.toString)
} else innerCopy(i) = e
}
private lazy val innerCopy = foldLeft(EmptyVector:Vector[T]) { _ + _ }
}