From 261b9975e2d673cc3f41cb3b3635f6fd2bce878d Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Mon, 23 Mar 2026 16:54:57 +0200 Subject: [PATCH 1/7] Add block-based list implementation with benchmarks Introduces BlockedList (copy-on-write) and BlockedLostCopy (write-direct) as proposed in #634. Includes JMH benchmarks comparing both implementations against scala.List across prepend, uncons, foldLeft, and foreach. --- .../bench/BlockedListBenchmark.scala | 177 ++++++++++++++++++ .../scala/cats/collections/BlockedList.scala | 88 +++++++++ .../cats/collections/BlockedListCopy.scala | 89 +++++++++ 3 files changed, 354 insertions(+) create mode 100644 bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala create mode 100644 core/src/main/scala/cats/collections/BlockedList.scala create mode 100644 core/src/main/scala/cats/collections/BlockedListCopy.scala diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala new file mode 100644 index 00000000..d39e23d7 --- /dev/null +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -0,0 +1,177 @@ +package cats.collections.bench + +import cats.collections.{BlockedList, BlockedListCopy} +import org.openjdk.jmh.annotations.* + +import java.util.concurrent.TimeUnit + +@State(Scope.Thread) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +class BlockedListBenchmark { + + /** Block size under test. Stored per-node in both implementations. */ + @Param(Array("4", "8", "16", "32", "64")) + var blockSize: Int = _ + + var preparedList: BlockedList[Int] = _ + var preparedListCopy: BlockedListCopy[Int] = _ + var preparedScalaList: List[Int] = _ + + /** Number of elements used in all benchmarks. */ + final val ListSize = 10000 + + @Setup(Level.Trial) + def setup(): Unit = { + preparedList = BlockedList[Int](List.range(1, 10000))(blockSize) + preparedListCopy = BlockedListCopy[Int](List.range(1, 10000))(blockSize) + preparedScalaList = List.range(1, 10000) + + } + + // ////////////////////////////////copy on write /////////////////////////////// + @Benchmark + def copyOnWritePrepend(): BlockedList[Int] = { + var list = BlockedList.empty[Int](blockSize) + var i = 1 + while (i <= ListSize) { + list = list.prepend(i) + i += 1 + } + list + } + + @Benchmark + def copyOnWriteUncons(): Unit = { + var result = preparedList.uncons + while (result.isDefined) { + result = result.get._2.uncons + } + } + + @Benchmark + def copyOnWriteForEach(): Long = { + var sum = 0L + preparedList.forEach((a: Int) => sum += a) + sum + } + + @Benchmark + def copyOnWriteFoldLeft(): Long = { + preparedList.foldLeft(0L)((acc, elem) => acc + elem) + + } + + // ////////////////////////////////////////////////////////////////////////////////////////////// + // //////////////////////// no copy //////////////////////// + @Benchmark + def noCopyPrepend(): BlockedListCopy[Int] = { + var list = BlockedListCopy.empty[Int](blockSize) + var i = 1 + while (i <= ListSize) { + list = list.prepend(i) + i += 1 + } + list + } + + @Benchmark + def noCopyWriteUncons(): Unit = { + var result = preparedListCopy.uncons + while (result.isDefined) { + result = result.get._2.uncons + } + } + + @Benchmark + def noCopyWriteForEach(): Long = { + var sum = 0L + preparedListCopy.forEach((a: Int) => sum += a) + sum + } + + @Benchmark + def noCopyFoldLeft(): Long = { + preparedListCopy.foldLeft(0L)((acc, elem: Int) => acc + elem) + } + // ///////////////////////////////////////////////////////////// + + // //////////////////////////// Naive List ////////////////////// + + @Benchmark + def scalaListPrepend(): List[Int] = { + var list = List.empty[Int] + var i = 1 + while (i <= ListSize) { + list = i :: list + i += 1 + } + list + } + + @Benchmark + def scalaListUncons(): Unit = { + var list = preparedScalaList + while (list.nonEmpty) { + list = list.tail + } + } + + @Benchmark + def scalaListFoldLeft(): Long = { + preparedScalaList.foldLeft(0L)((acc, a) => acc + a) + } + + @Benchmark + def scalaListForeach(): Long = { + var sum = 0L + preparedScalaList.foreach(a => sum += a) + sum + } + +//I +// @Benchmark +// def copyPrepend(): BlockedList[Int] = { +// // This runs in a loop; you want to measure the cost of a single prepend +// // Usually you'd have a baseline and a measured operation +// var list = BlockedList.empty[Int] +// for (i <- 1 to 1000) { +// list = list.prepend(i)(blockSize) +// } +// list +// } +// +// @Benchmark +// def copyUncons(): Unit = { +// // Build a large list first, then uncons all elements +// var list = BlockedList[Int](List.range(1, 10000))(blockSize) +// while (list.uncons(blockSize).isDefined) { +// list = list.uncons(blockSize).get._2 +// } + // } + +// +// @Benchmark +// def scalaListPrepend1000(): List[Int] = { +// var list = List.empty[Int] +// var i = 0 +// while (i < 1000) { +// list = i :: list +// i += 1 +// } +// list +// } +// +// // Compare with Scala's Vector prepend +// @Benchmark +// def scalaVectorPrepend1000(): Vector[Int] = { +// var vec = Vector.empty[Int] +// var i = 0 +// while (i < 1000) { +// vec = i +: vec +// i += 1 +// } +// vec +// } + +} \ No newline at end of file diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala new file mode 100644 index 00000000..845da1b2 --- /dev/null +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -0,0 +1,88 @@ +package cats.collections + +trait BlockedList[+T]{ + def uncons[A >: T]: Option[(A, BlockedList[A])] + def prepend[A >: T](a: A): BlockedList[A] + def forEach[U](f: T => U): Unit + def foldLeft[B](start: B)(f: (B, T) => B): B + def isEmpty: Boolean +} + +object BlockedList { + + + def apply[A](elements: A*)(BlockSize: Int): BlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def apply[A](elements: List[A])(BlockSize: Int): BlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def empty[A](BlockSize: Int): BlockedList[A] = Empty(BlockSize) + + + private case class Empty(BlockSize: Int) extends BlockedList[Nothing] { + + override def uncons[A >: Nothing]: Option[(A, BlockedList[A])] = None + + override def prepend[A >: Nothing](a: A): BlockedList[A] = { + val arrayBlock = new Array[Any](BlockSize) + val offset = BlockSize - 1 + arrayBlock(offset) = a + Impl(offset, arrayBlock, this, BlockSize) + } + + override def isEmpty: Boolean = true + + override def forEach[U](f: Nothing => U): Unit = () + + override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + } + + private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) extends BlockedList[T] { + @inline + override def uncons[A >: T]: Option[(A, BlockedList[A])] = { + val next = if (offset + 1 < BlockSize) { + Impl(offset + 1, block, tail, BlockSize) + } else { + tail + } + Some( (block(offset).asInstanceOf[A], next) ) + } + + override def prepend[A >: T](a: A): BlockedList[A] = { + val newArray = new Array[Any](BlockSize) + if (offset > 0) { + System.arraycopy(block, offset, newArray, offset, BlockSize - offset) + val nextOffset = offset - 1 + newArray(nextOffset) = a + Impl(nextOffset, newArray, tail, BlockSize) + } else { + val newOffset = BlockSize - 1 + newArray(newOffset) = a + Impl(newOffset, newArray, this, BlockSize) + } + } + + override def isEmpty: Boolean = false + + override def forEach[U](f: T => U): Unit = { + var i = offset + while (i < BlockSize){ + f(block(i).asInstanceOf[T]) + i += 1 + } + tail.forEach(f) + } + + override def foldLeft[B](start: B)(f: (B, T) => B): B = { + var acc = start + var i = offset + while(i < BlockSize){ + acc = f(acc, block(i).asInstanceOf[T]) + i += 1 + } + tail.foldLeft(acc)(f) + } + + } +} diff --git a/core/src/main/scala/cats/collections/BlockedListCopy.scala b/core/src/main/scala/cats/collections/BlockedListCopy.scala new file mode 100644 index 00000000..ab12e0a7 --- /dev/null +++ b/core/src/main/scala/cats/collections/BlockedListCopy.scala @@ -0,0 +1,89 @@ +package cats.collections + +trait BlockedListCopy [+T]{ + def uncons[A >: T]: Option[(A, BlockedListCopy[A])] + def prepend[A >: T](a: A): BlockedListCopy[A] + def forEach[U](f: T => U): Unit + def foldLeft[B](start: B)(f: (B, T) => B): B + def isEmpty: Boolean + } + + object BlockedListCopy { + + + def apply[A](elements: A*)(BlockSize: Int): BlockedListCopy[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def apply[A](elements: List[A])(BlockSize: Int): BlockedListCopy[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def empty[A](BlockSize: Int): BlockedListCopy[A] = Empty(BlockSize) + + + + private case class Empty(BlockSize: Int) extends BlockedListCopy[Nothing] { + + override def uncons[A >: Nothing]: Option[(A, BlockedListCopy[A])] = None + + override def prepend[A >: Nothing](a: A): BlockedListCopy[A] = { + val arrayBlock = new Array[Any](BlockSize) + val offset = BlockSize - 1 + arrayBlock(offset) = a + Impl(offset, arrayBlock, this, BlockSize) + } + + override def isEmpty: Boolean = true + + override def forEach[U](f: Nothing => U): Unit = () + + override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + } + + private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedListCopy[T], BlockSize: Int) extends BlockedListCopy[T] { + @inline + override def uncons[A >: T]: Option[(A, BlockedListCopy[A])] = { + + val next = if (offset + 1 < BlockSize) { + Impl(offset + 1, block, tail, BlockSize) + } else { + tail + } + Some((block(offset).asInstanceOf[A], next)) + } + + override def prepend[A >: T](a: A): BlockedListCopy[A] = { + if (offset > 0) { + val nextOffset = offset - 1 + block(nextOffset) = a + Impl(nextOffset, block, tail, BlockSize) + } else { + val newBlockedArray = new Array[Any](BlockSize) + val newOffset = BlockSize - 1 + newBlockedArray(newOffset) = a + Impl(newOffset, newBlockedArray, this, BlockSize) + } + } + + override def isEmpty: Boolean = false + + override def forEach[U](f: T => U): Unit = { + var i = offset + while (i < BlockSize){ + f(block(i).asInstanceOf[T]) + i += 1 + } + tail.forEach(f) + } + + override def foldLeft[B](start: B)(f: (B, T) => B): B = { + var acc = start + var i = offset + while(i < BlockSize){ + acc = f(acc, block(i).asInstanceOf[T]) + i += 1 + } + tail.foldLeft(acc)(f) + } + + } + } From 988d1154da5aa1a9cbd32398157e6e054d7a857e Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Wed, 25 Mar 2026 13:41:49 +0200 Subject: [PATCH 2/7] Fixed the typos, added benchmark details, and added missing license headers. Please let me know if anything else needs attention --- .../bench/BlockedListBenchmark.scala | 48 ++++++-- .../scala/cats/collections/BlockedList.scala | 22 ++++ .../cats/collections/BlockedListCopy.scala | 89 -------------- .../cats/collections/FastBlockedList.scala | 111 ++++++++++++++++++ 4 files changed, 168 insertions(+), 102 deletions(-) delete mode 100644 core/src/main/scala/cats/collections/BlockedListCopy.scala create mode 100644 core/src/main/scala/cats/collections/FastBlockedList.scala diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala index d39e23d7..f60f7b2a 100644 --- a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -1,6 +1,28 @@ +/* + * 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.bench -import cats.collections.{BlockedList, BlockedListCopy} +import cats.collections.{BlockedList, FastBlockedList} import org.openjdk.jmh.annotations.* import java.util.concurrent.TimeUnit @@ -14,8 +36,8 @@ class BlockedListBenchmark { @Param(Array("4", "8", "16", "32", "64")) var blockSize: Int = _ - var preparedList: BlockedList[Int] = _ - var preparedListCopy: BlockedListCopy[Int] = _ + var preparedBlockedList: BlockedList[Int] = _ + var preparedFastBlockedList: FastBlockedList[Int] = _ var preparedScalaList: List[Int] = _ /** Number of elements used in all benchmarks. */ @@ -23,8 +45,8 @@ class BlockedListBenchmark { @Setup(Level.Trial) def setup(): Unit = { - preparedList = BlockedList[Int](List.range(1, 10000))(blockSize) - preparedListCopy = BlockedListCopy[Int](List.range(1, 10000))(blockSize) + preparedBlockedList = BlockedList[Int](List.range(1, 10000))(blockSize) + preparedFastBlockedList = FastBlockedList[Int](List.range(1, 10000))(blockSize) preparedScalaList = List.range(1, 10000) } @@ -43,7 +65,7 @@ class BlockedListBenchmark { @Benchmark def copyOnWriteUncons(): Unit = { - var result = preparedList.uncons + var result = preparedBlockedList.uncons while (result.isDefined) { result = result.get._2.uncons } @@ -52,21 +74,21 @@ class BlockedListBenchmark { @Benchmark def copyOnWriteForEach(): Long = { var sum = 0L - preparedList.forEach((a: Int) => sum += a) + preparedBlockedList.forEach((a: Int) => sum += a) sum } @Benchmark def copyOnWriteFoldLeft(): Long = { - preparedList.foldLeft(0L)((acc, elem) => acc + elem) + preparedBlockedList.foldLeft(0L)((acc, elem) => acc + elem) } // ////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////// no copy //////////////////////// @Benchmark - def noCopyPrepend(): BlockedListCopy[Int] = { - var list = BlockedListCopy.empty[Int](blockSize) + def noCopyPrepend(): FastBlockedList[Int] = { + var list = FastBlockedList.empty[Int](blockSize) var i = 1 while (i <= ListSize) { list = list.prepend(i) @@ -77,7 +99,7 @@ class BlockedListBenchmark { @Benchmark def noCopyWriteUncons(): Unit = { - var result = preparedListCopy.uncons + var result = preparedFastBlockedList.uncons while (result.isDefined) { result = result.get._2.uncons } @@ -86,13 +108,13 @@ class BlockedListBenchmark { @Benchmark def noCopyWriteForEach(): Long = { var sum = 0L - preparedListCopy.forEach((a: Int) => sum += a) + preparedFastBlockedList.forEach((a: Int) => sum += a) sum } @Benchmark def noCopyFoldLeft(): Long = { - preparedListCopy.foldLeft(0L)((acc, elem: Int) => acc + elem) + preparedFastBlockedList.foldLeft(0L)((acc, elem: Int) => acc + elem) } // ///////////////////////////////////////////////////////////// diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index 845da1b2..ff5aaec6 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -1,3 +1,25 @@ +/* + * 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 trait BlockedList[+T]{ diff --git a/core/src/main/scala/cats/collections/BlockedListCopy.scala b/core/src/main/scala/cats/collections/BlockedListCopy.scala deleted file mode 100644 index ab12e0a7..00000000 --- a/core/src/main/scala/cats/collections/BlockedListCopy.scala +++ /dev/null @@ -1,89 +0,0 @@ -package cats.collections - -trait BlockedListCopy [+T]{ - def uncons[A >: T]: Option[(A, BlockedListCopy[A])] - def prepend[A >: T](a: A): BlockedListCopy[A] - def forEach[U](f: T => U): Unit - def foldLeft[B](start: B)(f: (B, T) => B): B - def isEmpty: Boolean - } - - object BlockedListCopy { - - - def apply[A](elements: A*)(BlockSize: Int): BlockedListCopy[A] = { - elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) - } - def apply[A](elements: List[A])(BlockSize: Int): BlockedListCopy[A] = { - elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) - } - def empty[A](BlockSize: Int): BlockedListCopy[A] = Empty(BlockSize) - - - - private case class Empty(BlockSize: Int) extends BlockedListCopy[Nothing] { - - override def uncons[A >: Nothing]: Option[(A, BlockedListCopy[A])] = None - - override def prepend[A >: Nothing](a: A): BlockedListCopy[A] = { - val arrayBlock = new Array[Any](BlockSize) - val offset = BlockSize - 1 - arrayBlock(offset) = a - Impl(offset, arrayBlock, this, BlockSize) - } - - override def isEmpty: Boolean = true - - override def forEach[U](f: Nothing => U): Unit = () - - override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start - } - - private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedListCopy[T], BlockSize: Int) extends BlockedListCopy[T] { - @inline - override def uncons[A >: T]: Option[(A, BlockedListCopy[A])] = { - - val next = if (offset + 1 < BlockSize) { - Impl(offset + 1, block, tail, BlockSize) - } else { - tail - } - Some((block(offset).asInstanceOf[A], next)) - } - - override def prepend[A >: T](a: A): BlockedListCopy[A] = { - if (offset > 0) { - val nextOffset = offset - 1 - block(nextOffset) = a - Impl(nextOffset, block, tail, BlockSize) - } else { - val newBlockedArray = new Array[Any](BlockSize) - val newOffset = BlockSize - 1 - newBlockedArray(newOffset) = a - Impl(newOffset, newBlockedArray, this, BlockSize) - } - } - - override def isEmpty: Boolean = false - - override def forEach[U](f: T => U): Unit = { - var i = offset - while (i < BlockSize){ - f(block(i).asInstanceOf[T]) - i += 1 - } - tail.forEach(f) - } - - override def foldLeft[B](start: B)(f: (B, T) => B): B = { - var acc = start - var i = offset - while(i < BlockSize){ - acc = f(acc, block(i).asInstanceOf[T]) - i += 1 - } - tail.foldLeft(acc)(f) - } - - } - } diff --git a/core/src/main/scala/cats/collections/FastBlockedList.scala b/core/src/main/scala/cats/collections/FastBlockedList.scala new file mode 100644 index 00000000..eb933e10 --- /dev/null +++ b/core/src/main/scala/cats/collections/FastBlockedList.scala @@ -0,0 +1,111 @@ +/* + * 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 + +trait FastBlockedList[+T]{ + def uncons[A >: T]: Option[(A, FastBlockedList[A])] + def prepend[A >: T](a: A): FastBlockedList[A] + def forEach[U](f: T => U): Unit + def foldLeft[B](start: B)(f: (B, T) => B): B + def isEmpty: Boolean + } + + object FastBlockedList { + + + def apply[A](elements: A*)(BlockSize: Int): FastBlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def apply[A](elements: List[A])(BlockSize: Int): FastBlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def empty[A](BlockSize: Int): FastBlockedList[A] = Empty(BlockSize) + + + + private case class Empty(BlockSize: Int) extends FastBlockedList[Nothing] { + + override def uncons[A >: Nothing]: Option[(A, FastBlockedList[A])] = None + + override def prepend[A >: Nothing](a: A): FastBlockedList[A] = { + val arrayBlock = new Array[Any](BlockSize) + val offset = BlockSize - 1 + arrayBlock(offset) = a + Impl(offset, arrayBlock, this, BlockSize) + } + + override def isEmpty: Boolean = true + + override def forEach[U](f: Nothing => U): Unit = () + + override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + } + + private case class Impl[+T](offset: Int, block: Array[Any], tail: FastBlockedList[T], BlockSize: Int) extends FastBlockedList[T] { + @inline + override def uncons[A >: T]: Option[(A, FastBlockedList[A])] = { + + val next = if (offset + 1 < BlockSize) { + Impl(offset + 1, block, tail, BlockSize) + } else { + tail + } + Some((block(offset).asInstanceOf[A], next)) + } + + override def prepend[A >: T](a: A): FastBlockedList[A] = { + if (offset > 0) { + val nextOffset = offset - 1 + block(nextOffset) = a + Impl(nextOffset, block, tail, BlockSize) + } else { + val newBlockedArray = new Array[Any](BlockSize) + val newOffset = BlockSize - 1 + newBlockedArray(newOffset) = a + Impl(newOffset, newBlockedArray, this, BlockSize) + } + } + + override def isEmpty: Boolean = false + + override def forEach[U](f: T => U): Unit = { + var i = offset + while (i < BlockSize){ + f(block(i).asInstanceOf[T]) + i += 1 + } + tail.forEach(f) + } + + override def foldLeft[B](start: B)(f: (B, T) => B): B = { + var acc = start + var i = offset + while(i < BlockSize){ + acc = f(acc, block(i).asInstanceOf[T]) + i += 1 + } + tail.foldLeft(acc)(f) + } + + } + } From 11130368aedb9f976201a9a74489136d607ef57e Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Wed, 25 Mar 2026 13:46:11 +0200 Subject: [PATCH 3/7] Fixing headers --- .../bench/BlockedListBenchmark.scala | 17 ++++++++--------- .../scala/cats/collections/BlockedList.scala | 17 ++++++++--------- .../cats/collections/FastBlockedList.scala | 17 ++++++++--------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala index f60f7b2a..33bf7f25 100644 --- a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -4,20 +4,19 @@ * 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: + * 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. + * 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.bench diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index ff5aaec6..709d6e74 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -4,20 +4,19 @@ * 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: + * 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. + * 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 diff --git a/core/src/main/scala/cats/collections/FastBlockedList.scala b/core/src/main/scala/cats/collections/FastBlockedList.scala index eb933e10..05bf7cd4 100644 --- a/core/src/main/scala/cats/collections/FastBlockedList.scala +++ b/core/src/main/scala/cats/collections/FastBlockedList.scala @@ -4,20 +4,19 @@ * 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: + * 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. + * 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 From 0d9ade162c1af589aabecb7dd0a2ff1b4064d735 Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Wed, 25 Mar 2026 13:49:42 +0200 Subject: [PATCH 4/7] Fixing format --- .../bench/BlockedListBenchmark.scala | 12 +- .../scala/cats/collections/BlockedList.scala | 17 ++- .../cats/collections/FastBlockedList.scala | 130 +++++++++--------- 3 files changed, 80 insertions(+), 79 deletions(-) diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala index 33bf7f25..e9782a92 100644 --- a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -31,7 +31,9 @@ import java.util.concurrent.TimeUnit @OutputTimeUnit(TimeUnit.NANOSECONDS) class BlockedListBenchmark { - /** Block size under test. Stored per-node in both implementations. */ + /** + * Block size under test. Stored per-node in both implementations. + */ @Param(Array("4", "8", "16", "32", "64")) var blockSize: Int = _ @@ -39,7 +41,9 @@ class BlockedListBenchmark { var preparedFastBlockedList: FastBlockedList[Int] = _ var preparedScalaList: List[Int] = _ - /** Number of elements used in all benchmarks. */ + /** + * Number of elements used in all benchmarks. + */ final val ListSize = 10000 @Setup(Level.Trial) @@ -169,7 +173,7 @@ class BlockedListBenchmark { // while (list.uncons(blockSize).isDefined) { // list = list.uncons(blockSize).get._2 // } - // } + // } // // @Benchmark @@ -195,4 +199,4 @@ class BlockedListBenchmark { // vec // } -} \ No newline at end of file +} diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index 709d6e74..9a8cd1ca 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -21,7 +21,7 @@ package cats.collections -trait BlockedList[+T]{ +trait BlockedList[+T] { def uncons[A >: T]: Option[(A, BlockedList[A])] def prepend[A >: T](a: A): BlockedList[A] def forEach[U](f: T => U): Unit @@ -31,7 +31,6 @@ trait BlockedList[+T]{ object BlockedList { - def apply[A](elements: A*)(BlockSize: Int): BlockedList[A] = { elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) } @@ -40,13 +39,12 @@ object BlockedList { } def empty[A](BlockSize: Int): BlockedList[A] = Empty(BlockSize) - private case class Empty(BlockSize: Int) extends BlockedList[Nothing] { override def uncons[A >: Nothing]: Option[(A, BlockedList[A])] = None override def prepend[A >: Nothing](a: A): BlockedList[A] = { - val arrayBlock = new Array[Any](BlockSize) + val arrayBlock = new Array[Any](BlockSize) val offset = BlockSize - 1 arrayBlock(offset) = a Impl(offset, arrayBlock, this, BlockSize) @@ -59,7 +57,8 @@ object BlockedList { override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start } - private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) extends BlockedList[T] { + private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) + extends BlockedList[T] { @inline override def uncons[A >: T]: Option[(A, BlockedList[A])] = { val next = if (offset + 1 < BlockSize) { @@ -67,7 +66,7 @@ object BlockedList { } else { tail } - Some( (block(offset).asInstanceOf[A], next) ) + Some((block(offset).asInstanceOf[A], next)) } override def prepend[A >: T](a: A): BlockedList[A] = { @@ -80,7 +79,7 @@ object BlockedList { } else { val newOffset = BlockSize - 1 newArray(newOffset) = a - Impl(newOffset, newArray, this, BlockSize) + Impl(newOffset, newArray, this, BlockSize) } } @@ -88,7 +87,7 @@ object BlockedList { override def forEach[U](f: T => U): Unit = { var i = offset - while (i < BlockSize){ + while (i < BlockSize) { f(block(i).asInstanceOf[T]) i += 1 } @@ -98,7 +97,7 @@ object BlockedList { override def foldLeft[B](start: B)(f: (B, T) => B): B = { var acc = start var i = offset - while(i < BlockSize){ + while (i < BlockSize) { acc = f(acc, block(i).asInstanceOf[T]) i += 1 } diff --git a/core/src/main/scala/cats/collections/FastBlockedList.scala b/core/src/main/scala/cats/collections/FastBlockedList.scala index 05bf7cd4..0c91f718 100644 --- a/core/src/main/scala/cats/collections/FastBlockedList.scala +++ b/core/src/main/scala/cats/collections/FastBlockedList.scala @@ -21,90 +21,88 @@ package cats.collections -trait FastBlockedList[+T]{ - def uncons[A >: T]: Option[(A, FastBlockedList[A])] - def prepend[A >: T](a: A): FastBlockedList[A] - def forEach[U](f: T => U): Unit - def foldLeft[B](start: B)(f: (B, T) => B): B - def isEmpty: Boolean +trait FastBlockedList[+T] { + def uncons[A >: T]: Option[(A, FastBlockedList[A])] + def prepend[A >: T](a: A): FastBlockedList[A] + def forEach[U](f: T => U): Unit + def foldLeft[B](start: B)(f: (B, T) => B): B + def isEmpty: Boolean +} + +object FastBlockedList { + + def apply[A](elements: A*)(BlockSize: Int): FastBlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) } + def apply[A](elements: List[A])(BlockSize: Int): FastBlockedList[A] = { + elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + } + def empty[A](BlockSize: Int): FastBlockedList[A] = Empty(BlockSize) - object FastBlockedList { + private case class Empty(BlockSize: Int) extends FastBlockedList[Nothing] { + override def uncons[A >: Nothing]: Option[(A, FastBlockedList[A])] = None - def apply[A](elements: A*)(BlockSize: Int): FastBlockedList[A] = { - elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) - } - def apply[A](elements: List[A])(BlockSize: Int): FastBlockedList[A] = { - elements.foldRight(empty[A](BlockSize))((elem, acc) => acc.prepend(elem)) + override def prepend[A >: Nothing](a: A): FastBlockedList[A] = { + val arrayBlock = new Array[Any](BlockSize) + val offset = BlockSize - 1 + arrayBlock(offset) = a + Impl(offset, arrayBlock, this, BlockSize) } - def empty[A](BlockSize: Int): FastBlockedList[A] = Empty(BlockSize) + override def isEmpty: Boolean = true + override def forEach[U](f: Nothing => U): Unit = () - private case class Empty(BlockSize: Int) extends FastBlockedList[Nothing] { + override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + } - override def uncons[A >: Nothing]: Option[(A, FastBlockedList[A])] = None + private case class Impl[+T](offset: Int, block: Array[Any], tail: FastBlockedList[T], BlockSize: Int) + extends FastBlockedList[T] { + @inline + override def uncons[A >: T]: Option[(A, FastBlockedList[A])] = { - override def prepend[A >: Nothing](a: A): FastBlockedList[A] = { - val arrayBlock = new Array[Any](BlockSize) - val offset = BlockSize - 1 - arrayBlock(offset) = a - Impl(offset, arrayBlock, this, BlockSize) + val next = if (offset + 1 < BlockSize) { + Impl(offset + 1, block, tail, BlockSize) + } else { + tail } - - override def isEmpty: Boolean = true - - override def forEach[U](f: Nothing => U): Unit = () - - override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + Some((block(offset).asInstanceOf[A], next)) } - private case class Impl[+T](offset: Int, block: Array[Any], tail: FastBlockedList[T], BlockSize: Int) extends FastBlockedList[T] { - @inline - override def uncons[A >: T]: Option[(A, FastBlockedList[A])] = { - - val next = if (offset + 1 < BlockSize) { - Impl(offset + 1, block, tail, BlockSize) - } else { - tail - } - Some((block(offset).asInstanceOf[A], next)) - } - - override def prepend[A >: T](a: A): FastBlockedList[A] = { - if (offset > 0) { - val nextOffset = offset - 1 - block(nextOffset) = a - Impl(nextOffset, block, tail, BlockSize) - } else { - val newBlockedArray = new Array[Any](BlockSize) - val newOffset = BlockSize - 1 - newBlockedArray(newOffset) = a - Impl(newOffset, newBlockedArray, this, BlockSize) - } + override def prepend[A >: T](a: A): FastBlockedList[A] = { + if (offset > 0) { + val nextOffset = offset - 1 + block(nextOffset) = a + Impl(nextOffset, block, tail, BlockSize) + } else { + val newBlockedArray = new Array[Any](BlockSize) + val newOffset = BlockSize - 1 + newBlockedArray(newOffset) = a + Impl(newOffset, newBlockedArray, this, BlockSize) } + } - override def isEmpty: Boolean = false + override def isEmpty: Boolean = false - override def forEach[U](f: T => U): Unit = { - var i = offset - while (i < BlockSize){ - f(block(i).asInstanceOf[T]) - i += 1 - } - tail.forEach(f) + override def forEach[U](f: T => U): Unit = { + var i = offset + while (i < BlockSize) { + f(block(i).asInstanceOf[T]) + i += 1 } + tail.forEach(f) + } - override def foldLeft[B](start: B)(f: (B, T) => B): B = { - var acc = start - var i = offset - while(i < BlockSize){ - acc = f(acc, block(i).asInstanceOf[T]) - i += 1 - } - tail.foldLeft(acc)(f) + override def foldLeft[B](start: B)(f: (B, T) => B): B = { + var acc = start + var i = offset + while (i < BlockSize) { + acc = f(acc, block(i).asInstanceOf[T]) + i += 1 } - + tail.foldLeft(acc)(f) } + } +} From 440066d76e3b91160790c349a8ec95609a96c44e Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Thu, 26 Mar 2026 13:08:51 +0200 Subject: [PATCH 5/7] Added map implementation and updated the benchmark results --- .../bench/BlockedListBenchmark.scala | 12 +++++++++++ .../scala/cats/collections/BlockedList.scala | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala index e9782a92..4d2ce658 100644 --- a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -154,6 +154,18 @@ class BlockedListBenchmark { sum } + // Map + + @Benchmark + def blockedListMap(): BlockedList[Int] = { + preparedBlockedList.map(_ + 1) + } + + @Benchmark + def scalaListMap(): List[Int] = { + preparedScalaList.map(_ + 1) + } + //I // @Benchmark // def copyPrepend(): BlockedList[Int] = { diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index 9a8cd1ca..f614c6ed 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -26,6 +26,8 @@ trait BlockedList[+T] { def prepend[A >: T](a: A): BlockedList[A] def forEach[U](f: T => U): Unit def foldLeft[B](start: B)(f: (B, T) => B): B + def map[B](f: T => B): BlockedList[B] + def map2expirement[B](f: T => B): BlockedList[B] def isEmpty: Boolean } @@ -55,6 +57,10 @@ object BlockedList { override def forEach[U](f: Nothing => U): Unit = () override def foldLeft[B](start: B)(f: (B, Nothing) => B): B = start + + override def map[B](f: Nothing => B): BlockedList[B] = this + + override def map2expirement[B](f: Nothing => B): BlockedList[B] = this } private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) @@ -104,5 +110,19 @@ object BlockedList { tail.foldLeft(acc)(f) } + override def map[B](f: T => B): BlockedList[B] = { + val arrayCopy = new Array[Any](BlockSize) + var i = offset + while (i < BlockSize) { + arrayCopy(i) = f(block(i).asInstanceOf[T]) + i += 1 + } + Impl(offset, arrayCopy, tail.map(f), BlockSize) + } + + def map2expirement[B](f: T => B): BlockedList[B] = { + this.foldLeft(empty[B](BlockSize))((acc, element) => acc.prepend(f(element))) + } + } } From c1fcf4495959e5a28840dc86fce2b67e4b13c4ec Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Sat, 28 Mar 2026 18:51:27 +0200 Subject: [PATCH 6/7] Refactoring into tail-recursive implementation and adding new benchmark result --- .../scala/cats/collections/BlockedList.scala | 99 +++++++++++++++---- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index f614c6ed..0bc407d1 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -21,13 +21,18 @@ package cats.collections -trait BlockedList[+T] { +import cats.Eval + +import scala.annotation.tailrec + +sealed trait BlockedList[+T] { def uncons[A >: T]: Option[(A, BlockedList[A])] def prepend[A >: T](a: A): BlockedList[A] def forEach[U](f: T => U): Unit def foldLeft[B](start: B)(f: (B, T) => B): B def map[B](f: T => B): BlockedList[B] def map2expirement[B](f: T => B): BlockedList[B] + def reverse: BlockedList[T] def isEmpty: Boolean } @@ -61,6 +66,8 @@ object BlockedList { override def map[B](f: Nothing => B): BlockedList[B] = this override def map2expirement[B](f: Nothing => B): BlockedList[B] = this + + override def reverse: BlockedList[Nothing] = this } private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) @@ -92,36 +99,90 @@ object BlockedList { override def isEmpty: Boolean = false override def forEach[U](f: T => U): Unit = { - var i = offset - while (i < BlockSize) { - f(block(i).asInstanceOf[T]) - i += 1 + @tailrec + def helper(acc: BlockedList[T]): Unit = { + acc match { + case Empty(BlockSize) => () + case Impl(offset, block, tail, BlockSize) => + var i = offset + while (i < BlockSize) { + f(block(i).asInstanceOf[T]) + i += 1 + } + helper(tail) + } } - tail.forEach(f) + helper(this) } override def foldLeft[B](start: B)(f: (B, T) => B): B = { - var acc = start - var i = offset - while (i < BlockSize) { - acc = f(acc, block(i).asInstanceOf[T]) - i += 1 + + @tailrec + def helper(finalAcc: B, remainList: BlockedList[T]): B = { + remainList match { + case Empty(BlockSize) => finalAcc + case Impl(offset, block, tail, BlockSize) => + var acc = finalAcc + var i = offset + while (i < BlockSize) { + acc = f(acc, block(i).asInstanceOf[T]) + i += 1 + } + helper(acc, tail) + } } - tail.foldLeft(acc)(f) + helper(start, this) } override def map[B](f: T => B): BlockedList[B] = { - val arrayCopy = new Array[Any](BlockSize) - var i = offset - while (i < BlockSize) { - arrayCopy(i) = f(block(i).asInstanceOf[T]) - i += 1 + + @tailrec + def helper(curent: BlockedList[T], acc: BlockedList[B]): BlockedList[B] = { + curent match { + case Empty(BlockSize) => acc + case Impl(offset, block, tail, BlockSize) => + val arrayCopy = new Array[Any](BlockSize) + var i = offset + while (i < BlockSize) { + arrayCopy(i) = f(block(i).asInstanceOf[T]) + i += 1 + } + helper(tail, Impl(offset, arrayCopy, acc, BlockSize)) + } } - Impl(offset, arrayCopy, tail.map(f), BlockSize) + + helper(this, Empty(BlockSize)).reverse + } def map2expirement[B](f: T => B): BlockedList[B] = { - this.foldLeft(empty[B](BlockSize))((acc, element) => acc.prepend(f(element))) + def helper(curent: BlockedList[T], acc: BlockedList[B] => BlockedList[B]): BlockedList[B] = { + curent match { + case Empty(BlockSize) => acc(empty(BlockSize)) + + case Impl(offset, block, tail, BlockSize) => + val arrayCopy = new Array[Any](BlockSize) + var i = offset + while (i < BlockSize) { + arrayCopy(i) = f(block(i).asInstanceOf[T]) + i += 1 + } + helper(tail, (rest: BlockedList[B]) => acc(Impl(offset, arrayCopy, rest, BlockSize))) + } + } + helper(this, identity) + } + + override def reverse: BlockedList[T] = { + @tailrec + def helper(curent: BlockedList[T], acc: BlockedList[T]): BlockedList[T] = { + curent match { + case Empty(BlockSize) => acc + case Impl(offset, block, tail, BlockSize) => + helper(tail, Impl(offset, block, acc, BlockSize)) + } + } + helper(this, Empty(BlockSize)) } } From 163dd9b6fba798c81c063284f69d785859a42b68 Mon Sep 17 00:00:00 2001 From: Zayd-R Date: Mon, 30 Mar 2026 16:44:51 +0200 Subject: [PATCH 7/7] Adding Tail and uncons implementation and benchmarks, refactor Map --- .../bench/BlockedListBenchmark.scala | 153 ++++++------------ .../scala/cats/collections/BlockedList.scala | 98 +++++++---- 2 files changed, 120 insertions(+), 131 deletions(-) diff --git a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala index 4d2ce658..9c8fe3f6 100644 --- a/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala +++ b/bench/src/main/scala/cats/collections/bench/BlockedListBenchmark.scala @@ -38,7 +38,7 @@ class BlockedListBenchmark { var blockSize: Int = _ var preparedBlockedList: BlockedList[Int] = _ - var preparedFastBlockedList: FastBlockedList[Int] = _ + // var preparedFastBlockedList: FastBlockedList[Int] = _ var preparedScalaList: List[Int] = _ /** @@ -49,12 +49,17 @@ class BlockedListBenchmark { @Setup(Level.Trial) def setup(): Unit = { preparedBlockedList = BlockedList[Int](List.range(1, 10000))(blockSize) - preparedFastBlockedList = FastBlockedList[Int](List.range(1, 10000))(blockSize) + // preparedFastBlockedList = FastBlockedList[Int](List.range(1, 10000))(blockSize) preparedScalaList = List.range(1, 10000) } - // ////////////////////////////////copy on write /////////////////////////////// + def listUncons[A](lst: List[A]): Option[(A, List[A])] = lst match { + case ::(head, next) => Some((head, next)) + case Nil => None + } + + // ----------------------------------------------- Prepend ----------------------------- @Benchmark def copyOnWritePrepend(): BlockedList[Int] = { var list = BlockedList.empty[Int](blockSize) @@ -66,6 +71,18 @@ class BlockedListBenchmark { list } + @Benchmark + def scalaListPrepend(): List[Int] = { + var list = List.empty[Int] + var i = 1 + while (i <= ListSize) { + list = i :: list + i += 1 + } + list + } + + // ---------------------------------------------- unCons ------------------------------------------------ @Benchmark def copyOnWriteUncons(): Unit = { var result = preparedBlockedList.uncons @@ -75,71 +92,53 @@ class BlockedListBenchmark { } @Benchmark - def copyOnWriteForEach(): Long = { - var sum = 0L - preparedBlockedList.forEach((a: Int) => sum += a) - sum + def scalaListUncons(): Unit = { + var result = listUncons(preparedScalaList) + while (result.isDefined) { + result = listUncons(result.get._2) + } } - @Benchmark - def copyOnWriteFoldLeft(): Long = { - preparedBlockedList.foldLeft(0L)((acc, elem) => acc + elem) - - } + // ---------------------------------------------- tail ------------------------------------------------ - // ////////////////////////////////////////////////////////////////////////////////////////////// - // //////////////////////// no copy //////////////////////// @Benchmark - def noCopyPrepend(): FastBlockedList[Int] = { - var list = FastBlockedList.empty[Int](blockSize) - var i = 1 - while (i <= ListSize) { - list = list.prepend(i) - i += 1 + def copyOnTail(): Unit = { + var result = preparedBlockedList.tailE + while (!result.isEmpty) { + result = result.tailE } - list } @Benchmark - def noCopyWriteUncons(): Unit = { - var result = preparedFastBlockedList.uncons - while (result.isDefined) { - result = result.get._2.uncons + def scalaListTail(): Unit = { + var result = preparedScalaList + while (result.nonEmpty) { + result = result.tail } } + // -------------------------- ForeEach ------------------------------- + @Benchmark - def noCopyWriteForEach(): Long = { + def copyOnWriteForEach(): Long = { var sum = 0L - preparedFastBlockedList.forEach((a: Int) => sum += a) + preparedBlockedList.forEach((a: Int) => sum += a) sum } @Benchmark - def noCopyFoldLeft(): Long = { - preparedFastBlockedList.foldLeft(0L)((acc, elem: Int) => acc + elem) + def scalaListForeach(): Long = { + var sum = 0L + preparedScalaList.foreach(a => sum += a) + sum } - // ///////////////////////////////////////////////////////////// - // //////////////////////////// Naive List ////////////////////// + // ----------------------------------- FoldLeft --------------------------------- @Benchmark - def scalaListPrepend(): List[Int] = { - var list = List.empty[Int] - var i = 1 - while (i <= ListSize) { - list = i :: list - i += 1 - } - list - } + def copyOnWriteFoldLeft(): Long = { + preparedBlockedList.foldLeft(0L)((acc, elem) => acc + elem) - @Benchmark - def scalaListUncons(): Unit = { - var list = preparedScalaList - while (list.nonEmpty) { - list = list.tail - } } @Benchmark @@ -147,68 +146,22 @@ class BlockedListBenchmark { preparedScalaList.foldLeft(0L)((acc, a) => acc + a) } - @Benchmark - def scalaListForeach(): Long = { - var sum = 0L - preparedScalaList.foreach(a => sum += a) - sum - } - - // Map + // ----------------------------------- Map --------------------------------- @Benchmark def blockedListMap(): BlockedList[Int] = { preparedBlockedList.map(_ + 1) } + @Benchmark + def blockedListMap2expirement(): BlockedList[Int] = { + preparedBlockedList.map2expirement(_ + 1) + } + @Benchmark def scalaListMap(): List[Int] = { preparedScalaList.map(_ + 1) } - -//I -// @Benchmark -// def copyPrepend(): BlockedList[Int] = { -// // This runs in a loop; you want to measure the cost of a single prepend -// // Usually you'd have a baseline and a measured operation -// var list = BlockedList.empty[Int] -// for (i <- 1 to 1000) { -// list = list.prepend(i)(blockSize) -// } -// list -// } -// -// @Benchmark -// def copyUncons(): Unit = { -// // Build a large list first, then uncons all elements -// var list = BlockedList[Int](List.range(1, 10000))(blockSize) -// while (list.uncons(blockSize).isDefined) { -// list = list.uncons(blockSize).get._2 -// } - // } - -// -// @Benchmark -// def scalaListPrepend1000(): List[Int] = { -// var list = List.empty[Int] -// var i = 0 -// while (i < 1000) { -// list = i :: list -// i += 1 -// } -// list -// } -// -// // Compare with Scala's Vector prepend -// @Benchmark -// def scalaVectorPrepend1000(): Vector[Int] = { -// var vec = Vector.empty[Int] -// var i = 0 -// while (i < 1000) { -// vec = i +: vec -// i += 1 -// } -// vec -// } - } + +// ----------------------------------- ---------------------------- --------------------------------- diff --git a/core/src/main/scala/cats/collections/BlockedList.scala b/core/src/main/scala/cats/collections/BlockedList.scala index 0bc407d1..e39e28cf 100644 --- a/core/src/main/scala/cats/collections/BlockedList.scala +++ b/core/src/main/scala/cats/collections/BlockedList.scala @@ -23,16 +23,18 @@ package cats.collections import cats.Eval +import java.util.NoSuchElementException import scala.annotation.tailrec sealed trait BlockedList[+T] { def uncons[A >: T]: Option[(A, BlockedList[A])] def prepend[A >: T](a: A): BlockedList[A] + def tailE: BlockedList[T] def forEach[U](f: T => U): Unit def foldLeft[B](start: B)(f: (B, T) => B): B def map[B](f: T => B): BlockedList[B] - def map2expirement[B](f: T => B): BlockedList[B] def reverse: BlockedList[T] + def map2expirement[B](f: T => B): BlockedList[B] def isEmpty: Boolean } @@ -46,7 +48,7 @@ object BlockedList { } def empty[A](BlockSize: Int): BlockedList[A] = Empty(BlockSize) - private case class Empty(BlockSize: Int) extends BlockedList[Nothing] { + final case class Empty(BlockSize: Int) extends BlockedList[Nothing] { override def uncons[A >: Nothing]: Option[(A, BlockedList[A])] = None @@ -68,9 +70,11 @@ object BlockedList { override def map2expirement[B](f: Nothing => B): BlockedList[B] = this override def reverse: BlockedList[Nothing] = this + + override def tailE: BlockedList[Nothing] = throw new NoSuchElementException() } - private case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) + final case class Impl[+T](offset: Int, block: Array[Any], tail: BlockedList[T], BlockSize: Int) extends BlockedList[T] { @inline override def uncons[A >: T]: Option[(A, BlockedList[A])] = { @@ -102,14 +106,15 @@ object BlockedList { @tailrec def helper(acc: BlockedList[T]): Unit = { acc match { - case Empty(BlockSize) => () - case Impl(offset, block, tail, BlockSize) => + case Impl(offset, block, tail, bs) => var i = offset - while (i < BlockSize) { + while (i < bs) { f(block(i).asInstanceOf[T]) i += 1 } helper(tail) + + case Empty(bs) => () } } helper(this) @@ -120,15 +125,17 @@ object BlockedList { @tailrec def helper(finalAcc: B, remainList: BlockedList[T]): B = { remainList match { - case Empty(BlockSize) => finalAcc - case Impl(offset, block, tail, BlockSize) => + + case Impl(offset, block, tail, bs) => var acc = finalAcc var i = offset - while (i < BlockSize) { + while (i < bs) { acc = f(acc, block(i).asInstanceOf[T]) i += 1 } helper(acc, tail) + + case Empty(bs) => finalAcc } } helper(start, this) @@ -139,15 +146,17 @@ object BlockedList { @tailrec def helper(curent: BlockedList[T], acc: BlockedList[B]): BlockedList[B] = { curent match { - case Empty(BlockSize) => acc - case Impl(offset, block, tail, BlockSize) => + + case Impl(offset, block, tail, bs) => val arrayCopy = new Array[Any](BlockSize) var i = offset - while (i < BlockSize) { + while (i < bs) { arrayCopy(i) = f(block(i).asInstanceOf[T]) i += 1 } - helper(tail, Impl(offset, arrayCopy, acc, BlockSize)) + helper(tail, Impl(offset, arrayCopy, acc, bs)) + + case Empty(bs) => acc } } @@ -155,35 +164,62 @@ object BlockedList { } - def map2expirement[B](f: T => B): BlockedList[B] = { - def helper(curent: BlockedList[T], acc: BlockedList[B] => BlockedList[B]): BlockedList[B] = { - curent match { - case Empty(BlockSize) => acc(empty(BlockSize)) - - case Impl(offset, block, tail, BlockSize) => - val arrayCopy = new Array[Any](BlockSize) - var i = offset - while (i < BlockSize) { - arrayCopy(i) = f(block(i).asInstanceOf[T]) - i += 1 - } - helper(tail, (rest: BlockedList[B]) => acc(Impl(offset, arrayCopy, rest, BlockSize))) - } + override def map2expirement[B](f: T => B): BlockedList[B] = { + def helper(blocks: List[(Int, Array[Any])], acc: BlockedList[T]): BlockedList[B] = acc match { + case Impl(offset, block, tail, bs) => + val arrayCopy = new Array[Any](bs) + var i = offset + while (i < bs) { + arrayCopy(i) = f(block(i).asInstanceOf[T]) + i += 1 + } + helper((offset, arrayCopy) :: blocks, tail) + + case Empty(bs) => + blocks.foldLeft(BlockedList.empty(bs)) { case (blockListAcc, (perNodeOffset, arrayBlock)) => + Impl(perNodeOffset, arrayBlock, blockListAcc, bs) + } } - helper(this, identity) + helper(Nil, this) } + // def map2expirement[B](f: T => B): BlockedList[B] = { + // def helper(curent: BlockedList[T], acc: BlockedList[B] => BlockedList[B]): BlockedList[B] = { + // curent match { + // case Impl(offset, block, tail, bs) => + // val arrayCopy = new Array[Any](BlockSize) + // var i = offset + // while(i < bs) { + // arrayCopy(i) = f( block(i).asInstanceOf[T] ) + // i += 1 + // } + // helper(tail, ( (rest: BlockedList[B]) => acc(Impl(offset, arrayCopy, rest, bs)) )) + // + // case Empty(bs) => acc(empty(bs)) + // } + // } + // helper(this, identity) + // } + override def reverse: BlockedList[T] = { @tailrec def helper(curent: BlockedList[T], acc: BlockedList[T]): BlockedList[T] = { curent match { - case Empty(BlockSize) => acc - case Impl(offset, block, tail, BlockSize) => - helper(tail, Impl(offset, block, acc, BlockSize)) + case Impl(offset, block, tail, bs) => + helper(tail, Impl(offset, block, acc, bs)) + + case Empty(bs) => acc } } helper(this, Empty(BlockSize)) } + override def tailE: BlockedList[T] = { + if (offset + 1 < BlockSize) { + Impl(offset + 1, block, tail, BlockSize) + } else { + tail + } + } } }