diff --git a/src/main/scala/com/github/skozlov/commons/collection/Slice.scala b/src/main/scala/com/github/skozlov/commons/collection/Slice.scala new file mode 100644 index 0000000..c97bc9b --- /dev/null +++ b/src/main/scala/com/github/skozlov/commons/collection/Slice.scala @@ -0,0 +1,81 @@ +package com.github.skozlov.commons.collection + +import com.github.skozlov.commons.collection.SliceOps.checkBounds + +import scala.collection.IndexedSeqView +import scala.collection.IndexedSeqView.SomeIndexedSeqOps + +trait SliceOps[A, Underlying <: SomeIndexedSeqOps[A], SubSlice <: SliceOps[A, Underlying, _]] + extends IndexedSeqView[A] { + this: SubSlice => + + import SliceOps._ + + def underlying: Underlying + def from: Int + def until: Int + + override val length: Int = until - from + + //noinspection ScalaWeakerAccess + @throws[IndexOutOfBoundsException] + def indexToUnderlying(i: Int): Int = { + if (i < 0 || i >= length) { + throw IndexOutOfBoundsException(s"$i is out of bounds (min 0, max ${length - 1})") + } + from + i + } + + override def apply(i: Int): A = underlying(indexToUnderlying(i)) + + protected def newSubSlice(underlying: Underlying, from: Int, until: Int): SubSlice + + override def slice(from: Int, until: Int): SubSlice = { + if (from == 0 && until == this.length) { + this + } + else { + checkBounds(this, from, until) + newSubSlice(underlying, this.from + from, this.from + until) + } + } + + override def take(n: Int): SubSlice = slice(0, math.min(math.max(n, 0), length)) + + override def drop(n: Int): SubSlice = slice(math.min(math.max(n, 0), length), length) + + override def takeRight(n: Int): SubSlice = drop(length - math.max(n, 0)) + + override def dropRight(n: Int): SubSlice = take(length - math.max(n, 0)) +} + +object SliceOps { + //noinspection ScalaWeakerAccess + @throws[IllegalArgumentException] + def checkBounds[A](underlying: SomeIndexedSeqOps[A], from: Int, until: Int): Unit = { + require(from >= 0, s"Negative `from`: $from") + require(from <= until, s"`from` ($from) is greater than `until` ($until)") + require(until <= underlying.length, s"`until` ($until) is greater than underlying length (${underlying.length})") + } +} + +type Slice[A] = SliceOps[A, _, _] + +object Slice { + class Impl[A]( + override val underlying: SomeIndexedSeqOps[A], + override val from: Int, + override val until: Int + ) extends SliceOps[A, SomeIndexedSeqOps[A], Impl[A]] { + + checkBounds(underlying, from, until) + + override protected def newSubSlice(underlying: SomeIndexedSeqOps[A], from: Int, until: Int): Impl[A] = { + Impl(underlying, from, until) + } + } + + def apply[A](underlying: SomeIndexedSeqOps[A])(from: Int = 0, until: Int = underlying.length): Slice[A] = { + Impl(underlying, from, until) + } +} diff --git a/src/main/scala/com/github/skozlov/commons/collection/WritableSlice.scala b/src/main/scala/com/github/skozlov/commons/collection/WritableSlice.scala new file mode 100644 index 0000000..7d5414c --- /dev/null +++ b/src/main/scala/com/github/skozlov/commons/collection/WritableSlice.scala @@ -0,0 +1,36 @@ +package com.github.skozlov.commons.collection + +import com.github.skozlov.commons.collection.SliceOps.checkBounds + +import scala.collection.{View, mutable} + +trait WritableSliceOps[A, Underlying <: SomeMutableIndexedSeqOps[A], SubSlice <: WritableSliceOps[A, Underlying, _]] + extends SliceOps[A, Underlying, SubSlice] + with mutable.IndexedSeqOps[A, View, View[A]] { + this: SubSlice => + + override def update(idx: Int, elem: A): Unit = { + underlying(indexToUnderlying(idx)) = elem + } +} + +type WritableSlice[A] = WritableSliceOps[A, _, _] + +object WritableSlice { + class Impl[A]( + override val underlying: SomeMutableIndexedSeqOps[A], + override val from: Int, + override val until: Int + ) extends WritableSliceOps[A, SomeMutableIndexedSeqOps[A], Impl[A]] { + + checkBounds(underlying, from, until) + + override protected def newSubSlice(underlying: SomeMutableIndexedSeqOps[A], from: Int, until: Int): Impl[A] = { + Impl(underlying, from, until) + } + } + + def apply[A](underlying: SomeMutableIndexedSeqOps[A])(from: Int = 0, until: Int = underlying.length): WritableSlice[A] = { + Impl(underlying, from, until) + } +} diff --git a/src/main/scala/com/github/skozlov/commons/collection/package.scala b/src/main/scala/com/github/skozlov/commons/collection/package.scala new file mode 100644 index 0000000..8c21d0c --- /dev/null +++ b/src/main/scala/com/github/skozlov/commons/collection/package.scala @@ -0,0 +1,9 @@ +package com.github.skozlov.commons + +import scala.collection.{SeqOps, mutable} + +package object collection { + type SomeSeqOps[+A] = SeqOps[A, AnyConstr, _] + + type SomeMutableIndexedSeqOps[A] = mutable.IndexedSeqOps[A, AnyConstr, _] +} diff --git a/src/main/scala/com/github/skozlov/commons/package.scala b/src/main/scala/com/github/skozlov/commons/package.scala new file mode 100644 index 0000000..34c3ee3 --- /dev/null +++ b/src/main/scala/com/github/skozlov/commons/package.scala @@ -0,0 +1,5 @@ +package com.github.skozlov + +package object commons { + type AnyConstr[X] = Any +} diff --git a/src/test/scala/com/github/skozlov/algorithms/Test.scala b/src/test/scala/com/github/skozlov/Test.scala similarity index 79% rename from src/test/scala/com/github/skozlov/algorithms/Test.scala rename to src/test/scala/com/github/skozlov/Test.scala index 7f46548..d0b2969 100644 --- a/src/test/scala/com/github/skozlov/algorithms/Test.scala +++ b/src/test/scala/com/github/skozlov/Test.scala @@ -1,4 +1,4 @@ -package com.github.skozlov.algorithms +package com.github.skozlov import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers diff --git a/src/test/scala/com/github/skozlov/algorithms/sort/SortTest.scala b/src/test/scala/com/github/skozlov/algorithms/sort/SortTest.scala index ae58146..4c0f1c1 100644 --- a/src/test/scala/com/github/skozlov/algorithms/sort/SortTest.scala +++ b/src/test/scala/com/github/skozlov/algorithms/sort/SortTest.scala @@ -1,6 +1,6 @@ package com.github.skozlov.algorithms.sort -import com.github.skozlov.algorithms.Test +import com.github.skozlov.Test class SortTest extends Test { private val cases: Seq[Seq[(Int, Int)]] = { diff --git a/src/test/scala/com/github/skozlov/commons/collection/SliceTest.scala b/src/test/scala/com/github/skozlov/commons/collection/SliceTest.scala new file mode 100644 index 0000000..c7f9092 --- /dev/null +++ b/src/test/scala/com/github/skozlov/commons/collection/SliceTest.scala @@ -0,0 +1,88 @@ +package com.github.skozlov.commons.collection + +import com.github.skozlov.Test + +class SliceTest extends Test { + test("constructor") { + val underlying = Array(1, 2, 3, 4, 5) + Slice(underlying)(from = 1, until = 4).toSeq shouldBe Seq(2, 3, 4) + Slice(underlying)(until = 2).toSeq shouldBe Seq(1, 2) + Slice(underlying)(from = 3).toSeq shouldBe Seq(4, 5) + Slice(underlying)(from = 1, until = 1).toSeq shouldBe Seq() + the[IllegalArgumentException] thrownBy Slice(underlying)(from = -1, until = 4) should have message "requirement failed: Negative `from`: -1" + the[IllegalArgumentException] thrownBy Slice(underlying)(from = 2, until = 1) should have message "requirement failed: `from` (2) is greater than `until` (1)" + the[IllegalArgumentException] thrownBy Slice(underlying)(from = 3, until = 6) should have message "requirement failed: `until` (6) is greater than underlying length (5)" + } + + test("changing underlying") { + val underlying = Array(1, 2, 3, 4, 5) + val slice = Slice(underlying)(from = 1, until = 4) + slice.toSeq shouldBe Seq(2, 3, 4) + for (i <- underlying.indices) { + underlying(i) = underlying(i) * 10 + } + slice.toSeq shouldBe Seq(20, 30, 40) + } + + test("length"){ + val underlying = Array(1, 2, 3, 4, 5) + Slice(underlying)(from = 1, until = 4).length shouldBe 3 + Slice(underlying)(from = 1, until = 2).length shouldBe 1 + Slice(underlying)(from = 1, until = 1).length shouldBe 0 + } + + test("apply(i: Int)"){ + val slice = Slice(underlying = Array(1, 2, 3, 4))(from = 1, until = 3) + slice(0) shouldBe 2 + slice(1) shouldBe 3 + the[IndexOutOfBoundsException] thrownBy slice(2) should have message "2 is out of bounds (min 0, max 1)" + the[IndexOutOfBoundsException] thrownBy slice(-1) should have message "-1 is out of bounds (min 0, max 1)" + } + + test("slice(from: Int, until: Int)") { + val underlying = Array(1, 2, 3, 4, 5, 6, 7) + val slice = Slice(underlying)(from = 1, until = 6) + slice.slice(from = 1, until = 4).toSeq shouldBe Seq(3, 4, 5) + slice.slice(from = 0, until = 5).toSeq shouldBe Seq(2, 3, 4, 5, 6) + slice.slice(from = 1, until = 1).toSeq shouldBe Seq() + the[IllegalArgumentException] thrownBy slice.slice(from = -1, until = 5) should have message "requirement failed: Negative `from`: -1" + the[IllegalArgumentException] thrownBy slice.slice(from = 1, until = 0) should have message "requirement failed: `from` (1) is greater than `until` (0)" + the[IllegalArgumentException] thrownBy slice.slice(from = 0, until = 6) should have message "requirement failed: `until` (6) is greater than underlying length (5)" + } + + test("take(n: Int)") { + val slice = Slice(underlying = Array(1, 2, 3, 4))(from = 1, until = 3) + (slice take 0).toSeq shouldBe Seq() + (slice take -1).toSeq shouldBe Seq() + (slice take 1).toSeq shouldBe Seq(2) + (slice take 2).toSeq shouldBe Seq(2, 3) + (slice take 3).toSeq shouldBe Seq(2, 3) + } + + test("drop(n: Int)") { + val slice = Slice(underlying = Array(1, 2, 3, 4))(from = 1, until = 3) + (slice drop 0).toSeq shouldBe Seq(2, 3) + (slice drop -1).toSeq shouldBe Seq(2, 3) + (slice drop 1).toSeq shouldBe Seq(3) + (slice drop 2).toSeq shouldBe Seq() + (slice drop 3).toSeq shouldBe Seq() + } + + test("takeRight(n: Int)") { + val slice = Slice(underlying = Array(1, 2, 3, 4))(from = 1, until = 3) + (slice takeRight 0).toSeq shouldBe Seq() + (slice takeRight -1).toSeq shouldBe Seq() + (slice takeRight 1).toSeq shouldBe Seq(3) + (slice takeRight 2).toSeq shouldBe Seq(2, 3) + (slice takeRight 3).toSeq shouldBe Seq(2, 3) + } + + test("dropRight(n: Int)") { + val slice = Slice(underlying = Array(1, 2, 3, 4))(from = 1, until = 3) + (slice dropRight 0).toSeq shouldBe Seq(2, 3) + (slice dropRight -1).toSeq shouldBe Seq(2, 3) + (slice dropRight 1).toSeq shouldBe Seq(2) + (slice dropRight 2).toSeq shouldBe Seq() + (slice dropRight 3).toSeq shouldBe Seq() + } +}