Skip to content

Commit

Permalink
Slice
Browse files Browse the repository at this point in the history
  • Loading branch information
skozlov committed Aug 4, 2024
1 parent 4afb935 commit ca0cbba
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.skozlov.algorithms.sort

import scala.collection.mutable
import com.github.skozlov.commons.collection.{Slice, WritableSlice}

import scala.reflect.ClassTag

/** Sort which puts ordered elements into the output sequence and does not
Expand All @@ -12,29 +13,24 @@ trait FunctionalSort {
* sequence will not be modified.
*
* @param in
* input sequence which contains elements to sort
* input slice which contains elements to sort
* @param out
* output sequence to put sorted elements into
* output slice to put sorted elements into
* @throws IllegalArgumentException
* if the input and output sequences have different sizes
*/
def sortFunctionally[A: Ordering](
in: collection.IndexedSeq[A],
out: mutable.IndexedSeq[A],
): Unit
def sortFunctionally[A: Ordering](in: Slice[A], out: WritableSlice[A]): Unit

/** Sorts the input sequence putting elements into the new array. Input
* sequence will not be modified.
* @param in
* input sequence which contains elements to sort
* input slice which contains elements to sort
* @return
* array which contains sorted elements
*/
def sortFunctionally[A: Ordering: ClassTag](
in: collection.IndexedSeq[A]
): Array[A] = {
def sortFunctionally[A: Ordering: ClassTag](in: Slice[A]): Array[A] = {
val out = Array.ofDim[A](in.size)
sortFunctionally(in, out)
sortFunctionally(in, WritableSlice(out)())
out
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.skozlov.algorithms.sort

import scala.collection.mutable
import com.github.skozlov.commons.collection.WritableSlice

trait InPlaceSort {
def sortInPlace[A: Ordering](elements: mutable.IndexedSeq[A]): Unit
def sortInPlace[A: Ordering](elements: WritableSlice[A]): Unit
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.github.skozlov.algorithms.sort

import com.github.skozlov.commons.collection.{Slice, WritableSlice}

import scala.annotation.tailrec
import scala.collection.mutable
import scala.math.Ordered.orderingToOrdered

/** Simple sorting algorithm that is efficient for small sequences.
Expand All @@ -14,10 +15,7 @@ import scala.math.Ordered.orderingToOrdered
* [[https://en.wikipedia.org/wiki/Insertion_sort]]
*/
object InsertionSort extends InPlaceSort with FunctionalSort with StableSort {
private def sort[A: Ordering](
in: collection.IndexedSeq[A],
out: mutable.IndexedSeq[A],
): Unit = {
private def sort[A: Ordering](in: Slice[A], out: WritableSlice[A]): Unit = {
require(
in.size == out.size,
s"in and out have different sizes: ${in.size} and ${out.size}",
Expand Down Expand Up @@ -63,15 +61,13 @@ object InsertionSort extends InPlaceSort with FunctionalSort with StableSort {
}
}

override def sortInPlace[A: Ordering](
elements: mutable.IndexedSeq[A]
): Unit = {
override def sortInPlace[A: Ordering](elements: WritableSlice[A]): Unit = {
sort(in = elements, out = elements)
}

override def sortFunctionally[A: Ordering](
in: collection.IndexedSeq[A],
out: mutable.IndexedSeq[A],
in: Slice[A],
out: WritableSlice[A],
): Unit = {
sort(in, out)
}
Expand Down
106 changes: 106 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,106 @@
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, SubSlice],
] 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, _ <: SomeIndexedSeqOps[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,49 @@
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, SubSlice],
] 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,
_ <: SomeMutableIndexedSeqOps[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,7 @@
package com.github.skozlov.commons

import scala.collection.mutable

package object collection {
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,7 @@
package com.github.skozlov.algorithms.sort

import com.github.skozlov.algorithms.Test
import com.github.skozlov.Test
import com.github.skozlov.commons.collection.{Slice, WritableSlice}

class SortTest extends Test {
private val cases: Seq[Seq[(Int, Int)]] = {
Expand Down Expand Up @@ -42,7 +43,7 @@ class SortTest extends Test {
private def testInPlaceSort(sort: InPlaceSort): Unit = {
val arrays: Seq[Array[(Int, Int)]] = cases map { _.toArray }
val results: Seq[Seq[(Int, Int)]] = for (array <- arrays) yield {
sort.sortInPlace(array)
sort.sortInPlace(WritableSlice(array)())
array.toSeq
}
checkResults(results, sort.isInstanceOf[StableSort])
Expand All @@ -53,7 +54,7 @@ class SortTest extends Test {
for {
_case: Seq[(Int, Int)] <- cases
input: Array[(Int, Int)] = _case.toArray
output: Array[(Int, Int)] = sort.sortFunctionally(input)
output: Array[(Int, Int)] = sort.sortFunctionally(Slice(input)())
} yield (input, output)
checkResults(
inputsAndOutputsAfterSort map { _._2.toSeq },
Expand Down
Loading

0 comments on commit ca0cbba

Please sign in to comment.