Skip to content

Commit

Permalink
Slice
Browse files Browse the repository at this point in the history
  • Loading branch information
skozlov committed Aug 3, 2024
1 parent 4afb935 commit 804c23b
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 2 deletions.
81 changes: 81 additions & 0 deletions src/main/scala/com/github/skozlov/commons/collection/Slice.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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, _]
}
5 changes: 5 additions & 0 deletions src/main/scala/com/github/skozlov/commons/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.github.skozlov

package object commons {
type AnyConstr[X] = Any
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.skozlov.algorithms
package com.github.skozlov

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
Expand Down
Original file line number Diff line number Diff line change
@@ -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)]] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}

0 comments on commit 804c23b

Please sign in to comment.