Skip to content

Commit

Permalink
Insertion sort
Browse files Browse the repository at this point in the history
  • Loading branch information
skozlov committed Jul 18, 2024
1 parent 7a20126 commit a66f994
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: CI
on:
pull_request:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/[email protected]
- name: Setup JDK
uses: actions/[email protected]
with:
java-version: 17
distribution: temurin
cache: sbt
- name: Build
run: sbt rebuild
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.bsp
/.idea
/project/target
/project/project/target
/target
4 changes: 4 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version = 3.8.0
runner.dialect = scala3

rewrite.trailingCommas.style = multiple
31 changes: 31 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "3.3.1"

ThisBuild / scalacOptions ++= Seq(
"-encoding",
"utf8",
"--release:17",
"-deprecation",
"-Xfatal-warnings",
)

lazy val root = (project in file("."))
.settings(
name := "algorithms",
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.19" % Test
),
)

commands ++= Seq(
Command.command("build") { state =>
"scalafmtCheckAll" ::
"scalafmtSbtCheck" ::
"test" ::
state
},
Command.command("rebuild") { state =>
"clean" :: "build" :: state
},
)
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.9.9
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.github.skozlov.algorithms.sort

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

object InsertionSort {

/** Sorts the input sequence putting elements to the output sequence.
*
* The sequences may be independent (then the input sequence isn't modified)
* or be the same sequence (then in-place sort is performed).
*
* This sort is stable.
*
* Time consumption: O(n<sup>2</sup>).
*
* Memory consumption: O(1).
* @param in
* input sequence which contains elements to sort
* @param out
* output sequence to put sorted elements into
* @throws IllegalArgumentException
* if the input and output sequences have different sizes
*/
def sort[A: Ordering](
in: collection.IndexedSeq[A],
out: mutable.IndexedSeq[A],
): Unit = {
require(
in.size == out.size,
s"in and out have different sizes: ${in.size} and ${out.size}",
)

if (in.nonEmpty) {

/** Assuming that out[0:index-1] contains sorted in[0:index-1], inserts
* in[index] into out[0:index-1] so that out[0:index] contains sorted
* in[0:index].
*/
def insert(index: Int): Unit = {
val elementToInsert = in(index)

/** In out sequence, shifts elements which are greater than
* elementToInsert one position to the right, going right to left
* starting from startIndex.
* @return
* the index to insert elementToInsert into after the shift
*/
@tailrec
def shiftGreaterReturningTargetIndex(startIndex: Int): Int = {
val elementToCompare = out(startIndex)
if (elementToCompare > elementToInsert) {
out(startIndex + 1) = elementToCompare
if (startIndex == 0) {
0
} else {
shiftGreaterReturningTargetIndex(startIndex - 1)
}
} else startIndex + 1
}

val targetIndex =
shiftGreaterReturningTargetIndex(startIndex = index - 1)
out(targetIndex) = elementToInsert
}

out(0) = in(0)
for (i <- 1 until in.size) {
insert(i)
}
}
}
}
6 changes: 6 additions & 0 deletions src/test/scala/com/github/skozlov/algorithms/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.skozlov.algorithms

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

abstract class Test extends AnyFunSuite with Matchers
73 changes: 73 additions & 0 deletions src/test/scala/com/github/skozlov/algorithms/sort/SortTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.github.skozlov.algorithms.sort

import com.github.skozlov.algorithms.Test

class SortTest extends Test {
private val cases: Seq[Seq[(Int, Int)]] = {
Seq(
Seq.empty[Int],
Seq(1),
Seq(1, 2, 3),
Seq(1, 3, 2),
Seq(2, 1, 3),
Seq(2, 3, 1),
Seq(3, 1, 2),
Seq(3, 2, 1),
Seq(1, 3, 2, 3, 1),
) map { _.zipWithIndex }
}

private implicit val ordering: Ordering[(Int, Int)] =
Ordering.by[(Int, Int), Int](_._1)

private val commonExpectedResults: Seq[Seq[Int]] = cases map {
_.map { _._1 }.sorted
}

private val stableSortExpectedResults: Seq[Seq[(Int, Int)]] = cases map {
_.sorted(Ordering.by[(Int, Int), Int](_._1).orElseBy(_._2))
}

private def checkResults(
results: Seq[Seq[(Int, Int)]],
stableSort: Boolean,
): Unit = {
if (stableSort) {
results shouldBe stableSortExpectedResults
} else {
(results map { _ map { _._1 } }) shouldBe commonExpectedResults
}
}

private def testInPlaceSort(
sort: InsertionSort.type,
stable: Boolean,
): Unit = {
val arrays: Seq[Array[(Int, Int)]] = cases map { _.toArray }
val results: Seq[Seq[(Int, Int)]] = for (array <- arrays) yield {
sort.sort(in = array, out = array)
array.toSeq
}
checkResults(results, stable)
}

private def testImmutableInputSort(
sort: InsertionSort.type,
stable: Boolean,
): Unit = {
val inputsAndOutputsAfterSort: Seq[(Array[(Int, Int)], Array[(Int, Int)])] =
for {
_case: Seq[(Int, Int)] <- cases
input: Array[(Int, Int)] = _case.toArray
output: Array[(Int, Int)] = Array.ofDim[(Int, Int)](_case.size)
_ = sort.sort(input, output)
} yield (input, output)
checkResults(inputsAndOutputsAfterSort map { _._2.toSeq }, stable)
(inputsAndOutputsAfterSort map { _._1.toSeq }) shouldBe cases
}

test("insertion sort") {
testInPlaceSort(InsertionSort, stable = true)
testImmutableInputSort(InsertionSort, stable = true)
}
}

0 comments on commit a66f994

Please sign in to comment.