-
-
Notifications
You must be signed in to change notification settings - Fork 108
Foundations for BList #825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: l/faster-immutable-list-634
Are you sure you want to change the base?
Changes from all commits
116687a
f8b1a16
877d3d6
9172ebe
7224246
971943b
a1a5d9a
f9e6c8b
089d854
7888e92
e12726d
2a7df73
8b02e8a
bdf9d36
d9a0dee
69865f6
2c30668
dba9c9a
34a91f2
62c530e
d67a55c
f8a1d26
2e56cfb
d623b83
aefdb7b
db06cc4
ffde5b5
0bf49bd
f18b0e5
cb70945
ce76352
0c35b05
8659fea
7325bf8
62bee70
12d460b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,383 @@ | ||
| /* | ||
| * Copyright (c) 2015 Typelevel | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| * this software and associated documentation files (the "Software"), to deal in | ||
| * the Software without restriction, including without limitation the rights to | ||
| * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
| * the Software, and to permit persons to whom the Software is furnished to do so, | ||
| * subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
| * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
| * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
| * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
|
|
||
| package cats.collections | ||
|
|
||
| import scala.annotation.tailrec | ||
| import scala.annotation.unchecked.uncheckedVariance | ||
| import scala.util.hashing.MurmurHash3 | ||
|
|
||
| sealed abstract class BList[+A] { | ||
| def uncons: Option[(A, BList[A])] | ||
| def prepend[B >: A](a: B): BList.NonEmpty[B] | ||
| def headOption: Option[A] | ||
| def tailOption: Option[BList[A]] | ||
| def get(idx: Long): Option[A] | ||
| def getUnsafe(idx: Long): A | ||
| def lastOption: Option[A] | ||
| def size: Long | ||
| def map[B](fn: A => B): BList[B] | ||
| def foldLeft[B](init: B)(fn: (B, A) => B): B | ||
| def drop(n: Long): BList[A] | ||
| def concat[B >: A](l2: BList[B]): BList[B] | ||
| def toList: List[A] | ||
| def isEmpty: Boolean | ||
| def iterator: Iterator[A] | ||
|
|
||
| final def ::[B >: A](a: B): BList[B] = prepend(a) | ||
| final def ++[B >: A](l2: BList[B]): BList[B] = concat(l2) | ||
|
|
||
| // for development and testing | ||
| private[collections] def toStringInBlocks: String | ||
|
|
||
| override def toString: String = { | ||
| val strb = new java.lang.StringBuilder | ||
| strb.append("BList(") | ||
| @tailrec | ||
| def loop(first: Boolean, l: BList[A]): Unit = | ||
| l.uncons match { | ||
| case None => () | ||
| case Some((h, t)) => | ||
| if (!first) strb.append(", "): Unit | ||
| strb.append(h.toString) | ||
| loop(false, t) | ||
| } | ||
|
|
||
| loop(true, this) | ||
| strb.append(")") | ||
| strb.toString | ||
| } | ||
|
|
||
| def equals(other: Any): Boolean | ||
| def hashCode(): Int | ||
| } | ||
|
|
||
| object BList { | ||
| final private[collections] val BlockSize = 4 // test with different values | ||
|
|
||
| case object Empty extends BList[Nothing] { | ||
| def uncons = None | ||
| def prepend[B >: Nothing](a: B): BList.NonEmpty[B] = { | ||
| val ary = new Array[Any](BlockSize) | ||
| val offset = BlockSize - 1 | ||
| ary(offset) = a | ||
| Impl(offset, ary, Empty) | ||
| } | ||
| def headOption: None.type = None | ||
| def tailOption: None.type = None | ||
| def get(idx: Long): None.type = None | ||
| def getUnsafe(idx: Long): Nothing = throw new IndexOutOfBoundsException | ||
| def lastOption: None.type = None | ||
| def size: Long = 0 | ||
| def map[B](fn: Nothing => B): BList[B] = Empty | ||
| def foldLeft[B](acc: B)(fn: (B, Nothing) => B): B = acc | ||
| def drop(n: Long): BList[Nothing] = Empty | ||
| def concat[B](l2: BList[B]): BList[B] = l2 | ||
| override def toList: List[Nothing] = Nil | ||
| def isEmpty: Boolean = true | ||
| def iterator: Iterator[Nothing] = Iterator.empty | ||
| private[collections] def toStringInBlocks: String = "Empty" | ||
|
|
||
| override def equals(other: Any): Boolean = other match { | ||
| case _: Empty.type => true | ||
| case _ => false | ||
| } | ||
|
|
||
| override def hashCode(): Int = Empty.iterator.## | ||
|
|
||
| } | ||
| sealed abstract class NonEmpty[+A] extends BList[A] { | ||
| def head: A | ||
| def tail: BList[A] | ||
| def map[B](fn: A => B): BList.NonEmpty[B] | ||
| def concat[B >: A](l2: BList[B]): BList.NonEmpty[B] | ||
| def uncons: Some[(A, BList[A])] | ||
| def headOption: Some[A] | ||
| def tailOption: Some[BList[A]] | ||
| } | ||
|
|
||
| object NonEmpty { | ||
| def apply[A](h: A, t: BList[A]): NonEmpty[A] = | ||
| t.prepend(h) | ||
| def unapply[A](l: NonEmpty[A]): Some[(A, BList[A])] = | ||
| l.uncons | ||
| } | ||
|
|
||
| private object Impl { | ||
| def apply[A](offset: Int, block: Array[A], tailBList: BList[A]): Impl[A] = | ||
| new Impl(offset, block, tailBList) | ||
| def apply[A](offset: Int, block: Array[Any], tailBList: BList[A]): Impl[A] = | ||
| new Impl(offset, block.asInstanceOf[Array[A]], tailBList) | ||
| def unapply[A](l: Impl[A]): Some[(Int, Array[A], BList[A])] = | ||
| Some((l.offset, l.block, l.tailBList)) | ||
| } | ||
|
|
||
| private class Impl[+A](val offset: Int, val block: Array[A @uncheckedVariance], val tailBList: BList[A]) | ||
| extends NonEmpty[A] { | ||
|
|
||
| override def equals(other: Any): Boolean = | ||
| other match { | ||
| case o: Impl[_] => | ||
| this.iterator.sameElements(o.iterator.asInstanceOf[Iterator[A]]) | ||
| case _ => false | ||
| } | ||
|
|
||
| override def hashCode: Int = | ||
| MurmurHash3.orderedHash(this.iterator) | ||
|
|
||
| def uncons: Some[(A, BList[A])] = { | ||
| Some((block(offset), this.tail)) | ||
| } | ||
| def prepend[B >: A](a: B): BList.NonEmpty[B] = { | ||
| if (offset > 0) { | ||
| val ary = block.clone().asInstanceOf[Array[B]] | ||
| val nextOffset = offset - 1 | ||
| ary(nextOffset) = a | ||
| Impl(nextOffset, ary, tailBList) | ||
| } else { | ||
| val ary = new Array[Any](BlockSize) | ||
| val offset = BlockSize - 1 | ||
| ary(offset) = a | ||
| Impl(offset, ary, this) | ||
| } | ||
| } | ||
| def head: A = { | ||
| block(offset) | ||
| } | ||
| def tail: BList[A] = { | ||
| if (offset < BlockSize - 1) { | ||
| val ary = new Array[Any](BlockSize) | ||
| System.arraycopy(block, offset + 1, ary, offset + 1, BlockSize - (offset + 1)) | ||
| Impl(offset + 1, ary, tailBList) | ||
| } else { | ||
| tailBList | ||
| } | ||
| } | ||
| def headOption: Some[A] = { | ||
| Some(block(offset)) | ||
| } | ||
| def tailOption: Some[BList[A]] = { | ||
| Some(tail) | ||
| } | ||
| def get(idx: Long): Option[A] = { | ||
| if (idx < 0) { None } | ||
| else { | ||
| @tailrec | ||
| def go(idx: Long, l: BList[A]): Option[A] = { | ||
| l match { | ||
| case Empty => None | ||
| case impl: Impl[A] @unchecked => | ||
| if (idx < BlockSize - impl.offset) { | ||
| Some(impl.block(impl.offset + idx.toInt)) | ||
| } else { | ||
| go(idx - (BlockSize - impl.offset), impl.tailBList) | ||
| } | ||
| } | ||
| } | ||
| go(idx, this) | ||
| } | ||
| } | ||
| def getUnsafe(idx: Long): A = { | ||
| if (idx < 0) | ||
| throw new IndexOutOfBoundsException | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we are throwing a different kind of exception for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checked and it seems to throw the same index out of bounds exception for index too small or too large! |
||
|
|
||
| @tailrec | ||
| def go(idx: Long, l: BList[A]): A = { | ||
| l match { | ||
| case Empty => throw new IndexOutOfBoundsException | ||
| case impl: Impl[A] @unchecked => | ||
| if (idx < BlockSize - impl.offset) { | ||
| impl.block(impl.offset + idx.toInt) | ||
| } else { | ||
| go(idx - (BlockSize - impl.offset), impl.tailBList) | ||
| } | ||
| } | ||
| } | ||
| go(idx, this) | ||
| } | ||
| def lastOption: Some[A] = { | ||
| @tailrec | ||
| def go(self: Impl[A]): Some[A] = { | ||
| self.tailBList match { | ||
| case Empty => Some(self.block(BlockSize - 1)) | ||
| case next: Impl[A] @unchecked => go(next) | ||
| } | ||
| } | ||
| go(this) | ||
| } | ||
|
satorg marked this conversation as resolved.
|
||
| def size: Long = { | ||
| @tailrec | ||
| def loop(l: BList[A], acc: Long): Long = { | ||
| l match { | ||
| case Empty => acc | ||
| case impl: Impl[A] @unchecked => loop(impl.tailBList, acc + (BlockSize - impl.offset)) | ||
| } | ||
| } | ||
| loop(this, 0L) | ||
| } | ||
| def map[B](fn: A => B): BList.NonEmpty[B] = { | ||
| val ary = new Array[Any](BlockSize) | ||
| var i = offset | ||
| while (i < ary.length) { | ||
| ary(i) = fn(block(i)) | ||
| i += 1 | ||
| } | ||
| Impl(offset, ary, tailBList.map(fn)) | ||
| } | ||
| def foldLeft[B](acc: B)(fn: (B, A) => B): B = { | ||
| @tailrec | ||
| def loop(acc: B, l: BList[A]): B = | ||
| l match { | ||
| case Empty => acc | ||
| case impl: Impl[A] @unchecked => | ||
| var newacc = acc | ||
| var i = impl.offset | ||
| while (i < impl.block.length) { | ||
| newacc = fn(newacc, impl.block(i)) | ||
| i += 1 | ||
| } | ||
| loop(newacc, impl.tailBList) | ||
| } | ||
| loop(acc, this) | ||
| } | ||
| def drop(n: Long): BList[A] = { | ||
| @tailrec | ||
| def go(n: Long, l: BList[A]): BList[A] = { | ||
| if (n <= 0) { | ||
| l | ||
| } else { | ||
| l match { | ||
| case Empty => | ||
| Empty | ||
| case impl: Impl[A] @unchecked => | ||
| if (n >= BlockSize - impl.offset) { | ||
| go(n - (BlockSize - impl.offset), impl.tailBList) | ||
| } else { | ||
| val ary = new Array[Any](BlockSize) | ||
| val newOffset: Int = impl.offset + n.asInstanceOf[Int] // type conversion safe because 0<n<BlockSize | ||
| System.arraycopy(impl.block, newOffset, ary, newOffset, BlockSize - newOffset) | ||
| Impl(newOffset, ary, impl.tailBList) | ||
| } | ||
|
|
||
| } | ||
| } | ||
| } | ||
| go(n, this) | ||
| } | ||
| def concat[B >: A](l2: BList[B]): BList.NonEmpty[B] = { | ||
| // not tail rec | ||
| def go(self: Impl[A]): BList.NonEmpty[B] = { | ||
| self.tailBList match { | ||
| case Empty => Impl(self.offset, self.block.asInstanceOf[Array[B]], l2) | ||
| case next: Impl[A] @unchecked => Impl(self.offset, self.block.asInstanceOf[Array[B]], go(next)) | ||
| } | ||
| } | ||
|
|
||
| // for now, the only optimization is checking if either are empty | ||
| (this, l2) match { | ||
| case (_, Empty) => this | ||
| case (_, _) => go(this) | ||
| } | ||
| } | ||
|
|
||
| override def toList: List[A] = { | ||
| val builder = List.newBuilder[A] | ||
| @tailrec | ||
| def loop(l: BList[A]): List[A] = | ||
| l match { | ||
| case Empty => builder.result() | ||
| case impl: Impl[A] @unchecked => | ||
| // append valid things in the block to acc | ||
| for (i <- impl.offset until BlockSize) { | ||
| builder += impl.block(i) | ||
| } | ||
| loop(impl.tailBList) | ||
| } | ||
| loop(this) | ||
| } | ||
|
|
||
| def isEmpty: Boolean = false | ||
| def iterator: Iterator[A] = new BListIterator(this) | ||
|
|
||
| private[collections] def toStringInBlocks: String = { | ||
| val strb = new java.lang.StringBuilder | ||
| strb.append("BList(") | ||
| @tailrec | ||
| def loop(first: Boolean, l: BList[A]): Unit = { | ||
| if (!first) strb.append(", "): Unit | ||
| l match { | ||
| case Empty => strb.append("Empty"): Unit | ||
| case impl: Impl[A] @unchecked => | ||
| strb.append("Block(") | ||
| strb.append(impl.block(impl.offset).toString) | ||
| for (i <- impl.offset + 1 until BlockSize) { | ||
| strb.append(", ") | ||
| strb.append(impl.block(i).toString) | ||
| } | ||
| strb.append(")") | ||
| loop(false, impl.tailBList) | ||
| } | ||
| } | ||
| loop(true, this) | ||
| strb.append(")") | ||
| strb.toString | ||
| } | ||
| final private class BListIterator(var curNode: Impl[A @uncheckedVariance]) extends Iterator[A] { | ||
| var curOffset: Int = curNode.offset | ||
|
|
||
| def hasNext: Boolean = curOffset < BlockSize | ||
| def next(): A = | ||
| if (curOffset >= BlockSize) { | ||
| throw new NoSuchElementException | ||
| } else { | ||
| val next_elmt = curNode.block(curOffset) | ||
| curOffset += 1 | ||
| if (curOffset >= BlockSize) { | ||
| // try to advance to next block | ||
| curNode.tailBList match { | ||
| case impl: Impl[A] => | ||
| curOffset = impl.offset | ||
| curNode = impl | ||
| case Empty => // nothing, keep offset at blocksize | ||
| } | ||
| } | ||
| next_elmt | ||
| } | ||
| } | ||
| } | ||
|
|
||
| def fromList[A](l: List[A]): BList[A] = | ||
| fromListReverse(l.reverse) | ||
|
|
||
| def fromListReverse[A](l: List[A]): BList[A] = { | ||
| @tailrec | ||
| def go(l: List[A], acc: BList[A]): BList[A] = { | ||
| l match { | ||
| case Nil => acc | ||
| case h :: t => go(t, acc.prepend(h)) | ||
| } | ||
| } | ||
| go(l, empty) | ||
| } | ||
|
|
||
| def empty[A]: BList[A] = Empty | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.