diff --git a/.gitignore b/.gitignore index bf7ada31e..5669c4920 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ collectedLogs.zip .bloop/ .metals/ .swp -project/metals.sbt +**/project/metals.sbt # sbt specific .cache/ diff --git a/code-snippets/build.sbt b/code-snippets/build.sbt index a70dd8b24..41da2b22e 100644 --- a/code-snippets/build.sbt +++ b/code-snippets/build.sbt @@ -1,4 +1,4 @@ -val dottyVersion = "0.24.0-RC1" +val dottyVersion = "0.27.0-RC1" Global / onChangedBuildSource := ReloadOnSourceChanges @@ -9,8 +9,8 @@ lazy val `dotty-snippets` = project name := "dotty-simple", version := "0.1.0", - ThisBuild / scalaVersion := dottyVersion, - //ThisBuild / scalaVersion := dottyLatestNightlyBuild.get, + //ThisBuild / scalaVersion := dottyVersion, + ThisBuild / scalaVersion := dottyLatestNightlyBuild.get, libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" ) diff --git a/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala new file mode 100644 index 000000000..5923685cf --- /dev/null +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala @@ -0,0 +1,18 @@ +package org.lunatech.dotty.extensionmethods.collective + +extension (i: Int) + def isEven: Boolean = i % 2 == 0 + + def unary_! : Int = -i + + def square : Int = i * i + +extension [T](xs: List[T]): + def second: T = xs.tail.head + def third: T = xs.tail.second + +@main def collective() = + println(List(1,2,3).second) + println(! 5) + println(5.isEven) + println(5.square) \ No newline at end of file diff --git a/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala new file mode 100644 index 000000000..67b94dfb4 --- /dev/null +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala @@ -0,0 +1,9 @@ +package org.lunatech.dotty.extensionmethods + +extension (a: String) def < (b: String): Boolean = a.compareTo(b) < 0 + +extension (a: Int) def +++: (b: List[Int]) = a::b + +@main def extensionOperators() = + println("abc" < "pqr") // true + println(1 +++: List(2,3,4)) // val lst: List[Int] = List(1, 2, 3, 4) diff --git a/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala new file mode 100644 index 000000000..b957d3101 --- /dev/null +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala @@ -0,0 +1,21 @@ +package org.lunatech.dotty.extensionmethods + +extension [T](xs: List[T]) + def second = + xs.tail.head + +extension [T](xs: List[List[T]]) + def flattened = + xs.foldLeft[List[T]](Nil)(_ ++ _) + +extension [T: Numeric](x: T) + def + (y: T): T = + summon[Numeric[T]].plus(x, y) + +def [T: Numeric](x: T) - (y: T) = summon[Numeric[T]].plus(x, y) + +case class Circle(x: Double, y: Double, radius: Double) +def (c: Circle).circumference: Double = c.radius * math.Pi * 2 + +@main def circles() = + println(s"Circle: ${Circle(1.0, 2.0, 4).circumference}") \ No newline at end of file diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala similarity index 67% rename from code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala rename to code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala index 42ba70efe..2d5b4be2a 100644 --- a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/IntOps.scala @@ -1,12 +1,9 @@ package org.lunatech.dotty.extensionmethods -trait IntOps { - def (i: Int).isEven: Boolean = i % 2 == 0 +extension (i: Int): + def isEven: Boolean = i % 2 == 0 - def (i: Int).unary_! : Int = -i -} - -object IntOperations { + def unary_! : Int = -i // 1. An extension method is applicable if it is visible under a simple name, // by being defined or inherited or imported in a scope enclosing the application @@ -17,10 +14,7 @@ object IntOperations { // we could define this object as `IntOperations extends IntOps`. // A more elegant way to bring `IntOps` to the context is by using a `given` - given IntOps +@main def show(): Unit = + println(2.isEven) // true + println(!5) // -5 - @main def show(): Unit = { - println(2.isEven) // true - println(!5) // -5 - } -} diff --git a/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ScopeExtensionMethods.worksheet.sc b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ScopeExtensionMethods.worksheet.sc new file mode 100644 index 000000000..35bb33419 --- /dev/null +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/extensionmethods/ScopeExtensionMethods.worksheet.sc @@ -0,0 +1,49 @@ +trait IntOps: + extension (i: Int) def isZero: Boolean = i == 0 + + extension (i: Int) def safeMod(x: Int): Option[Int] = + // extension method defined in same scope IntOps + if x.isZero then None + else Some(i % x) + +object IntOpsEx extends IntOps: + extension (i: Int) def safeDiv(x: Int): Option[Int] = + // extension method brought into scope via inheritance from IntOps + if x.isZero then None + else Some(i / x) + +object SafeDiv: + import IntOpsEx._ // brings safeDiv and safeMod into scope + + extension (i: Int) def divide(d: Int) : Option[(Int, Int)] = + // extension methods imported and thus in scope + (i.safeDiv(d), i.safeMod(d)) match + case (Some(d), Some(r)) => Some((d, r)) + case _ => None + + val d1 = 25.divide(3) + val d2 = 25.divide(0) + +SafeDiv.d1 +SafeDiv.d2 + +given IntOps + +20.safeMod(0) +20.safeMod(3) + +extension [T](xs: List[List[T]]) + def flatten: List[T] = xs.foldLeft(Nil: List[T])(_ ++ _) + +given [T: Ordering] as Ordering[List[T]]: + override def compare(l1: List[T], l2: List[T]): Int = 1 + extension (xs: List[T]) + def < (ys: List[T]): Boolean = summon[Ordering[T]].lt(xs.head, ys.head) + + +// extension method available since it is in the implicit scope of List[List[Int]] +List(List(1, 2), List(3, 4)).flatten + +// extension method available since it is in the given Ordering[List[T]], +// which is itself in the implicit scope of List[Int] +List(1, 2) < List(3) \ No newline at end of file diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/multiversalequality/BadRepository.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/multiversalequality/BadRepository.scala similarity index 100% rename from code-snippets/src/main/scala/org/lunatech/dotty/multiversalequality/BadRepository.scala rename to code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/multiversalequality/BadRepository.scala diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/multiversalequality/GoodRepository.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/multiversalequality/GoodRepository.scala similarity index 100% rename from code-snippets/src/main/scala/org/lunatech/dotty/multiversalequality/GoodRepository.scala rename to code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/multiversalequality/GoodRepository.scala diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala similarity index 96% rename from code-snippets/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala rename to code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala index 4cc567f4e..6446ee48a 100644 --- a/code-snippets/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala +++ b/code-snippets/contextual-abstractions/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala @@ -138,15 +138,15 @@ object Scala3OpaqueTypeAliasesDefinitions { def apply(d: Double): Miles = d } - extension { - def (a: Kilometres) + (b: Kilometres): Kilometres = a + b - def (km: Kilometres).toMiles: Miles = km / 1.6 - } + extension (a: Kilometres): + def +(b: Kilometres): Kilometres = a + b + def toMiles: Miles = a / 1.6 - extension { - def (a: Miles) + (b: Miles): Miles = a + b - def (miles: Miles).toKm: Kilometres = miles * 1.6 - } + + extension (a: Miles): + // def +(b: Miles): Miles = a + b + def toKm: Kilometres = a * 1.6 + } diff --git a/code-snippets/project/metals.sbt b/code-snippets/project/metals.sbt index e36d9e95b..e1c8ed3ea 100644 --- a/code-snippets/project/metals.sbt +++ b/code-snippets/project/metals.sbt @@ -1,4 +1,4 @@ // DO NOT EDIT! This file is auto-generated. // This file enables sbt-bloop to create bloop config files. -addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.0-RC1-229-b7c15aa9") +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.3-31-b16d7e50") diff --git a/code-snippets/project/project/metals.sbt b/code-snippets/project/project/metals.sbt new file mode 100644 index 000000000..e1c8ed3ea --- /dev/null +++ b/code-snippets/project/project/metals.sbt @@ -0,0 +1,4 @@ +// DO NOT EDIT! This file is auto-generated. +// This file enables sbt-bloop to create bloop config files. + +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.3-31-b16d7e50") diff --git a/code-snippets/project/project/project/metals.sbt b/code-snippets/project/project/project/metals.sbt new file mode 100644 index 000000000..e1c8ed3ea --- /dev/null +++ b/code-snippets/project/project/project/metals.sbt @@ -0,0 +1,4 @@ +// DO NOT EDIT! This file is auto-generated. +// This file enables sbt-bloop to create bloop config files. + +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.3-31-b16d7e50") diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala b/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala deleted file mode 100644 index a4808931e..000000000 --- a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/CollectiveExtensions.scala +++ /dev/null @@ -1,14 +0,0 @@ -package org.lunatech.dotty.extensionmethods - - extension on (i: Int) { - def isEven: Boolean = i % 2 == 0 - - def unary_! : Int = -i - - def square : Int = i * i - } - - extension listOps on [T](xs: List[T]) { - def second: T = xs.tail.head - def third: T = xs.tail.second - } diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionInstance.scala b/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionInstance.scala deleted file mode 100644 index bd98605d6..000000000 --- a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionInstance.scala +++ /dev/null @@ -1,24 +0,0 @@ -package org.lunatech.dotty.extensionmethods - -extension intOps { - def (i: Int).isEven: Boolean = i % 2 == 0 - - def (i: Int).unary_! : Int = -i - - def (x: Int).square : Int = x * x -} - - -@main def test: Unit = { - println(4.isEven) - - println(3.square) -} - -// given intOps as AnyRef { -// def (i: Int).isEven: Boolean = i % 2 == 0 - -// def (i: Int).unary_! : Int = -i - -// def (x: Int).square : Int = x * x -// } diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala b/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala deleted file mode 100644 index 697bb99ff..000000000 --- a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/ExtensionOperators.scala +++ /dev/null @@ -1,11 +0,0 @@ -package org.lunatech.dotty.extensionmethods - -object ExtensionOperators extends App { - def (a: String) < (b: String): Boolean = a.compareTo(b) < 0 - - def (a: Int) +: (b: List[Int]) = a::b - - println("abc" < "pqr") // true - - val lst = 1 +: List(2,3,4) // val lst: List[Int] = List(1, 2, 3, 4) -} diff --git a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala b/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala deleted file mode 100644 index b743d26f6..000000000 --- a/code-snippets/src/main/scala/org/lunatech/dotty/extensionmethods/GenericExtension.scala +++ /dev/null @@ -1,12 +0,0 @@ -package org.lunatech.dotty.extensionmethods - -def [T](xs: List[T]) second = - xs.tail.head - -def [T](xs: List[List[T]]) flattened = - xs.foldLeft[List[T]](Nil)(_ ++ _) - -def [T: Numeric](x: T) + (y: T): T = - summon[Numeric[T]].plus(x, y) - - diff --git a/code-snippets/src/test/scala/Test1.scala b/code-snippets/src/test/scala/Test1.scala deleted file mode 100644 index 4faf217a2..000000000 --- a/code-snippets/src/test/scala/Test1.scala +++ /dev/null @@ -1,8 +0,0 @@ -import org.junit.Test -import org.junit.Assert._ - -class Test1 { - @Test def t1(): Unit = { - assertEquals("I was compiled by dotty :)", Main.msg) - } -} \ No newline at end of file diff --git a/exercises/.sbtopts b/exercises/.sbtopts index 025105ff9..16094f991 100644 --- a/exercises/.sbtopts +++ b/exercises/.sbtopts @@ -1,4 +1,3 @@ -J-Xmx4G --J-XX:+CMSClassUnloadingEnabled -J-Xss4M -Duser.timezone=GMT diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 65d52e421..1ec983f19 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -40,7 +40,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -58,7 +59,8 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } } diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index d35a5bffc..195fd157e 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -90,7 +90,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 65d52e421..1ec983f19 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -40,7 +40,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -58,7 +59,8 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index a6b9b3a96..59c7d8cc3 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -90,7 +90,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 65d52e421..1ec983f19 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -40,7 +40,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -58,7 +59,8 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index d35a5bffc..195fd157e 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -90,7 +90,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 9229b68d7..aa832c5f8 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -object ReductionRules { +object ReductionRules: - def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = { + def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) val completeInputCellGroups = inputCellsGrouped.filter { case (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ object ReductionRules { completeAndIsolatedValueSets.foldLeft(reductionSet) { case (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def reductionRuleTwo(reductionSet: ReductionSet): ReductionSet = { + def reductionRuleTwo(reductionSet: ReductionSet): ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -43,5 +42,3 @@ object ReductionRules { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5de08a4ea..dca04e881 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol sealed trait Command @@ -23,21 +23,18 @@ object SudokuDetailProcessor { def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit def processorName(id: Int): String - } implicit val rowUpdateSender: UpdateSender[Row] = new UpdateSender[Row] { - def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = { + def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" } @@ -52,9 +49,8 @@ object SudokuDetailProcessor { sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor._ @@ -64,20 +60,18 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = implicitly[UpdateSender[DetailType]] updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -92,14 +86,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -107,11 +100,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index cf366a8ea..28ca94f49 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,7 +5,7 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: sealed trait Command case object SendNewSudoku extends Command @@ -25,13 +25,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -86,4 +85,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 65d52e421..5de00ef7b 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: sealed trait Command final case class NewUpdatesInFlight(count: Int) extends Command @@ -19,13 +19,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -40,7 +39,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -58,7 +58,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index d35a5bffc..3d1bb81ef 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol sealed trait Command @@ -30,7 +30,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +39,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +54,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +88,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +97,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { case (rowCellNr, newCellContent) => @@ -141,9 +140,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -151,7 +149,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -161,8 +158,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index 83263e413..24cf1cacc 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_003_top_level_definitions/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,21 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal { +implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal: import scala.language.implicitConversions - def toSudokuField: SudokuField = { + def toSudokuField: SudokuField = val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { + val sudoku = for (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) x = cellUpdates.to(Map).withDefaultValue(Set(0)) y = Vector.range(0, 9).map(n => x(n)) - } yield y + yield y SudokuField(sudoku) - } -} -implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { +implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) def rotateCW: SudokuField = SudokuField(sudokuField.sudoku.reverse.transpose) @@ -41,21 +39,19 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { - SudokuField( + def rowSwap(row1: Int, row2: Int): SudokuField = + SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) case (_, `row2`) => sudokuField.sudoku(row1) case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -67,9 +63,8 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[RowUpdate] = { + def toRowUpdates: Vector[RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -78,5 +73,3 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { .map { case (c, i) => RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 076b77dc6..b1808ac20 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -object ReductionRules { +object ReductionRules: - def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = { + def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -10,16 +10,15 @@ object ReductionRules { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def reductionRuleTwo(reductionSet: ReductionSet): ReductionSet = { + def reductionRuleTwo(reductionSet: ReductionSet): ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -41,5 +40,3 @@ object ReductionRules { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5de08a4ea..dca04e881 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol sealed trait Command @@ -23,21 +23,18 @@ object SudokuDetailProcessor { def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit def processorName(id: Int): String - } implicit val rowUpdateSender: UpdateSender[Row] = new UpdateSender[Row] { - def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = { + def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" } @@ -52,9 +49,8 @@ object SudokuDetailProcessor { sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor._ @@ -64,20 +60,18 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = implicitly[UpdateSender[DetailType]] updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -92,14 +86,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -107,11 +100,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index c9696276d..6de9e1690 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,7 +5,7 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: sealed trait Command case object SendNewSudoku extends Command @@ -25,13 +25,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -86,4 +85,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 3121df8fc..2b8be98af 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: sealed trait Command final case class NewUpdatesInFlight(count: Int) extends Command @@ -19,13 +19,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -39,7 +38,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -57,7 +57,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index fd3648d74..2198ad5dc 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol sealed trait Command @@ -30,7 +30,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +39,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +54,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +88,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +97,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) @@ -138,9 +137,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -148,7 +146,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -158,8 +155,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index a4dfd1e7a..a032b951c 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_004_parameter_untupling/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,21 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal { +implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal: import scala.language.implicitConversions - def toSudokuField: SudokuField = { + def toSudokuField: SudokuField = val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { + val sudoku = for (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) x = cellUpdates.to(Map).withDefaultValue(Set(0)) y = Vector.range(0, 9).map(n => x(n)) - } yield y + yield y SudokuField(sudoku) - } -} -implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { +implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) def rotateCW: SudokuField = SudokuField(sudokuField.sudoku.reverse.transpose) @@ -41,7 +39,7 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -49,13 +47,11 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -67,9 +63,8 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[RowUpdate] = { + def toRowUpdates: Vector[RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -78,5 +73,3 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal { .map { (c, i) => RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5b45077a6..42f7ce29d 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol sealed trait Command @@ -23,21 +23,18 @@ object SudokuDetailProcessor { def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (implicit updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit def processorName(id: Int): String - } implicit val rowUpdateSender: UpdateSender[Row] = new UpdateSender[Row] { - def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = { + def sendUpdate(id: Int, cellUpdates: CellUpdates)(implicit sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" } @@ -52,9 +49,8 @@ object SudokuDetailProcessor { sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -63,20 +59,18 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = implicitly[UpdateSender[DetailType]] updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -91,14 +85,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -106,11 +99,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index c9696276d..6de9e1690 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,7 +5,7 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: sealed trait Command case object SendNewSudoku extends Command @@ -25,13 +25,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -86,4 +85,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 3121df8fc..2b8be98af 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: sealed trait Command final case class NewUpdatesInFlight(count: Int) extends Command @@ -19,13 +19,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -39,7 +38,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -57,7 +57,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index fd3648d74..2198ad5dc 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol sealed trait Command @@ -30,7 +30,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +39,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +54,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +88,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +97,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) @@ -138,9 +137,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -148,7 +146,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -158,8 +155,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index aaab41528..95b5c7251 100644 --- a/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_005_extension_methods/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,22 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -42,7 +41,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -50,13 +49,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -68,9 +65,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -79,5 +75,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9dd0d48ee..7a4ee4a8e 100644 --- a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -123,4 +122,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 7ed19e3be..7b7bec8cb 100644 --- a/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_005_extension_methods/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,19 +1,16 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 808831cb4..5bd895bc5 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol sealed trait Command @@ -23,21 +23,18 @@ object SudokuDetailProcessor { def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } implicit val rowUpdateSender: UpdateSender[Row] = new UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" } @@ -52,9 +49,8 @@ object SudokuDetailProcessor { sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -63,20 +59,18 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -91,14 +85,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -106,11 +99,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index c9696276d..6de9e1690 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,7 +5,7 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: sealed trait Command case object SendNewSudoku extends Command @@ -25,13 +25,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -86,4 +85,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 3121df8fc..2b8be98af 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: sealed trait Command final case class NewUpdatesInFlight(count: Int) extends Command @@ -19,13 +19,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -39,7 +38,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -57,7 +57,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 800157e9d..9cab9de3a 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol sealed trait Command @@ -30,7 +30,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +39,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +54,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +88,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +97,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) @@ -138,9 +137,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -148,7 +146,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -158,8 +155,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index aaab41528..95b5c7251 100644 --- a/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_006_using_and_summon/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,22 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -42,7 +41,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -50,13 +49,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -68,9 +65,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -79,5 +75,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9dd0d48ee..7a4ee4a8e 100644 --- a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -123,4 +122,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 7ed19e3be..7b7bec8cb 100644 --- a/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,19 +1,16 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 2ce5608d7..97a3ca7e3 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol sealed trait Command @@ -23,38 +23,31 @@ object SudokuDetailProcessor { def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } - given UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + given UpdateSender[Row]: + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" - } - given UpdateSender[Column] { + given UpdateSender[Column]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! ColumnUpdate(id, cellUpdates) def processorName(id: Int): String = s"col-processor-$id" - } - given UpdateSender[Block] { + given UpdateSender[Block]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" - } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -63,23 +56,21 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] // The following can also be written as: // given ActorRef[Response] = replyTo // updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState)) updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -94,14 +85,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -109,11 +99,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index c9696276d..6de9e1690 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,7 +5,7 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: sealed trait Command case object SendNewSudoku extends Command @@ -25,13 +25,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -86,4 +85,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 3121df8fc..2b8be98af 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: sealed trait Command final case class NewUpdatesInFlight(count: Int) extends Command @@ -19,13 +19,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -39,7 +38,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -57,7 +57,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 800157e9d..9cab9de3a 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol sealed trait Command @@ -30,7 +30,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +39,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +54,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +88,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +97,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) @@ -138,9 +137,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -148,7 +146,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -158,8 +155,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index aaab41528..95b5c7251 100644 --- a/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_007_givens/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,22 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -42,7 +41,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -50,13 +49,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -68,9 +65,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -79,5 +75,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9dd0d48ee..7a4ee4a8e 100644 --- a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -123,4 +122,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 7ed19e3be..7b7bec8cb 100644 --- a/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_007_givens/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,19 +1,16 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5f378ce7d..cfef9f2ef 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,61 +4,52 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol - enum Command { + enum Command: case ResetSudokuDetailState case Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) case GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - } export Command._ // My responses - enum Response { + enum Response: case RowUpdate(id: Int, cellUpdates: CellUpdates) case ColumnUpdate(id: Int, cellUpdates: CellUpdates) case BlockUpdate(id: Int, cellUpdates: CellUpdates) case SudokuDetailUnchanged - } export Response._ val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } - given UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + given UpdateSender[Row]: + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" - } - given UpdateSender[Column] { + given UpdateSender[Column]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! ColumnUpdate(id, cellUpdates) def processorName(id: Int): String = s"col-processor-$id" - } - given UpdateSender[Block] { + given UpdateSender[Block]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" - } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -67,23 +58,21 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] // The following can also be written as: // given ActorRef[Response] = replyTo // updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState)) updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -98,14 +87,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -113,11 +101,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 8d5ef3194..caa5ff630 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,13 +5,12 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: - enum Command { + enum Command: case SendNewSudoku // Wrapped responses case SolutionWrapper(result: SudokuSolver.Response) - } export Command._ private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = @@ -27,13 +26,12 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.Command], timers: TimerScheduler[SudokuProblemSender.Command], sudokuSolverSettings: SudokuSolverSettings -) { +): import SudokuProblemSender._ private val solutionWrapper: ActorRef[SudokuSolver.Response] = @@ -88,4 +86,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 804a54185..4ab2464cd 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,18 +3,16 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: - enum Command { + enum Command: case NewUpdatesInFlight(count: Int) case SudokuDetailState(index: Int, state: ReductionSet) - } export Command._ // My responses - enum Response { + enum Response: case Result(sudoku: Sudoku) - } export Response._ def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], @@ -24,13 +22,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -44,7 +41,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -62,7 +60,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 15e9b719b..b35037e8e 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,29 +8,27 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol - enum Command { + enum Command: case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], replyTo: ActorRef[SudokuSolver.Response]) // Wrapped responses case SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) case SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) - } export Command._ // My Responses - enum Response { + enum Response: case SudokuSolution(sudoku: Sudoku) - } export Response._ import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -39,7 +37,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -55,11 +52,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ) -} class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command] -) { +): import CellMappings._ import SudokuSolver._ @@ -90,7 +86,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -98,7 +95,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], def processRequest(requestor: Option[ActorRef[Response]], startTime: Long): Behavior[Command] = Behaviors.receiveMessage { case SudokuDetailProcessorResponseWrapped(response) => - response match { + response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) @@ -138,9 +135,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case unchanged @ SudokuDetailProcessor.SudokuDetailUnchanged => progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(-1) Behaviors.same - } case SudokuProgressTrackerResponseWrapped(result) => - result match { + result match case SudokuProgressTracker.Result(sudoku) => context.log.info( s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" @@ -148,7 +144,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) - } case msg: InitialRowUpdates if buffer.isFull => context.log.info(s"DROPPING REQUEST") Behaviors.same @@ -158,8 +153,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index aaab41528..95b5c7251 100644 --- a/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_008_enum_and_export/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,22 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -42,7 +41,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -50,13 +49,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -68,9 +65,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -79,5 +75,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9dd0d48ee..7a4ee4a8e 100644 --- a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -123,4 +122,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 7ed19e3be..7b7bec8cb 100644 --- a/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,19 +1,16 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5f378ce7d..cfef9f2ef 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,61 +4,52 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol - enum Command { + enum Command: case ResetSudokuDetailState case Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) case GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - } export Command._ // My responses - enum Response { + enum Response: case RowUpdate(id: Int, cellUpdates: CellUpdates) case ColumnUpdate(id: Int, cellUpdates: CellUpdates) case BlockUpdate(id: Int, cellUpdates: CellUpdates) case SudokuDetailUnchanged - } export Response._ val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } - given UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + given UpdateSender[Row]: + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" - } - given UpdateSender[Column] { + given UpdateSender[Column]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! ColumnUpdate(id, cellUpdates) def processorName(id: Int): String = s"col-processor-$id" - } - given UpdateSender[Block] { + given UpdateSender[Block]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" - } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -67,23 +58,21 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] // The following can also be written as: // given ActorRef[Response] = replyTo // updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState)) updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -98,14 +87,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -113,11 +101,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 1ab0200ec..aea108532 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,11 +5,10 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: - enum Command { + enum Command: case SendNewSudoku - } export Command._ type CommandAndResponses = Command | SudokuSolver.Response @@ -26,12 +25,11 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } }.narrow // Restrict the actor's [external] protocol to its set of commands -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.CommandAndResponses], timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], - sudokuSolverSettings: SudokuSolverSettings) { + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender._ private val initialSudokuField = rowUpdates.toSudokuField @@ -83,4 +81,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 804a54185..4ab2464cd 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,18 +3,16 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: - enum Command { + enum Command: case NewUpdatesInFlight(count: Int) case SudokuDetailState(index: Int, state: ReductionSet) - } export Command._ // My responses - enum Response { + enum Response: case Result(sudoku: Sudoku) - } export Response._ def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], @@ -24,13 +22,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -44,7 +41,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -62,7 +60,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index cca901b49..70e95e63b 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,19 +8,17 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol - enum Command { + enum Command: case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], replyTo: ActorRef[SudokuSolver.Response]) - } export Command._ // My Responses - enum Response { + enum Response: case SudokuSolution(sudoku: Sudoku) - } export Response._ type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response @@ -29,7 +27,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -38,7 +36,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -54,11 +51,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ).narrow -} class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], buffer: StashBuffer[SudokuSolver.CommandAndResponses] -) { +): import CellMappings._ import SudokuSolver._ @@ -84,7 +80,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -148,8 +145,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index aaab41528..95b5c7251 100644 --- a/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_009_union_types/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -14,23 +14,22 @@ val cellUpdatesEmpty = Vector.empty[(Int, Set[Int])] import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -42,7 +41,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -50,13 +49,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -68,9 +65,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -79,5 +75,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9dd0d48ee..7a4ee4a8e 100644 --- a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -123,4 +122,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 7ed19e3be..7b7bec8cb 100644 --- a/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_009_union_types/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,19 +1,16 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5f378ce7d..cfef9f2ef 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,61 +4,52 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol - enum Command { + enum Command: case ResetSudokuDetailState case Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) case GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - } export Command._ // My responses - enum Response { + enum Response: case RowUpdate(id: Int, cellUpdates: CellUpdates) case ColumnUpdate(id: Int, cellUpdates: CellUpdates) case BlockUpdate(id: Int, cellUpdates: CellUpdates) case SudokuDetailUnchanged - } export Response._ val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } - given UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + given UpdateSender[Row]: + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" - } - given UpdateSender[Column] { + given UpdateSender[Column]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! ColumnUpdate(id, cellUpdates) def processorName(id: Int): String = s"col-processor-$id" - } - given UpdateSender[Block] { + given UpdateSender[Block]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" - } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -67,23 +58,21 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] // The following can also be written as: // given ActorRef[Response] = replyTo // updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState)) updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -98,14 +87,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -113,11 +101,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 1ab0200ec..aea108532 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,11 +5,10 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: - enum Command { + enum Command: case SendNewSudoku - } export Command._ type CommandAndResponses = Command | SudokuSolver.Response @@ -26,12 +25,11 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } }.narrow // Restrict the actor's [external] protocol to its set of commands -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.CommandAndResponses], timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], - sudokuSolverSettings: SudokuSolverSettings) { + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender._ private val initialSudokuField = rowUpdates.toSudokuField @@ -83,4 +81,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 804a54185..4ab2464cd 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,18 +3,16 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: - enum Command { + enum Command: case NewUpdatesInFlight(count: Int) case SudokuDetailState(index: Int, state: ReductionSet) - } export Command._ // My responses - enum Response { + enum Response: case Result(sudoku: Sudoku) - } export Response._ def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], @@ -24,13 +22,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -44,7 +41,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -62,7 +60,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 29265a3cd..3d3cc67d9 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,19 +8,17 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol - enum Command { + enum Command: case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], replyTo: ActorRef[SudokuSolver.Response]) - } export Command._ // My Responses - enum Response { + enum Response: case SudokuSolution(sudoku: Sudoku) - } export Response._ type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response @@ -29,7 +27,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -38,7 +36,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -54,11 +51,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ).narrow -} class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], buffer: StashBuffer[SudokuSolver.CommandAndResponses] -) { +): import CellMappings._ import SudokuSolver._ @@ -84,7 +80,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -148,8 +145,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index 79d7c84ff..208927faf 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -12,47 +12,44 @@ type ReductionSet = Vector[CellContent] type Sudoku = Vector[ReductionSet] opaque type CellUpdates = Vector[(Int, Set[Int])] -object CellUpdates { +object CellUpdates: def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates: _*) -} val cellUpdatesEmpty: CellUpdates = Vector.empty[(Int, Set[Int])] -extension on (updates: CellUpdates) { +extension[A] (updates: CellUpdates) /** * Optionally, given that we only use `to(Map)`, we can create a non-generic extension method * For ex.: def toMap: Map[Int, Set[Int]] = updates.to(Map).withDefaultValue(Set(0)) */ - def to[C1](factory: Factory[(Int, Set[Int]), C1]): C1 = updates.to(factory) + def to(factory: Factory[(Int, Set[Int]), A]): A = updates.to(factory) - def foldLeft[B](z: B)(op: (B, (Int, Set[Int])) => B): B = updates.foldLeft(z)(op) + def foldLeft(z: A)(op: (A, (Int, Set[Int])) => A): A = updates.foldLeft(z)(op) - def foreach[U](f: ((Int, Set[Int])) => U): Unit = updates.foreach(f) + def foreach(f: ((Int, Set[Int])) => A): Unit = updates.foreach(f) def size: Int = updates.size -} def (update: (Int, Set[Int])) +: (updates: CellUpdates): CellUpdates = update +: updates import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -64,7 +61,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -72,13 +69,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -90,9 +85,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -101,5 +95,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 47e519581..417cf6774 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -48,7 +47,7 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) - val update1 = { + val update1 = val cellUpdates = stringToReductionSet(Vector( "12345678 ", @@ -62,11 +61,10 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { " 23 78 " )).zipWithIndex.map { _.swap} Update(CellUpdates(cellUpdates:_ *), detailParentProbe.ref) - } detailProcessor ! update1 - val reducedUpdate1 = { + val reducedUpdate1 = val cellUpdates = stringToReductionSet(Vector( " 23 56 ", @@ -81,7 +79,6 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { )).zipWithIndex.map(_.swap) BlockUpdate(2, CellUpdates(cellUpdates:_ *)) - } detailParentProbe.expectMessage(reducedUpdate1) @@ -128,4 +125,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 29a124357..0267766aa 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,20 +1,17 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = val updates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) CellUpdates(updates:_ *) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/exercise_011_multiversal_equality/build.sbt b/exercises/exercise_011_multiversal_equality/build.sbt deleted file mode 100644 index c256b58ba..000000000 --- a/exercises/exercise_011_multiversal_equality/build.sbt +++ /dev/null @@ -1 +0,0 @@ -scalacOptions in Compile += "-language:strictEquality" diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index fd382837c..efe7cd294 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -28,7 +28,7 @@ import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, Sudoku import scala.io.StdIn import scala.Console.{ GREEN, RESET } -object Main { +object Main: def apply(): Behavior[NotUsed] = Behaviors.setup { context => val sudokuSolverSettings = SudokuSolverSettings("sudokusolver.conf") @@ -44,16 +44,13 @@ object Main { Behaviors.stopped } } -} -object SudokuSolverMain { +object SudokuSolverMain: - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system") println(s"${GREEN}Hit RETURN to stop solver${RESET}") StdIn.readLine() system.terminate() - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala index 206e82823..f23cb5ebf 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/CellMappings.scala @@ -1,6 +1,6 @@ package org.lunatechlabs.dotty.sudoku -object CellMappings { +object CellMappings: def rowToColumnCoordinates(rowNr: Int, cellNr: Int): (Int, Int) = (cellNr, rowNr) @@ -19,4 +19,3 @@ object CellMappings { def blockToColumnCoordinates(blockNr: Int, cellNr: Int): (Int, Int) = ((blockNr % 3) * 3 + cellNr % 3, (blockNr / 3) * 3 + cellNr / 3) -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 50598fb5e..b7fe9d5bb 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -1,9 +1,9 @@ package org.lunatechlabs.dotty.sudoku // Extension instance wraps extension methods for type ReductionSet -extension reductionRules on (reductionSet: ReductionSet) { +extension (reductionSet: ReductionSet) - def applyReductionRuleOne: ReductionSet = { + def applyReductionRuleOne: ReductionSet = val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => set.size == setOccurrences.length @@ -11,16 +11,15 @@ extension reductionRules on (reductionSet: ReductionSet) { val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList completeAndIsolatedValueSets.foldLeft(reductionSet) { (cells, caivSet) => cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell + if cell != caivSet then cell &~ caivSet else cell } } - } - def applyReductionRuleTwo: ReductionSet = { + def applyReductionRuleTwo: ReductionSet = val valueOccurrences = CELLPossibleValues.map { value => cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => - if (cell contains value) index +: acc else acc + if cell contains value then index +: acc else acc } } @@ -42,5 +41,3 @@ extension reductionRules on (reductionSet: ReductionSet) { case ((cellValue, cellIndex), acc) => cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 5f378ce7d..cfef9f2ef 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -4,61 +4,52 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender -object SudokuDetailProcessor { +object SudokuDetailProcessor: // My protocol - enum Command { + enum Command: case ResetSudokuDetailState case Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) case GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - } export Command._ // My responses - enum Response { + enum Response: case RowUpdate(id: Int, cellUpdates: CellUpdates) case ColumnUpdate(id: Int, cellUpdates: CellUpdates) case BlockUpdate(id: Int, cellUpdates: CellUpdates) case SudokuDetailUnchanged - } export Response._ val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) def apply[DetailType <: SudokoDetailType](id: Int, state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = { + (using updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } - } - trait UpdateSender[A] { + trait UpdateSender[A]: def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit def processorName(id: Int): String - } - given UpdateSender[Row] { - override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = { + given UpdateSender[Row]: + override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! RowUpdate(id, cellUpdates) - } def processorName(id: Int): String = s"row-processor-$id" - } - given UpdateSender[Column] { + given UpdateSender[Column]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! ColumnUpdate(id, cellUpdates) def processorName(id: Int): String = s"col-processor-$id" - } - given UpdateSender[Block] { + given UpdateSender[Block]: override def sendUpdate(id: Int, cellUpdates: CellUpdates)(using sender: ActorRef[Response]): Unit = sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" - } -} -class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]) { +class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] private (context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor._ @@ -67,23 +58,21 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case Update(cellUpdates, replyTo) if ! fullyReduced => val previousState = state val updatedState = mergeState(state, cellUpdates) - if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if (transformedUpdatedState == state) { + if transformedUpdatedState == state then replyTo ! SudokuDetailUnchanged Behaviors.same - } else { + else val updateSender = summon[UpdateSender[DetailType]] // The following can also be written as: // given ActorRef[Response] = replyTo // updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState)) updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - } - } case Update(cellUpdates, replyTo) => replyTo ! SudokuDetailUnchanged @@ -98,14 +87,13 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva } - private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = { + private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => stateTally.updated(index, stateTally(index) & updatedCellContent) } - } - private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = { + private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) if updatedCellContent != previousCellContent => @@ -113,11 +101,8 @@ class SudokuDetailProcessor[DetailType <: SudokoDetailType : UpdateSender] priva case (_, cellUpdates) => cellUpdates } - } - private def isFullyReduced(state: ReductionSet): Boolean = { + private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index f48d0aa24..b6a005d51 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -2,52 +2,45 @@ package org.lunatechlabs.dotty.sudoku import java.util.NoSuchElementException -object SudokuIO { +object SudokuIO: - def printRow( row: ReductionSet): String = { - def printSubRow( subRowNo: Int): String = { + def printRow( row: ReductionSet): String = + def printSubRow( subRowNo: Int): String = val printItems = List(1,2,3).map( x => x + subRowNo * 3) - (for (elem <- row) + (for elem <- row yield { - (printItems.map (item => if ((elem & printItems.toSet).contains(item)) item.toString else " ")).mkString("") + (printItems.map (item => if (elem & printItems.toSet).contains(item) then item.toString else " ")).mkString("") }).mkString("| ", " | ", " |") - } - (for (subRow <- 0 until 3) yield printSubRow(subRow)).mkString("\n") - } + (for subRow <- 0 until 3 yield printSubRow(subRow)).mkString("\n") - def printRowShort( row: ReductionSet): String = { + def printRowShort( row: ReductionSet): String = (for - (elem <- row) + elem <- row yield { - if (elem.size == 1) elem.head.toString else " " + if elem.size == 1 then elem.head.toString else " " }).mkString("|","|","|") - } - private def sudokuCellRepresentation(content: CellContent): String = { - content.toList match { + private def sudokuCellRepresentation(content: CellContent): String = + content.toList match case Nil => "x" case singleValue +: Nil => singleValue.toString case _ => " " - } - } - private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { - val rowSubBlocks = for { + private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = + val rowSubBlocks = for row <- threeRows rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) rPres = rowSubBlock.mkString - } yield rPres + yield rPres rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") - } - def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { + def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = result.sudoku .sliding(3,3) .map(sudokuRowPrinter) .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") - } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth @@ -55,7 +48,7 @@ object SudokuIO { import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { + class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) var cachedLine: Option[String] = None @@ -63,44 +56,38 @@ object SudokuIO { override def iterator: Iterator[String] = new Iterator[String] { - override def hasNext: Boolean = (cachedLine, finished) match { + override def hasNext: Boolean = (cachedLine, finished) match case (Some(_), _) => true case (None, true) => false case (None, false) => - try { + try val line = input.readLine() - if (line == null) { + if line == null then finished = true input.close() fr.close() false - } else { + else cachedLine = Some(line) true - } - } catch { + catch case e: java.io.IOError => throw new IllegalStateException(e.toString) - } - } - override def next(): String = { - if (! hasNext) { + override def next(): String = + if ! hasNext then throw new NoSuchElementException("No more lines in file") - } val currentLine = cachedLine.get cachedLine = None currentLine - } } override def toString: String = "{Lines of " + file.getAbsolutePath + "}" - } def convertFromCellsToComplete(cellsIn: Vector[(String, Int)]): Vector[(Int, CellUpdates)] = - for { + for (rowCells, row) <- cellsIn updates = rowCells.zipWithIndex.foldLeft(cellUpdatesEmpty) { case (cellUpdates, (c, index)) if c != ' ' => @@ -108,10 +95,10 @@ object SudokuIO { case (cellUpdates, _) => cellUpdates } - } yield (row, updates) + yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { + def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines @@ -122,5 +109,3 @@ object SudokuIO { .zipWithIndex convertFromCellsToComplete(cellsIn) - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 1ab0200ec..aea108532 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -5,11 +5,10 @@ import java.io.File import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProblemSender { +object SudokuProblemSender: - enum Command { + enum Command: case SendNewSudoku - } export Command._ type CommandAndResponses = Command | SudokuSolver.Response @@ -26,12 +25,11 @@ object SudokuProblemSender { new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } }.narrow // Restrict the actor's [external] protocol to its set of commands -} class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context: ActorContext[SudokuProblemSender.CommandAndResponses], timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], - sudokuSolverSettings: SudokuSolverSettings) { + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender._ private val initialSudokuField = rowUpdates.toSudokuField @@ -83,4 +81,3 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], context.log.info(s"${SudokuIO.sudokuPrinter(solution)}") Behaviors.same } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 804a54185..4ab2464cd 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -3,18 +3,16 @@ package org.lunatechlabs.dotty.sudoku import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } import akka.actor.typed.{ ActorRef, Behavior } -object SudokuProgressTracker { +object SudokuProgressTracker: - enum Command { + enum Command: case NewUpdatesInFlight(count: Int) case SudokuDetailState(index: Int, state: ReductionSet) - } export Command._ // My responses - enum Response { + enum Response: case Result(sudoku: Sudoku) - } export Response._ def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], @@ -24,13 +22,12 @@ object SudokuProgressTracker { new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) .trackProgress(updatesInFlight = 0) } -} class SudokuProgressTracker private ( rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], context: ActorContext[SudokuProgressTracker.Command], sudokuSolver: ActorRef[SudokuProgressTracker.Response] -) { +): import SudokuProgressTracker._ @@ -44,7 +41,8 @@ class SudokuProgressTracker private ( case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) case msg: SudokuDetailState => - context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + // context.log.error("Received unexpected message in state 'trackProgress': {}", msg) + context.log.error(s"Received unexpected message in state 'trackProgress': ${msg}") Behaviors.same } @@ -62,7 +60,7 @@ class SudokuProgressTracker private ( case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) case msg: NewUpdatesInFlight => - context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + // context.log.error("Received unexpected message in state 'collectEndState': {}", msg) + context.log.error(s"Received unexpected message in state 'collectEndState': ${msg}") Behaviors.same } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 29265a3cd..3d3cc67d9 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -8,19 +8,17 @@ import scala.concurrent.duration._ final case class SudokuField(sudoku: Sudoku) -object SudokuSolver { +object SudokuSolver: // SudokuSolver Protocol - enum Command { + enum Command: case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], replyTo: ActorRef[SudokuSolver.Response]) - } export Command._ // My Responses - enum Response { + enum Response: case SudokuSolution(sudoku: Sudoku) - } export Response._ type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response @@ -29,7 +27,7 @@ object SudokuSolver { def genDetailProcessors[A <: SudokoDetailType: UpdateSender]( context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = import scala.language.implicitConversions cellIndexesVector .map { index => @@ -38,7 +36,6 @@ object SudokuSolver { (index, detailProcessor) } .to(Map) - } def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors @@ -54,11 +51,10 @@ object SudokuSolver { SupervisorStrategy .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) ).narrow -} class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], buffer: StashBuffer[SudokuSolver.CommandAndResponses] -) { +): import CellMappings._ import SudokuSolver._ @@ -84,7 +80,8 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) case unexpectedMsg => - context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + // context.log.error("Received an unexpected message in 'idle' state: {}", unexpectedMsg) + context.log.error(s"Received an unexpected message in 'idle' state: ${unexpectedMsg}") Behaviors.same } @@ -148,8 +145,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons } private def resetAllDetailProcessors(): Unit = - for { + for processors <- allDetailProcessors (_, processor) <- processors - } processor ! SudokuDetailProcessor.ResetSudokuDetailState -} + do processor ! SudokuDetailProcessor.ResetSudokuDetailState diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..af44845f6 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -4,20 +4,16 @@ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } -object SudokuSolverSettings { +object SudokuSolverSettings: def apply(configFile: String): SudokuSolverSettings = new SudokuSolverSettings(ConfigFactory.load(configFile)) -} -class SudokuSolverSettings(config: Config) { - object SudokuSolver { +class SudokuSolverSettings(config: Config): + object SudokuSolver: val StashBufferSize: Int = config.getInt("sudoku-solver.solver-stash-buffer-size") - } - object ProblemSender { + object ProblemSender: val SendInterval: FiniteDuration = Duration(config.getDuration("sudoku-solver.problem-sender.send-interval", Millis), Millis) - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index e17fe0846..0544ad358 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -12,49 +12,46 @@ type ReductionSet = Vector[CellContent] type Sudoku = Vector[ReductionSet] opaque type CellUpdates = Vector[(Int, Set[Int])] -object CellUpdates { +object CellUpdates: def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates: _*) -} val cellUpdatesEmpty: CellUpdates = Vector.empty[(Int, Set[Int])] given Eql[CellUpdates, CellUpdates] = Eql.derived -extension on (updates: CellUpdates) { +extension[A] (updates: CellUpdates) /** * Optionally, given that we only use `to(Map)`, we can create a non-generic extension method * For ex.: def toMap: Map[Int, Set[Int]] = updates.to(Map).withDefaultValue(Set(0)) */ - def to[C1](factory: Factory[(Int, Set[Int]), C1]): C1 = updates.to(factory) + def to(factory: Factory[(Int, Set[Int]), A]): A = updates.to(factory) - def foldLeft[B](z: B)(op: (B, (Int, Set[Int])) => B): B = updates.foldLeft(z)(op) + def foldLeft(z: A)(op: (A, (Int, Set[Int])) => A): A = updates.foldLeft(z)(op) - def foreach[U](f: ((Int, Set[Int])) => U): Unit = updates.foreach(f) + def foreach(f: ((Int, Set[Int])) => A): Unit = updates.foreach(f) def size: Int = updates.size -} def (update: (Int, Set[Int])) +: (updates: CellUpdates): CellUpdates = update +: updates import SudokuDetailProcessor.RowUpdate -def (update: Vector[SudokuDetailProcessor.RowUpdate]).toSudokuField: SudokuField = { +extension (update: Vector[SudokuDetailProcessor.RowUpdate]) def toSudokuField: SudokuField = import scala.language.implicitConversions val rows = update .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) - val sudoku = for { - (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) - x = cellUpdates.to(Map).withDefaultValue(Set(0)) - y = Vector.range(0, 9).map(n => x(n)) - } yield y - SudokuField(sudoku) -} + .to(Map).withDefaultValue(cellUpdatesEmpty) + val sudoku = for + (row, cellUpdates) <- Vector.range(0, 9).map(row => (row, rows(row))) + x = cellUpdates.to(Map).withDefaultValue(Set(0)) + y = Vector.range(0, 9).map(n => x(n)) + yield y + SudokuField(sudoku) // Collective Extensions: // define extension methods that share the same left-hand parameter type under a single extension instance. -extension sudokuFieldOps on (sudokuField: SudokuField) { +extension (sudokuField: SudokuField) def transpose: SudokuField = SudokuField(sudokuField.sudoku.transpose) @@ -66,7 +63,7 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW - def rowSwap(row1: Int, row2: Int): SudokuField = { + def rowSwap(row1: Int, row2: Int): SudokuField = SudokuField( sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) @@ -74,13 +71,11 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { case (row, _) => row } ) - } - def columnSwap(col1: Int, col2: Int): SudokuField = { + def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW - } - def randomSwapAround: SudokuField = { + def randomSwapAround: SudokuField = import scala.language.implicitConversions val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) // Generate a random swapping of cell values. A value 0 is used as a marker for a cell @@ -92,9 +87,8 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { SudokuField(sudokuField.sudoku.map { row => row.map(cell => Set(shuffledValuesMap(cell.head))) }) - } - def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = { + def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = sudokuField .sudoku .map(_.zipWithIndex) @@ -103,5 +97,3 @@ extension sudokuFieldOps on (sudokuField: SudokuField) { .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } - } -} diff --git a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala index 37c64bbfb..2f6a3e0e7 100644 --- a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala +++ b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/CellMappingSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import org.lunatechlabs.dotty.sudoku.CellMappings._ -class CellMappingSuite extends munit.FunSuite { +class CellMappingSuite extends munit.FunSuite: test("Mapping row coordinates should result in correct column & block coordinates") { assertEquals(rowToColumnCoordinates(0, 0), ((0, 0))) @@ -36,4 +36,3 @@ class CellMappingSuite extends munit.FunSuite { assertEquals(blockToRowCoordinates(5, 5), ((4, 8))) assertEquals(blockToColumnCoordinates(5, 5), ((8, 4))) } -} diff --git a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index e33f7cec4..d646d65d5 100644 --- a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -2,7 +2,7 @@ package org.lunatechlabs.dotty.sudoku import scala.language.implicitConversions -class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { +class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: test("Applying reduction rules should eliminate values in isolated complete sets from occurrences in other cells (First reduction rule)") { /** * Note: test data in this test is a "ReductionSet": is consists of 9 cells (e.g. in a @@ -187,4 +187,3 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(input), reducedInput) } -} diff --git a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 47e519581..417cf6774 100644 --- a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -5,13 +5,12 @@ import scala.language.implicitConversions import akka.actor.testkit.typed.scaladsl.ActorTestKit import java.nio.file._ -class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { +class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: val testKit: ActorTestKit = ActorTestKit() - override def afterAll(): Unit = { + override def afterAll(): Unit = testKit.shutdownTestKit() - } test("Sending no updates to a sudoku detail processor should result in sending a SudokuDetailUnchanged messsage") { @@ -48,7 +47,7 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) - val update1 = { + val update1 = val cellUpdates = stringToReductionSet(Vector( "12345678 ", @@ -62,11 +61,10 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { " 23 78 " )).zipWithIndex.map { _.swap} Update(CellUpdates(cellUpdates:_ *), detailParentProbe.ref) - } detailProcessor ! update1 - val reducedUpdate1 = { + val reducedUpdate1 = val cellUpdates = stringToReductionSet(Vector( " 23 56 ", @@ -81,7 +79,6 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { )).zipWithIndex.map(_.swap) BlockUpdate(2, CellUpdates(cellUpdates:_ *)) - } detailParentProbe.expectMessage(reducedUpdate1) @@ -128,4 +125,3 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) } -} diff --git a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 29a124357..0267766aa 100644 --- a/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_011_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -1,20 +1,17 @@ package org.lunatechlabs.dotty.sudoku -trait SudokuTestHelpers { +trait SudokuTestHelpers: - def stringToReductionSet(stringDef: Vector[String]): ReductionSet = { + def stringToReductionSet(stringDef: Vector[String]): ReductionSet = for { cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - } - def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = { + def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = val updates = for { (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) CellUpdates(updates:_ *) - } def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo -} diff --git a/exercises/project/Dependencies.scala b/exercises/project/Dependencies.scala index df48ae26b..ff81c4e16 100644 --- a/exercises/project/Dependencies.scala +++ b/exercises/project/Dependencies.scala @@ -23,8 +23,8 @@ import sbt._ object Version { val akkaVer = "2.6.8" val logbackVer = "1.2.3" - val mUnitVer = "0.7.10" - val scalaVersion = "0.26.0-RC1" + val mUnitVer = "0.7.12" + val scalaVersion = "0.27.0-RC1" } object Dependencies { diff --git a/exercises/project/build.properties b/exercises/project/build.properties index 654fe70c4..0837f7a13 100644 --- a/exercises/project/build.properties +++ b/exercises/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.12 +sbt.version=1.3.13