diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be4656572..91a21e3fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,9 +52,14 @@ jobs: cache: 'sbt' distribution: 'temurin' + - name: Check code formatting + run: sbt scalafmtCheckAll + working-directory: ${{env.EXERCISES_DIRECTORY}}/${{matrix.exercise}} + - name: Test with sbt run: sbt test working-directory: ${{env.EXERCISES_DIRECTORY}}/${{matrix.exercise}} + validate_course_summary: if: ${{ always() }} runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d3fa0add8..13130e16a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,8 @@ project/plugins/project/ *.sublime-workspace # OS specific -.DS_Store +**/.DS_Store +**/__MACOSX # Other Preci*.pdf diff --git a/exercises/exercise_000_sudoku_solver_initial_state/.scalafmt.conf b/exercises/exercise_000_sudoku_solver_initial_state/.scalafmt.conf new file mode 100644 index 000000000..6e0724822 --- /dev/null +++ b/exercises/exercise_000_sudoku_solver_initial_state/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala213 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_000_sudoku_solver_initial_state/IDE_setup.md b/exercises/exercise_000_sudoku_solver_initial_state/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_000_sudoku_solver_initial_state/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_000_sudoku_solver_initial_state/README.md b/exercises/exercise_000_sudoku_solver_initial_state/README.md index 473f03d63..aa201d0fc 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/README.md +++ b/exercises/exercise_000_sudoku_solver_initial_state/README.md @@ -1,5 +1,17 @@ # Initial State +## Documentation + +You may want to have look at the [Scala Language website](https://www3.scala-lang.org) +and bookmark it in your favourite browser. + +It has a number of links among which: + +- The [Scala 3 Language Reference](https://docs3.scala-lang.org/scala3/reference). +- The [Scala API documentation](https://docs3.scala-lang.org/api/all.html) for every version of Scala. +- The [Scala 3 Book](https://docs3.scala-lang.org/scala3/book/introduction.html) which + gives you a concise introduction to all things Scala 3. + ## An Akka Typed/Scala based Sudoku Solver ## Background @@ -123,3 +135,30 @@ Hit RETURN to stop solver - You can control the rate at which the Sudoku problem generator sends problems by tweaking the setting `sudoku-solver.problem-sender.send-interval` in the `sudokusolver.conf` configuration file. + +## Source code formatting & Markdown viewer in IntelliJ + + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ IDEA + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; +} +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) + diff --git a/exercises/exercise_000_sudoku_solver_initial_state/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_000_sudoku_solver_initial_state/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..05c714fa3 Binary files /dev/null and b/exercises/exercise_000_sudoku_solver_initial_state/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_000_sudoku_solver_initial_state/project/Build.scala b/exercises/exercise_000_sudoku_solver_initial_state/project/Build.scala index 2c1124a23..85eea50b0 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/project/Build.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/project/Build.scala @@ -14,7 +14,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -26,18 +26,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_000_sudoku_solver_initial_state/project/plugins.sbt b/exercises/exercise_000_sudoku_solver_initial_state/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_000_sudoku_solver_initial_state/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/resources/application.conf b/exercises/exercise_000_sudoku_solver_initial_state/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/resources/application.conf +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index b1ffc0919..67dc0f85e 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} import scala.io.StdIn object Main { @@ -36,13 +31,10 @@ object Main { // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } } diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 9229b68d7..ba502fd12 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -4,44 +4,39 @@ object ReductionRules { def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = { val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) - val completeInputCellGroups = inputCellsGrouped.filter { - case (set, setOccurrences) => set.size == setOccurrences.length + val completeInputCellGroups = inputCellsGrouped.filter { case (set, setOccurrences) => + set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList - completeAndIsolatedValueSets.foldLeft(reductionSet) { - case (cells, caivSet) => - cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell - } + completeAndIsolatedValueSets.foldLeft(reductionSet) { case (cells, caivSet) => + cells.map { cell => + if (cell != caivSet) cell &~ caivSet else cell + } } } 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 + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if (cell contains value) index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy { case (value, occurrence) => occurrence } - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy { case (value, occurrence) => occurrence }.filter { + case (loc, occ) => loc.length == occ.length && loc.length <= 6 + } - val cellIndexListToReducedValue = cellIndexesToValues.map { - case (index, seq) => (index, (seq.map { case (value, _) => value }).toSet) + val cellIndexListToReducedValue = cellIndexesToValues.map { case (index, seq) => + (index, seq.map { case (value, _) => value }.toSet) } - val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { - case (cellIndexList, reducedValue) => - cellIndexList.map(cellIndex => cellIndex -> reducedValue) + val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { case (cellIndexList, reducedValue) => + cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } } } diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 6233d137c..b01712963 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender object SudokuDetailProcessor { @@ -10,8 +10,7 @@ object SudokuDetailProcessor { sealed trait Command case object ResetSudokuDetailState extends Command final case class Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) extends Command - final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - extends Command + final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) extends Command // My responses sealed trait Response @@ -20,9 +19,8 @@ object SudokuDetailProcessor { final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -51,11 +49,10 @@ object SudokuDetailProcessor { } } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private( - context: ActorContext[SudokuDetailProcessor.Command] -) { +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]) { - import ReductionRules.{ reductionRuleOne, reductionRuleTwo } + import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor._ def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = @@ -66,21 +63,19 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { replyTo ! SudokuDetailUnchanged Behaviors.same - } - else { + } else { val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) if (transformedUpdatedState == state) { 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) => + case Update(_, replyTo) => replyTo ! SudokuDetailUnchanged Behaviors.same @@ -94,9 +89,8 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index d25970cf4..4da8476f0 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -1,40 +1,36 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO { private def sudokuCellRepresentation(content: CellContent): String = { content.toList match { - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " } } private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { val rowSubBlocks = for { row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString } yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") } def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -67,7 +63,7 @@ object SudokuIO { } override def next(): String = { - if (! hasNext) { + if (!hasNext) { throw new NoSuchElementException("No more lines in file") } val currentLine = cachedLine.get @@ -90,15 +86,14 @@ object SudokuIO { } yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .zipWithIndex convertFromCellsToComplete(cellsIn) diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 38bfa693e..5b18ab507 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender { @@ -13,13 +13,13 @@ object SudokuProblemSender { private final case class SolutionWrapper(result: SudokuSolver.Response) extends Command private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - SudokuIO - .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map { case (rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update) } + SudokuIO.readSudokuFromFile(new File("sudokus/001.sudoku")).map { case (rowIndex, update) => + SudokuDetailProcessor.RowUpdate(rowIndex, update) + } - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() @@ -27,11 +27,11 @@ object SudokuProblemSender { } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -) { +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] = @@ -64,15 +64,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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..e09f75846 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker { @@ -12,29 +12,26 @@ object SudokuProgressTracker { sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -) { + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]) { import SudokuProgressTracker._ def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach { - case (_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) + rowDetailProcessors.foreach { case (_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) } collectEndState() case NewUpdatesInFlight(updateCount) => @@ -44,16 +41,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 135ced47d..5e14e96b5 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration._ @@ -10,16 +10,15 @@ object SudokuSolver { // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver { import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -41,22 +39,17 @@ object SudokuSolver { def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) } -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -) { +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]) { import CellMappings._ import SudokuSolver._ @@ -72,17 +65,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -97,41 +87,42 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuDetailProcessorResponseWrapped(response) => response match { case SudokuDetailProcessor.RowUpdate(rowNr, updates) => - updates.foreach { - case (rowCellNr, newCellContent) => - val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (rowCellNr, newCellContent) => + val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.ColumnUpdate(columnNr, updates) => - updates.foreach { - case (colCellNr, newCellContent) => - val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (colCellNr, newCellContent) => + val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.BlockUpdate(blockNr, updates) => - updates.foreach { - case (blockCellNr, newCellContent) => - val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + updates.foreach { case (blockCellNr, newCellContent) => + val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -142,9 +133,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match { case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index f5f007fdb..da7f779fe 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS => Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS => Millis} object SudokuSolverSettings { diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala index 5d001cb8c..ea43091cf 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty package object sudoku { private val N = 9 - val CELLPossibleValues: Vector[Int] = (1 to N).toVector + val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) @@ -19,8 +19,7 @@ package object sudoku { import SudokuDetailProcessor.RowUpdate - implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) - extends AnyVal { + implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal { def toSudokuField: SudokuField = { val rows = update @@ -48,13 +47,11 @@ package object sudoku { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW @@ -78,9 +75,8 @@ package object sudoku { .map(row => row.filterNot(_._1 == Set(0))) .zipWithIndex .filter(_._1.nonEmpty) - .map { - case (c, i) => - RowUpdate(i, c.map(_.swap)) + .map { case (c, i) => + RowUpdate(i, c.map(_.swap)) } } } diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index 5b3d2c4ab..92ebbd509 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 3f57d7217..03209dfa8 100644 --- a/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_000_sudoku_solver_initial_state/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -20,100 +20,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -121,5 +100,5 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/.scalafmt.conf b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/IDE_setup.md b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/README.md b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/README.md index 751112338..8c9dda23a 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/README.md +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/README.md @@ -160,3 +160,29 @@ you may want to leave the latter enabled permanently in your Scala build, but take the first one out. By doing so, your attention will be drawn to code modifications that create issues (warning or errors) explicitly instead of them being corrected without you noticing them. + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/build.sbt b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/build.sbt +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/Build.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/Build.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/plugins.sbt b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/resources/application.conf b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/resources/application.conf +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index b1ffc0919..67dc0f85e 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} import scala.io.StdIn object Main { @@ -36,13 +31,10 @@ object Main { // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index 9229b68d7..ba502fd12 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -4,44 +4,39 @@ object ReductionRules { def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = { val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) - val completeInputCellGroups = inputCellsGrouped.filter { - case (set, setOccurrences) => set.size == setOccurrences.length + val completeInputCellGroups = inputCellsGrouped.filter { case (set, setOccurrences) => + set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList - completeAndIsolatedValueSets.foldLeft(reductionSet) { - case (cells, caivSet) => - cells.map { cell => - if (cell != caivSet) cell &~ caivSet else cell - } + completeAndIsolatedValueSets.foldLeft(reductionSet) { case (cells, caivSet) => + cells.map { cell => + if (cell != caivSet) cell &~ caivSet else cell + } } } 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 + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if (cell contains value) index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy { case (value, occurrence) => occurrence } - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy { case (value, occurrence) => occurrence }.filter { + case (loc, occ) => loc.length == occ.length && loc.length <= 6 + } - val cellIndexListToReducedValue = cellIndexesToValues.map { - case (index, seq) => (index, (seq.map { case (value, _) => value }).toSet) + val cellIndexListToReducedValue = cellIndexesToValues.map { case (index, seq) => + (index, seq.map { case (value, _) => value }.toSet) } - val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { - case (cellIndexList, reducedValue) => - cellIndexList.map(cellIndex => cellIndex -> reducedValue) + val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { case (cellIndexList, reducedValue) => + cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 3195019ba..cc4794b38 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender object SudokuDetailProcessor { @@ -10,8 +10,7 @@ object SudokuDetailProcessor { sealed trait Command case object ResetSudokuDetailState extends Command final case class Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) extends Command - final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - extends Command + final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) extends Command // My responses sealed trait Response @@ -20,9 +19,8 @@ object SudokuDetailProcessor { final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -51,11 +49,10 @@ object SudokuDetailProcessor { } } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private( - context: ActorContext[SudokuDetailProcessor.Command] -) { +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]) { - import ReductionRules.{ reductionRuleOne, reductionRuleTwo } + import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = @@ -66,21 +63,19 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat if (updatedState == previousState && cellUpdates != cellUpdatesEmpty) { replyTo ! SudokuDetailUnchanged Behaviors.same - } - else { + } else { val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) if (transformedUpdatedState == state) { 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) => + case Update(_, replyTo) => replyTo ! SudokuDetailUnchanged Behaviors.same @@ -94,9 +89,8 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index d25970cf4..4da8476f0 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -1,40 +1,36 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO { private def sudokuCellRepresentation(content: CellContent): String = { content.toList match { - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " } } private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = { val rowSubBlocks = for { row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString } yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") } def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = { - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") } /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String] { val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -67,7 +63,7 @@ object SudokuIO { } override def next(): String = { - if (! hasNext) { + if (!hasNext) { throw new NoSuchElementException("No more lines in file") } val currentLine = cachedLine.get @@ -90,15 +86,14 @@ object SudokuIO { } yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = { val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .zipWithIndex convertFromCellsToComplete(cellsIn) diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 360a773ca..fd550e14b 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender { @@ -13,13 +13,13 @@ object SudokuProblemSender { private final case class SolutionWrapper(result: SudokuSolver.Response) extends Command private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - SudokuIO - .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map { case (rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update) } + SudokuIO.readSudokuFromFile(new File("sudokus/001.sudoku")).map { case (rowIndex, update) => + SudokuDetailProcessor.RowUpdate(rowIndex, update) + } - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() @@ -27,11 +27,11 @@ object SudokuProblemSender { } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -) { +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] = @@ -64,15 +64,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 1103d6f63..b6b31c8d3 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker { @@ -12,29 +12,26 @@ object SudokuProgressTracker { sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -) { + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]) { import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach { - case (_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) + rowDetailProcessors.foreach { case (_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) } collectEndState() case NewUpdatesInFlight(updateCount) => @@ -44,16 +41,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 76d0b58d2..4cc17169c 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver { // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver { import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = { cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -41,22 +39,17 @@ object SudokuSolver { def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) } -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -) { +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]) { import CellMappings.* import SudokuSolver.* @@ -72,17 +65,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -97,41 +87,42 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuDetailProcessorResponseWrapped(response) => response match { case SudokuDetailProcessor.RowUpdate(rowNr, updates) => - updates.foreach { - case (rowCellNr, newCellContent) => - val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (rowCellNr, newCellContent) => + val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.ColumnUpdate(columnNr, updates) => - updates.foreach { - case (colCellNr, newCellContent) => - val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (colCellNr, newCellContent) => + val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.BlockUpdate(blockNr, updates) => - updates.foreach { - case (blockCellNr, newCellContent) => - val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + updates.foreach { case (blockCellNr, newCellContent) => + val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -142,9 +133,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match { case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) @@ -166,6 +155,6 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], private def checkHaha(s: String): Unit = { val haha = Symbol("Haha") val noHaha = Symbol("NoHaha") - if (s `startsWith` haha.name) println(haha.name) else println(noHaha.name) + if (s.`startsWith`(haha.name)) println(haha.name) else println(noHaha.name) } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index 8d43d00a1..4c082d2d9 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings { diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala index 5d001cb8c..ea43091cf 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty package object sudoku { private val N = 9 - val CELLPossibleValues: Vector[Int] = (1 to N).toVector + val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) @@ -19,8 +19,7 @@ package object sudoku { import SudokuDetailProcessor.RowUpdate - implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) - extends AnyVal { + implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal { def toSudokuField: SudokuField = { val rows = update @@ -48,13 +47,11 @@ package object sudoku { def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW @@ -78,9 +75,8 @@ package object sudoku { .map(row => row.filterNot(_._1 == Set(0))) .zipWithIndex .filter(_._1.nonEmpty) - .map { - case (c, i) => - RowUpdate(i, c.map(_.swap)) + .map { case (c, i) => + RowUpdate(i, c.map(_.swap)) } } } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index 5b3d2c4ab..92ebbd509 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers { assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index b0a303ac0..26362385f 100644 --- a/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_001_dotty_deprecated_syntax_rewriting/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -20,100 +20,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -121,5 +100,5 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers { detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/.scalafmt.conf b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/IDE_setup.md b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/README.md b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/README.md index 4c29033fe..1f3ea33e3 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/README.md +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/README.md @@ -56,4 +56,34 @@ documentation. - Repeat this by compiling the test code (`Test / compile`). - Explore the changes applied by the rewrites (you can use the `git diff` command for this). - - Repeat the process for another rewrite. + - Repeat the process for the next rewrite, so that you end up with code using the + New Control Structures syntax and the Fewer Braces syntax. + +> For the remainder of the exercises in this course, we will use the New Control +> Structure syntax and the Fewer Braces syntax. + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/build.sbt b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/build.sbt +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/Build.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/Build.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/plugins.sbt b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/resources/application.conf b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/resources/application.conf +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index 584068246..1cebda0ac 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} import scala.io.StdIn object Main: @@ -36,13 +31,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index aa832c5f8..2481df4e8 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -4,41 +4,36 @@ object ReductionRules: def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) - val completeInputCellGroups = inputCellsGrouped.filter { - case (set, setOccurrences) => set.size == setOccurrences.length + val completeInputCellGroups = inputCellsGrouped.filter { case (set, setOccurrences) => + set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList - completeAndIsolatedValueSets.foldLeft(reductionSet) { - case (cells, caivSet) => - cells.map { cell => - if cell != caivSet then cell &~ caivSet else cell - } + completeAndIsolatedValueSets.foldLeft(reductionSet) { case (cells, caivSet) => + cells.map { cell => + if cell != caivSet then cell &~ caivSet else cell + } } 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 then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy { case (value, occurrence) => occurrence } - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy { case (value, occurrence) => occurrence }.filter { + case (loc, occ) => loc.length == occ.length && loc.length <= 6 + } - val cellIndexListToReducedValue = cellIndexesToValues.map { - case (index, seq) => (index, (seq.map { case (value, _) => value }).toSet) + val cellIndexListToReducedValue = cellIndexesToValues.map { case (index, seq) => + (index, seq.map { case (value, _) => value }.toSet) } - val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { - case (cellIndexList, reducedValue) => - cellIndexList.map(cellIndex => cellIndex -> reducedValue) + val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { case (cellIndexList, reducedValue) => + cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index 4253b762a..587134881 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender object SudokuDetailProcessor: @@ -10,8 +10,7 @@ object SudokuDetailProcessor: sealed trait Command case object ResetSudokuDetailState extends Command final case class Update(cellUpdates: CellUpdates, replyTo: ActorRef[Response]) extends Command - final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) - extends Command + final case class GetSudokuDetailState(replyTo: ActorRef[SudokuProgressTracker.Command]) extends Command // My responses sealed trait Response @@ -20,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -46,11 +44,10 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private( - context: ActorContext[SudokuDetailProcessor.Command] -): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): - import ReductionRules.{ reductionRuleOne, reductionRuleTwo } + import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = @@ -71,7 +68,7 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - case Update(cellUpdates, replyTo) => + case Update(_, replyTo) => replyTo ! SudokuDetailUnchanged Behaviors.same @@ -85,9 +82,8 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = @@ -102,4 +98,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index 702d5be9a..68bd566e6 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -76,18 +70,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .zipWithIndex convertFromCellsToComplete(cellsIn) diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 9c2c6ef6f..22313fb54 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -13,24 +13,24 @@ object SudokuProblemSender: private final case class SolutionWrapper(result: SudokuSolver.Response) extends Command private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - SudokuIO - .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map { case (rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update) } + SudokuIO.readSudokuFromFile(new File("sudokus/001.sudoku")).map { case (rowIndex, update) => + SudokuDetailProcessor.RowUpdate(rowIndex, update) + } - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 5a4cb526d..ef7e7f1c6 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach { - case (_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) + rowDetailProcessors.foreach { case (_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) } collectEndState() case NewUpdatesInFlight(updateCount) => @@ -43,16 +40,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 c2a4f0b20..bf453c224 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -95,41 +85,42 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuDetailProcessorResponseWrapped(response) => response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => - updates.foreach { - case (rowCellNr, newCellContent) => - val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (rowCellNr, newCellContent) => + val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.ColumnUpdate(columnNr, updates) => - updates.foreach { - case (colCellNr, newCellContent) => - val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (colCellNr, newCellContent) => + val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.BlockUpdate(blockNr, updates) => - updates.foreach { - case (blockCellNr, newCellContent) => - val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + updates.foreach { case (blockCellNr, newCellContent) => + val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -139,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index 37032ae5f..4e072f9b8 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala index 33c33cf93..35197cdc6 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/main/scala/org/lunatechlabs/dotty/sudoku/package.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty package object sudoku: private val N = 9 - val CELLPossibleValues: Vector[Int] = (1 to N).toVector + val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) @@ -19,8 +19,7 @@ package object sudoku: import SudokuDetailProcessor.RowUpdate - implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) - extends AnyVal: + implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor.RowUpdate]) extends AnyVal: def toSudokuField: SudokuField = val rows = update @@ -46,13 +45,11 @@ package object sudoku: def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW @@ -75,8 +72,6 @@ package object sudoku: .map(row => row.filterNot(_._1 == Set(0))) .zipWithIndex .filter(_._1.nonEmpty) - .map { - case (c, i) => - RowUpdate(i, c.map(_.swap)) + .map { case (c, i) => + RowUpdate(i, c.map(_.swap)) } - diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 8a0d47dd4..a4c21a233 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } diff --git a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 6a0b5faea..90a4f9468 100644 --- a/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_002_dotty_new_syntax_and_indentation_based_syntax/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -5,14 +5,11 @@ trait SudokuTestHelpers: import org.lunatechlabs.dotty.sudoku.ReductionRules.{reductionRuleOne, reductionRuleTwo} def stringToReductionSet(stringDef: Vector[String]): ReductionSet = - for - cellString <- stringDef + for cellString <- stringDef yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet def stringToIndexedUpdate(stringDef: Vector[String]): CellUpdates = - for - (cellString, index) <- stringDef.zipWithIndex if cellString != "" + for (cellString, index) <- stringDef.zipWithIndex if cellString != "" yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionRuleTwo(reductionRuleOne(reductionSet)) - diff --git a/exercises/exercise_003_top_level_definitions/.scalafmt.conf b/exercises/exercise_003_top_level_definitions/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_003_top_level_definitions/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_003_top_level_definitions/IDE_setup.md b/exercises/exercise_003_top_level_definitions/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_003_top_level_definitions/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_003_top_level_definitions/README.md b/exercises/exercise_003_top_level_definitions/README.md index 7f1c83a68..69a0d91c8 100644 --- a/exercises/exercise_003_top_level_definitions/README.md +++ b/exercises/exercise_003_top_level_definitions/README.md @@ -53,4 +53,30 @@ Let's continue with the core topic of this exercise: - Run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass -- Verify that the application runs correctly \ No newline at end of file +- Verify that the application runs correctly + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_003_top_level_definitions/build.sbt b/exercises/exercise_003_top_level_definitions/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_003_top_level_definitions/build.sbt +++ b/exercises/exercise_003_top_level_definitions/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_003_top_level_definitions/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_003_top_level_definitions/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_003_top_level_definitions/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_003_top_level_definitions/project/Build.scala b/exercises/exercise_003_top_level_definitions/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_003_top_level_definitions/project/Build.scala +++ b/exercises/exercise_003_top_level_definitions/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_003_top_level_definitions/project/plugins.sbt b/exercises/exercise_003_top_level_definitions/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_003_top_level_definitions/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_003_top_level_definitions/src/main/resources/application.conf b/exercises/exercise_003_top_level_definitions/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_003_top_level_definitions/src/main/resources/application.conf +++ b/exercises/exercise_003_top_level_definitions/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 aa832c5f8..2481df4e8 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 @@ -4,41 +4,36 @@ object ReductionRules: def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) - val completeInputCellGroups = inputCellsGrouped.filter { - case (set, setOccurrences) => set.size == setOccurrences.length + val completeInputCellGroups = inputCellsGrouped.filter { case (set, setOccurrences) => + set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList - completeAndIsolatedValueSets.foldLeft(reductionSet) { - case (cells, caivSet) => - cells.map { cell => - if cell != caivSet then cell &~ caivSet else cell - } + completeAndIsolatedValueSets.foldLeft(reductionSet) { case (cells, caivSet) => + cells.map { cell => + if cell != caivSet then cell &~ caivSet else cell + } } 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 then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy { case (value, occurrence) => occurrence } - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy { case (value, occurrence) => occurrence }.filter { + case (loc, occ) => loc.length == occ.length && loc.length <= 6 + } - val cellIndexListToReducedValue = cellIndexesToValues.map { - case (index, seq) => (index, (seq.map { case (value, _) => value }).toSet) + val cellIndexListToReducedValue = cellIndexesToValues.map { case (index, seq) => + (index, seq.map { case (value, _) => value }.toSet) } - val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { - case (cellIndexList, reducedValue) => - cellIndexList.map(cellIndex => cellIndex -> reducedValue) + val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { case (cellIndexList, reducedValue) => + cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 d520d40eb..1df5f460d 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 @@ -19,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -48,52 +47,52 @@ object SudokuDetailProcessor: def processorName(id: Int): String = s"blk-processor-$id" } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same else - val updateSender = implicitly[UpdateSender[DetailType]] - updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) - operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - - case Update(cellUpdates, replyTo) => - replyTo ! SudokuDetailUnchanged - Behaviors.same + val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = implicitly[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -102,4 +101,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 9c2c6ef6f..22313fb54 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -13,24 +13,24 @@ object SudokuProblemSender: private final case class SolutionWrapper(result: SudokuSolver.Response) extends Command private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - SudokuIO - .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map { case (rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update) } + SudokuIO.readSudokuFromFile(new File("sudokus/001.sudoku")).map { case (rowIndex, update) => + SudokuDetailProcessor.RowUpdate(rowIndex, update) + } - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 5a4cb526d..ef7e7f1c6 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach { - case (_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) + rowDetailProcessors.foreach { case (_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) } collectEndState() case NewUpdatesInFlight(updateCount) => @@ -43,16 +40,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 c2a4f0b20..bf453c224 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -95,41 +85,42 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuDetailProcessorResponseWrapped(response) => response match case SudokuDetailProcessor.RowUpdate(rowNr, updates) => - updates.foreach { - case (rowCellNr, newCellContent) => - val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (rowCellNr, newCellContent) => + val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.ColumnUpdate(columnNr, updates) => - updates.foreach { - case (colCellNr, newCellContent) => - val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) - val blockUpdate = Vector(blockCellNr -> newCellContent) - blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) + updates.foreach { case (colCellNr, newCellContent) => + val (rowNr, rowCellNr) = columnToRowCoordinates(columnNr, colCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (blockNr, blockCellNr) = columnToBlockCoordinates(columnNr, colCellNr) + val blockUpdate = Vector(blockCellNr -> newCellContent) + blockDetailProcessors(blockNr) ! SudokuDetailProcessor.Update(blockUpdate, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same case SudokuDetailProcessor.BlockUpdate(blockNr, updates) => - updates.foreach { - case (blockCellNr, newCellContent) => - val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) - val rowUpdate = Vector(rowCellNr -> newCellContent) - rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) - - val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) - val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + updates.foreach { case (blockCellNr, newCellContent) => + val (rowNr, rowCellNr) = blockToRowCoordinates(blockNr, blockCellNr) + val rowUpdate = Vector(rowCellNr -> newCellContent) + rowDetailProcessors(rowNr) ! SudokuDetailProcessor.Update(rowUpdate, detailProcessorResponseMapper) + + val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) + val columnUpdate = Vector(columnCellNr -> newCellContent) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -139,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 ea79b93ea..426de584a 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -22,8 +22,9 @@ implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor. def toSudokuField: SudokuField = val rows = update - .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) + .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)) @@ -43,20 +44,18 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW def rowSwap(row1: Int, row2: Int): SudokuField = - SudokuField( - sudokuField.sudoku.zipWithIndex.map { + SudokuField(sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) case (_, `row2`) => sudokuField.sudoku(row1) - case (row, _) => row - } - ) + case (row, _) => row + }) def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -68,11 +67,11 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: }) def toRowUpdates: Vector[RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { case (c, i) => RowUpdate(i, c.map(_.swap)) } diff --git a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 100644 --- a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 9850cab50..e5fedcd63 100644 --- a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -20,100 +20,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) diff --git a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index d2da8c53b..e53415ef3 100644 --- a/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_003_top_level_definitions/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -9,11 +9,9 @@ trait SudokuTestHelpers: cellString <- stringDef } yield cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet - 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 = reductionRuleTwo(reductionRuleOne(reductionSet)) diff --git a/exercises/exercise_004_parameter_untupling/.scalafmt.conf b/exercises/exercise_004_parameter_untupling/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_004_parameter_untupling/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_004_parameter_untupling/IDE_setup.md b/exercises/exercise_004_parameter_untupling/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_004_parameter_untupling/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_004_parameter_untupling/README.md b/exercises/exercise_004_parameter_untupling/README.md index 50fe486c1..58dfc8052 100644 --- a/exercises/exercise_004_parameter_untupling/README.md +++ b/exercises/exercise_004_parameter_untupling/README.md @@ -55,4 +55,30 @@ pairs.map(_ + _) - Run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass -- Verify that the application runs correctly \ No newline at end of file +- Verify that the application runs correctly + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_004_parameter_untupling/build.sbt b/exercises/exercise_004_parameter_untupling/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_004_parameter_untupling/build.sbt +++ b/exercises/exercise_004_parameter_untupling/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_004_parameter_untupling/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_004_parameter_untupling/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_004_parameter_untupling/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_004_parameter_untupling/project/Build.scala b/exercises/exercise_004_parameter_untupling/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_004_parameter_untupling/project/Build.scala +++ b/exercises/exercise_004_parameter_untupling/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_004_parameter_untupling/project/plugins.sbt b/exercises/exercise_004_parameter_untupling/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_004_parameter_untupling/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_004_parameter_untupling/src/main/resources/application.conf b/exercises/exercise_004_parameter_untupling/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_004_parameter_untupling/src/main/resources/application.conf +++ b/exercises/exercise_004_parameter_untupling/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b1808ac20..da95962d4 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 @@ -4,7 +4,7 @@ object ReductionRules: def reductionRuleOne(reductionSet: ReductionSet): ReductionSet = val inputCellsGrouped = reductionSet.filter(_.size <= 7).groupBy(identity) - val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -15,28 +15,25 @@ object ReductionRules: } 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 then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 d520d40eb..1df5f460d 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 @@ -19,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -48,52 +47,52 @@ object SudokuDetailProcessor: def processorName(id: Int): String = s"blk-processor-$id" } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import ReductionRules.{reductionRuleOne, reductionRuleTwo} import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same else - val updateSender = implicitly[UpdateSender[DetailType]] - updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) - operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - - case Update(cellUpdates, replyTo) => - replyTo ! SudokuDetailUnchanged - Behaviors.same + val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState)) + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = implicitly[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -102,4 +101,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 0c8956d97..1a6098a28 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -15,22 +15,22 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 0ed573929..e9a03e1e8 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -42,16 +39,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 f7d996c46..dd64e0f37 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -98,7 +88,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) val blockUpdate = Vector(blockCellNr -> newCellContent) @@ -126,7 +118,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -136,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 6a19c8d66..9575ed7db 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -22,8 +22,9 @@ implicit class RowUpdatesToSudokuField(val update: Vector[SudokuDetailProcessor. def toSudokuField: SudokuField = val rows = update - .map { case SudokuDetailProcessor.RowUpdate(id, cellUpdates) => (id, cellUpdates)} - .to(Map).withDefaultValue(cellUpdatesEmpty) + .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)) @@ -43,20 +44,18 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW def rowSwap(row1: Int, row2: Int): SudokuField = - SudokuField( - sudokuField.sudoku.zipWithIndex.map { + SudokuField(sudokuField.sudoku.zipWithIndex.map { case (_, `row1`) => sudokuField.sudoku(row2) case (_, `row2`) => sudokuField.sudoku(row1) - case (row, _) => row - } - ) + case (row, _) => row + }) def columnSwap(col1: Int, col2: Int): SudokuField = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -68,11 +67,11 @@ implicit class SudokuFieldOps(val sudokuField: SudokuField) extends AnyVal: }) def toRowUpdates: Vector[RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => RowUpdate(i, c.map(_.swap)) } diff --git a/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 100644 --- a/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 402332155..e5fedcd63 100644 --- a/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_004_parameter_untupling/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -20,100 +20,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -121,4 +100,4 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } diff --git a/exercises/exercise_005_extension_methods/.scalafmt.conf b/exercises/exercise_005_extension_methods/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_005_extension_methods/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_005_extension_methods/IDE_setup.md b/exercises/exercise_005_extension_methods/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_005_extension_methods/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_005_extension_methods/README.md b/exercises/exercise_005_extension_methods/README.md index d6fe6fe65..beeddd8c7 100644 --- a/exercises/exercise_005_extension_methods/README.md +++ b/exercises/exercise_005_extension_methods/README.md @@ -67,11 +67,36 @@ part of this exercise (adding the new extension methods). exercise, doesn't compile. Figure out what's wrong (or rather, what's missing; remember we're doing an exercise on extension methods). Fix the problem (and don't change the test code). - + > Tip: consider creating new extension methods (names as suggested by the tests) at the `org.lunatechlabs.dotty.sudoku` package level. - + - Run the tests and make adjustments to your code until they pass. - Verify that the application runs correctly. +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_005_extension_methods/build.sbt b/exercises/exercise_005_extension_methods/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_005_extension_methods/build.sbt +++ b/exercises/exercise_005_extension_methods/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_005_extension_methods/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_005_extension_methods/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_005_extension_methods/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_005_extension_methods/project/Build.scala b/exercises/exercise_005_extension_methods/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_005_extension_methods/project/Build.scala +++ b/exercises/exercise_005_extension_methods/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_005_extension_methods/project/plugins.sbt b/exercises/exercise_005_extension_methods/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_005_extension_methods/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_005_extension_methods/src/main/resources/application.conf b/exercises/exercise_005_extension_methods/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_005_extension_methods/src/main/resources/application.conf +++ b/exercises/exercise_005_extension_methods/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b7fe9d5bb..b528b39a5 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 @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 72b5218c3..96ed8e406 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 @@ -19,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)( - implicit updateSender: UpdateSender[DetailType] - ): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(implicit + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -48,51 +47,51 @@ object SudokuDetailProcessor: def processorName(id: Int): String = s"blk-processor-$id" } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same else - val updateSender = implicitly[UpdateSender[DetailType]] - updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) - operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) - - case Update(cellUpdates, replyTo) => - replyTo ! SudokuDetailUnchanged - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = implicitly[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -101,4 +100,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 0c8956d97..1a6098a28 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -15,22 +15,22 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 0ed573929..e9a03e1e8 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -42,16 +39,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 f7d996c46..dd64e0f37 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = implicitly[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -98,7 +88,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) val blockUpdate = Vector(blockCellNr -> newCellContent) @@ -126,7 +118,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -136,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 3eb654ec3..200654935 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -21,9 +21,10 @@ 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) + 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)) @@ -46,20 +47,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -71,11 +70,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } 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 fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 7b7bec8cb..d7a893ae8 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_006_using_and_summon/.scalafmt.conf b/exercises/exercise_006_using_and_summon/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_006_using_and_summon/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_006_using_and_summon/IDE_setup.md b/exercises/exercise_006_using_and_summon/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_006_using_and_summon/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_006_using_and_summon/README.md b/exercises/exercise_006_using_and_summon/README.md index e0f36d51a..9879a78c3 100644 --- a/exercises/exercise_006_using_and_summon/README.md +++ b/exercises/exercise_006_using_and_summon/README.md @@ -27,4 +27,30 @@ use of _scala 2_'s `implicit`s and `implicitly`. - Run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass -- Verify that the application runs correctly \ No newline at end of file +- Verify that the application runs correctly + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_006_using_and_summon/build.sbt b/exercises/exercise_006_using_and_summon/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_006_using_and_summon/build.sbt +++ b/exercises/exercise_006_using_and_summon/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_006_using_and_summon/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_006_using_and_summon/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_006_using_and_summon/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_006_using_and_summon/project/Build.scala b/exercises/exercise_006_using_and_summon/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_006_using_and_summon/project/Build.scala +++ b/exercises/exercise_006_using_and_summon/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_006_using_and_summon/project/plugins.sbt b/exercises/exercise_006_using_and_summon/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_006_using_and_summon/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_006_using_and_summon/src/main/resources/application.conf b/exercises/exercise_006_using_and_summon/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_006_using_and_summon/src/main/resources/application.conf +++ b/exercises/exercise_006_using_and_summon/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b7fe9d5bb..b528b39a5 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 @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 6462a8267..a30798e3e 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 @@ -19,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -48,51 +47,51 @@ object SudokuDetailProcessor: def processorName(id: Int): String = s"blk-processor-$id" } -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -101,4 +100,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 0c8956d97..1a6098a28 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -15,22 +15,22 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 0ed573929..e9a03e1e8 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -42,16 +39,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 855573860..844fabfab 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -98,7 +88,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) val blockUpdate = Vector(blockCellNr -> newCellContent) @@ -126,7 +118,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -136,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 3eb654ec3..200654935 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -21,9 +21,10 @@ 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) + 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)) @@ -46,20 +47,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -71,11 +70,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .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/ReductionRuleSuite.scala b/exercises/exercise_006_using_and_summon/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 7b7bec8cb..d7a893ae8 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_007_givens/.scalafmt.conf b/exercises/exercise_007_givens/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_007_givens/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_007_givens/IDE_setup.md b/exercises/exercise_007_givens/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_007_givens/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_007_givens/README.md b/exercises/exercise_007_givens/README.md index 637bc4605..f5b53c4d0 100644 --- a/exercises/exercise_007_givens/README.md +++ b/exercises/exercise_007_givens/README.md @@ -32,3 +32,29 @@ secondly because these names don't really have a useful application in most case and verify that all tests pass. - Verify that the application runs correctly. + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_007_givens/build.sbt b/exercises/exercise_007_givens/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_007_givens/build.sbt +++ b/exercises/exercise_007_givens/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_007_givens/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_007_givens/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_007_givens/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_007_givens/project/Build.scala b/exercises/exercise_007_givens/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_007_givens/project/Build.scala +++ b/exercises/exercise_007_givens/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_007_givens/project/plugins.sbt b/exercises/exercise_007_givens/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_007_givens/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_007_givens/src/main/resources/application.conf b/exercises/exercise_007_givens/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_007_givens/src/main/resources/application.conf +++ b/exercises/exercise_007_givens/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b7fe9d5bb..b528b39a5 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 @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 710b2b252..5c4a61b4e 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 @@ -19,9 +19,8 @@ object SudokuDetailProcessor: final case class BlockUpdate(id: Int, cellUpdates: CellUpdates) extends Response case object SudokuDetailUnchanged extends Response - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -45,54 +44,51 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -101,4 +97,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 0c8956d97..1a6098a28 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -15,22 +15,22 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -63,15 +63,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 0ed573929..e9a03e1e8 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -12,28 +12,25 @@ object SudokuProgressTracker: sealed trait Response final case class Result(sudoku: Sudoku) extends Response - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -42,16 +39,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 855573860..844fabfab 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,16 +10,15 @@ object SudokuSolver: // SudokuSolver Protocol sealed trait Command - final case class InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response] - ) extends Command + final case class InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + extends Command // Wrapped responses - private final case class SudokuDetailProcessorResponseWrapped( - response: SudokuDetailProcessor.Response - ) extends Command - private final case class SudokuProgressTrackerResponseWrapped( - response: SudokuProgressTracker.Response - ) extends Command + private final case class SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) + extends Command + private final case class SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) + extends Command // My Responses sealed trait Response final case class SudokuSolution(sudoku: Sudoku) extends Response @@ -27,8 +26,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -40,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -70,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -98,7 +88,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) val blockUpdate = Vector(blockCellNr -> newCellContent) @@ -126,7 +118,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -136,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 3eb654ec3..200654935 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -21,9 +21,10 @@ 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) + 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)) @@ -46,20 +47,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -71,11 +70,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } 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 fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 7b7bec8cb..d7a893ae8 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_008_enum_and_export/.scalafmt.conf b/exercises/exercise_008_enum_and_export/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_008_enum_and_export/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_008_enum_and_export/IDE_setup.md b/exercises/exercise_008_enum_and_export/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_008_enum_and_export/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_008_enum_and_export/README.md b/exercises/exercise_008_enum_and_export/README.md index 4c4b16487..e6bc145c9 100644 --- a/exercises/exercise_008_enum_and_export/README.md +++ b/exercises/exercise_008_enum_and_export/README.md @@ -68,11 +68,37 @@ in this exercise. will remove these in the next exercise. You may have to change the `ResponseWrapper` members from `private` to `public`. Again, this is not really desirable, but we'll fix this in the exercise on Union types. - + - Use Scala 3's `export` feature to avoid having to refactor the code to utilise qualified references to the messages. - Run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass. - + - Verify that the application runs correctly. + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_008_enum_and_export/build.sbt b/exercises/exercise_008_enum_and_export/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_008_enum_and_export/build.sbt +++ b/exercises/exercise_008_enum_and_export/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_008_enum_and_export/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_008_enum_and_export/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_008_enum_and_export/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_008_enum_and_export/project/Build.scala b/exercises/exercise_008_enum_and_export/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_008_enum_and_export/project/Build.scala +++ b/exercises/exercise_008_enum_and_export/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_008_enum_and_export/project/plugins.sbt b/exercises/exercise_008_enum_and_export/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_008_enum_and_export/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_008_enum_and_export/src/main/resources/application.conf b/exercises/exercise_008_enum_and_export/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_008_enum_and_export/src/main/resources/application.conf +++ b/exercises/exercise_008_enum_and_export/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b7fe9d5bb..b528b39a5 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 @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 d502b0b05..2a41a2edd 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 @@ -21,9 +21,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -47,54 +46,51 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -103,4 +99,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 8028b4686..7f7890c17 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -16,22 +16,22 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings - ): Behavior[Command] = + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() } } -class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], - context: ActorContext[SudokuProblemSender.Command], - timers: TimerScheduler[SudokuProblemSender.Command], - sudokuSolverSettings: SudokuSolverSettings -): +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] = @@ -64,15 +64,14 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms def sending(): Behavior[Command] = 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 6e36e459a..7968e5f84 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 e7330b85b..6a3e8ba13 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,9 +10,10 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) - // Wrapped responses + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) + // Wrapped responses case SudokuDetailProcessorResponseWrapped(response: SudokuDetailProcessor.Response) case SudokuProgressTrackerResponseWrapped(response: SudokuProgressTracker.Response) export Command.* @@ -21,12 +22,11 @@ object SudokuSolver: enum Response: case SudokuSolution(sudoku: Sudoku) export Response.* - + import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[Command] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[Command]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -38,21 +38,16 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[Command] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ) + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) -class SudokuSolver private (context: ActorContext[SudokuSolver.Command], - buffer: StashBuffer[SudokuSolver.Command] -): +class SudokuSolver private (context: ActorContext[SudokuSolver.Command], buffer: StashBuffer[SudokuSolver.Command]): import CellMappings.* import SudokuSolver.* @@ -68,17 +63,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, progressTrackerResponseMapper), "sudoku-progress-tracker") def idle(): Behavior[Command] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -96,7 +88,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], updates.foreach { (rowCellNr, newCellContent) => val (columnNr, columnCellNr) = rowToColumnCoordinates(rowNr, rowCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) val (blockNr, blockCellNr) = rowToBlockCoordinates(rowNr, rowCellNr) val blockUpdate = Vector(blockCellNr -> newCellContent) @@ -124,7 +118,9 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], val (columnNr, columnCellNr) = blockToColumnCoordinates(blockNr, blockCellNr) val columnUpdate = Vector(columnCellNr -> newCellContent) - columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update(columnUpdate, detailProcessorResponseMapper) + columnDetailProcessors(columnNr) ! SudokuDetailProcessor.Update( + columnUpdate, + detailProcessorResponseMapper) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(2 * updates.size - 1) Behaviors.same @@ -134,9 +130,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.Command], case SudokuProgressTrackerResponseWrapped(result) => result match case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 3eb654ec3..200654935 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -21,9 +21,10 @@ 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) + 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)) @@ -46,20 +47,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -71,11 +70,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .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/ReductionRuleSuite.scala b/exercises/exercise_008_enum_and_export/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 7b7bec8cb..d7a893ae8 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_009_union_types/.scalafmt.conf b/exercises/exercise_009_union_types/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_009_union_types/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_009_union_types/IDE_setup.md b/exercises/exercise_009_union_types/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_009_union_types/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_009_union_types/README.md b/exercises/exercise_009_union_types/README.md index 8a6cb1b2b..88f9bbdcc 100644 --- a/exercises/exercise_009_union_types/README.md +++ b/exercises/exercise_009_union_types/README.md @@ -68,3 +68,29 @@ Which of these actors receive messages that are responses from other actors? in the previous exercise for pure technical reasons. - Verify that the application runs correctly + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_009_union_types/build.sbt b/exercises/exercise_009_union_types/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_009_union_types/build.sbt +++ b/exercises/exercise_009_union_types/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_009_union_types/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_009_union_types/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_009_union_types/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_009_union_types/project/Build.scala b/exercises/exercise_009_union_types/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_009_union_types/project/Build.scala +++ b/exercises/exercise_009_union_types/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_009_union_types/project/plugins.sbt b/exercises/exercise_009_union_types/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_009_union_types/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_009_union_types/src/main/resources/application.conf b/exercises/exercise_009_union_types/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_009_union_types/src/main/resources/application.conf +++ b/exercises/exercise_009_union_types/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } 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 b7fe9d5bb..b528b39a5 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 @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { 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 d502b0b05..2a41a2edd 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 @@ -21,9 +21,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -47,54 +46,51 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -103,4 +99,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat 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 e452c6a8f..a9751144f 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 @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 4a199bacb..b09fa4245 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -16,20 +16,24 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = - Behaviors.setup[CommandAndResponses] { context => - Behaviors.withTimers { timers => - new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = + Behaviors + .setup[CommandAndResponses] { context => + Behaviors.withTimers { timers => + new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + } } - }.narrow // Restrict the actor's [external] protocol to its set of commands + .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): +class SudokuProblemSender private ( + sudokuSolver: ActorRef[SudokuSolver.Command], + context: ActorContext[SudokuProblemSender.CommandAndResponses], + timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender.* private val initialSudokuField = rowUpdates.toSudokuField @@ -59,18 +63,17 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms - def sending(): Behavior[CommandAndResponses] = + def sending(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case SendNewSudoku => context.log.debug("sending new sudoku problem") 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 6e36e459a..7968e5f84 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 8026dcc93..5aef42003 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,8 +10,9 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) export Command.InitialRowUpdates // My Responses @@ -20,12 +21,11 @@ object SudokuSolver: export Response.SudokuSolution type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response - + import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[CommandAndResponses]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -37,21 +37,19 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[CommandAndResponses] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ).narrow + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) + .narrow -class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], - buffer: StashBuffer[SudokuSolver.CommandAndResponses] -): +class SudokuSolver private ( + context: ActorContext[SudokuSolver.CommandAndResponses], + buffer: StashBuffer[SudokuSolver.CommandAndResponses]): import CellMappings.* import SudokuSolver.* @@ -62,17 +60,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), "sudoku-progress-tracker") def idle(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -125,9 +120,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons Behaviors.same case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 3eb654ec3..200654935 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -21,9 +21,10 @@ 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) + 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)) @@ -46,20 +47,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -71,11 +70,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } 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 fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 7b7bec8cb..d7a893ae8 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_010_opaque_type_aliases/.scalafmt.conf b/exercises/exercise_010_opaque_type_aliases/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_010_opaque_type_aliases/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_010_opaque_type_aliases/IDE_setup.md b/exercises/exercise_010_opaque_type_aliases/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_010_opaque_type_aliases/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_010_opaque_type_aliases/README.md b/exercises/exercise_010_opaque_type_aliases/README.md index 31fe81be9..b2e09e7f7 100644 --- a/exercises/exercise_010_opaque_type_aliases/README.md +++ b/exercises/exercise_010_opaque_type_aliases/README.md @@ -105,3 +105,29 @@ There are more type aliases in the source code that potentially could be convert opaque versions. One that sticks out is the `Sudoku` type alias (`type Sudoku = Vector[ReductionSet`). You may want to take a stab at converting it... +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) + diff --git a/exercises/exercise_010_opaque_type_aliases/build.sbt b/exercises/exercise_010_opaque_type_aliases/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_010_opaque_type_aliases/build.sbt +++ b/exercises/exercise_010_opaque_type_aliases/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_010_opaque_type_aliases/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_010_opaque_type_aliases/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_010_opaque_type_aliases/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_010_opaque_type_aliases/project/Build.scala b/exercises/exercise_010_opaque_type_aliases/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_010_opaque_type_aliases/project/Build.scala +++ b/exercises/exercise_010_opaque_type_aliases/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_010_opaque_type_aliases/project/plugins.sbt b/exercises/exercise_010_opaque_type_aliases/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_010_opaque_type_aliases/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/resources/application.conf b/exercises/exercise_010_opaque_type_aliases/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/resources/application.conf +++ b/exercises/exercise_010_opaque_type_aliases/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } diff --git a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala index cd9dbcc23..37592076e 100644 --- a/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala +++ b/exercises/exercise_010_opaque_type_aliases/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala @@ -10,8 +10,8 @@ object ReductionSet: extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -22,42 +22,38 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } def mergeState(cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(reductionSet) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(reductionSet) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } def stateChanges(updatedState: ReductionSet): CellUpdates = - (reductionSet zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + reductionSet.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -76,10 +72,9 @@ end extension // ReductionSet private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " - + case _ => " " extension (vrs: Vector[ReductionSet]) def printSudokuRow: String = @@ -87,7 +82,6 @@ extension (vrs: Vector[ReductionSet]) row <- vrs rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") @@ -106,36 +100,34 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated // Map to that effect val shuffledValuesMap = - possibleCellValues.zip(scala.util.Random.shuffle(possibleCellValues)).to(Map) + (0 -> 0) + possibleCellValues.zip(scala.util.Random.shuffle(possibleCellValues)).to(Map) + (0 -> 0) SudokuField(sudokuField.sudoku.map { row => row.swapValues(shuffledValuesMap) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } 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 2b0be573c..7f68cac6c 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 @@ -21,9 +21,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -47,42 +46,38 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = state.mergeState(cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = state.mergeState(cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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, state.stateChanges(transformedUpdatedState))(using replyTo) - operational(id, transformedUpdatedState, transformedUpdatedState.isFullyReduced) - - case Update(cellUpdates, replyTo) => - replyTo ! SudokuDetailUnchanged - Behaviors.same - - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same - - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, state.stateChanges(transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, transformedUpdatedState.isFullyReduced) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - } + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) + } 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 dd14b8b88..81f7a4203 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 @@ -1,21 +1,17 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(_.printSudokuRow) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(_.printSudokuRow).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -45,8 +41,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -62,18 +57,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 4a199bacb..b09fa4245 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -16,20 +16,24 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = - Behaviors.setup[CommandAndResponses] { context => - Behaviors.withTimers { timers => - new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = + Behaviors + .setup[CommandAndResponses] { context => + Behaviors.withTimers { timers => + new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + } } - }.narrow // Restrict the actor's [external] protocol to its set of commands + .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): +class SudokuProblemSender private ( + sudokuSolver: ActorRef[SudokuSolver.Command], + context: ActorContext[SudokuProblemSender.CommandAndResponses], + timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender.* private val initialSudokuField = rowUpdates.toSudokuField @@ -59,18 +63,17 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms - def sending(): Behavior[CommandAndResponses] = + def sending(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case SendNewSudoku => context.log.debug("sending new sudoku problem") 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 6e36e459a..7968e5f84 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 8026dcc93..5aef42003 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,8 +10,9 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) export Command.InitialRowUpdates // My Responses @@ -20,12 +21,11 @@ object SudokuSolver: export Response.SudokuSolution type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response - + import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[CommandAndResponses]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -37,21 +37,19 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[CommandAndResponses] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ).narrow + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) + .narrow -class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], - buffer: StashBuffer[SudokuSolver.CommandAndResponses] -): +class SudokuSolver private ( + context: ActorContext[SudokuSolver.CommandAndResponses], + buffer: StashBuffer[SudokuSolver.CommandAndResponses]): import CellMappings.* import SudokuSolver.* @@ -62,17 +60,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), "sudoku-progress-tracker") def idle(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -125,9 +120,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons Behaviors.same case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 d277ad063..0e3ace015 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) @@ -19,13 +19,13 @@ 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) + 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 = ReductionSet(Vector.range(0, 9).map(n => x(n))) yield y SudokuField(sudoku) - 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 fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 51e02f34a..61b580979 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_011_multiversal_equality/.scalafmt.conf b/exercises/exercise_011_multiversal_equality/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_011_multiversal_equality/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_011_multiversal_equality/IDE_setup.md b/exercises/exercise_011_multiversal_equality/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_011_multiversal_equality/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_011_multiversal_equality/README.md b/exercises/exercise_011_multiversal_equality/README.md index 11de47b3f..e801fcb34 100644 --- a/exercises/exercise_011_multiversal_equality/README.md +++ b/exercises/exercise_011_multiversal_equality/README.md @@ -38,3 +38,28 @@ error. - Run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_011_multiversal_equality/build.sbt b/exercises/exercise_011_multiversal_equality/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_011_multiversal_equality/build.sbt +++ b/exercises/exercise_011_multiversal_equality/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_011_multiversal_equality/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_011_multiversal_equality/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_011_multiversal_equality/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_011_multiversal_equality/project/Build.scala b/exercises/exercise_011_multiversal_equality/project/Build.scala index 5ed67ad55..81e97095d 100644 --- a/exercises/exercise_011_multiversal_equality/project/Build.scala +++ b/exercises/exercise_011_multiversal_equality/project/Build.scala @@ -16,7 +16,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -28,18 +28,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_011_multiversal_equality/project/plugins.sbt b/exercises/exercise_011_multiversal_equality/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_011_multiversal_equality/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_011_multiversal_equality/src/main/resources/application.conf b/exercises/exercise_011_multiversal_equality/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/resources/application.conf +++ b/exercises/exercise_011_multiversal_equality/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" 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 9847e58de..5babffb1e 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 @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } diff --git a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala index bc46d6486..4b34b5fbf 100644 --- a/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala +++ b/exercises/exercise_011_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionSet.scala @@ -2,7 +2,6 @@ package org.lunatechlabs.dotty.sudoku opaque type ReductionSet = Vector[CellContent] - val InitialDetailState: ReductionSet = cellIndexesVector.map(_ => initialCell) given CanEqual[ReductionSet, ReductionSet] = CanEqual.derived @@ -13,8 +12,8 @@ object ReductionSet: extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -25,42 +24,38 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } def mergeState(cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(reductionSet) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(reductionSet) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } def stateChanges(updatedState: ReductionSet): CellUpdates = - (reductionSet zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + reductionSet.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -79,10 +74,9 @@ end extension // ReductionSet private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " - + case _ => " " extension (vrs: Vector[ReductionSet]) def printSudokuRow: String = @@ -90,7 +84,6 @@ extension (vrs: Vector[ReductionSet]) row <- vrs rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") @@ -109,36 +102,34 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated // Map to that effect val shuffledValuesMap = - possibleCellValues.zip(scala.util.Random.shuffle(possibleCellValues)).to(Map) + (0 -> 0) + possibleCellValues.zip(scala.util.Random.shuffle(possibleCellValues)).to(Map) + (0 -> 0) SudokuField(sudokuField.sudoku.map { row => row.swapValues(shuffledValuesMap) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } 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 6b6c3f138..dc0e0507c 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,7 +4,6 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.{ActorRef, Behavior} import org.lunatechlabs.dotty.sudoku.SudokuDetailProcessor.UpdateSender - object SudokuDetailProcessor: // My protocol @@ -15,7 +14,7 @@ object SudokuDetailProcessor: export Command.* given CanEqual[Command, Command] = CanEqual.derived - + // My responses enum Response: case RowUpdate(id: Int, cellUpdates: CellUpdates) @@ -24,9 +23,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -50,42 +48,38 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = state.mergeState(cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = state.mergeState(cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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, state.stateChanges(transformedUpdatedState))(using replyTo) - operational(id, transformedUpdatedState, transformedUpdatedState.isFullyReduced) - - case Update(cellUpdates, replyTo) => - replyTo ! SudokuDetailUnchanged - Behaviors.same - - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same - - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, state.stateChanges(transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, transformedUpdatedState.isFullyReduced) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - } + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) + } 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 dd14b8b88..81f7a4203 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 @@ -1,21 +1,17 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(_.printSudokuRow) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(_.printSudokuRow).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -45,8 +41,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -62,18 +57,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .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 d56931599..5618ac012 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 @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -18,20 +18,24 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = - Behaviors.setup[CommandAndResponses] { context => - Behaviors.withTimers { timers => - new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = + Behaviors + .setup[CommandAndResponses] { context => + Behaviors.withTimers { timers => + new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + } } - }.narrow // Restrict the actor's [external] protocol to its set of commands + .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): +class SudokuProblemSender private ( + sudokuSolver: ActorRef[SudokuSolver.Command], + context: ActorContext[SudokuProblemSender.CommandAndResponses], + timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender.* private val initialSudokuField = rowUpdates.toSudokuField @@ -61,18 +65,17 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms - def sending(): Behavior[CommandAndResponses] = + def sending(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case SendNewSudoku => context.log.debug("sending new sudoku problem") 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 6e36e459a..7968e5f84 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) 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 4b0be6247..6c1801d4c 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,8 +10,9 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) export Command.InitialRowUpdates // My Responses @@ -22,12 +23,11 @@ object SudokuSolver: type CommandAndResponses = Command | SudokuDetailProcessor.Response | SudokuProgressTracker.Response given CanEqual[SudokuDetailProcessor.Response, CommandAndResponses] = CanEqual.derived - + import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[CommandAndResponses]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -39,21 +39,19 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[CommandAndResponses] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ).narrow + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) + .narrow -class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], - buffer: StashBuffer[SudokuSolver.CommandAndResponses] -): +class SudokuSolver private ( + context: ActorContext[SudokuSolver.CommandAndResponses], + buffer: StashBuffer[SudokuSolver.CommandAndResponses]): import CellMappings.* import SudokuSolver.* @@ -64,17 +62,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), "sudoku-progress-tracker") def idle(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -127,9 +122,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons Behaviors.same case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) 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 37032ae5f..4e072f9b8 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 @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: 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 d277ad063..0e3ace015 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 @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) @@ -19,13 +19,13 @@ 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) + 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 = ReductionSet(Vector.range(0, 9).map(n => x(n))) yield y SudokuField(sudoku) - 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 fd713298c..2eb6e203a 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 @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) 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 8a0d47dd4..a4c21a233 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 @@ -19,100 +19,79 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(Vector((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = - Update(stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap}, - detailParentProbe.ref - ) + Update( + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap }, + detailParentProbe.ref) detailProcessor ! update1 - val reducedUpdate1 = BlockUpdate(2, stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) - ) + val reducedUpdate1 = BlockUpdate( + 2, + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap)) detailParentProbe.expectMessage(reducedUpdate1) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -120,4 +99,4 @@ 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 51e02f34a..61b580979 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 @@ -12,5 +12,5 @@ trait SudokuTestHelpers: (cellString, index) <- stringDef.zipWithIndex if cellString != "" } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_020_opaque_type_aliases_alt/.scalafmt.conf b/exercises/exercise_020_opaque_type_aliases_alt/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_020_opaque_type_aliases_alt/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_020_opaque_type_aliases_alt/IDE_setup.md b/exercises/exercise_020_opaque_type_aliases_alt/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_020_opaque_type_aliases_alt/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_020_opaque_type_aliases_alt/README.md b/exercises/exercise_020_opaque_type_aliases_alt/README.md index ca73f946b..02134ef5d 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/README.md +++ b/exercises/exercise_020_opaque_type_aliases_alt/README.md @@ -66,4 +66,30 @@ this exercise. Specifically we will convert the last of these type aliases, - Once all the compilation errors are fixed, run the provided tests by executing the `test` command from the `sbt` prompt and verify that all tests pass -- Verify that the application runs correctly \ No newline at end of file +- Verify that the application runs correctly + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_020_opaque_type_aliases_alt/build.sbt b/exercises/exercise_020_opaque_type_aliases_alt/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/build.sbt +++ b/exercises/exercise_020_opaque_type_aliases_alt/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_020_opaque_type_aliases_alt/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_020_opaque_type_aliases_alt/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_020_opaque_type_aliases_alt/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_020_opaque_type_aliases_alt/project/Build.scala b/exercises/exercise_020_opaque_type_aliases_alt/project/Build.scala index fee9c8416..ecf13483e 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/project/Build.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/project/Build.scala @@ -15,7 +15,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -27,18 +27,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_020_opaque_type_aliases_alt/project/plugins.sbt b/exercises/exercise_020_opaque_type_aliases_alt/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_020_opaque_type_aliases_alt/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/resources/application.conf b/exercises/exercise_020_opaque_type_aliases_alt/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/resources/application.conf +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index 9847e58de..5babffb1e 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index b7fe9d5bb..b528b39a5 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index d502b0b05..2a41a2edd 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -21,9 +21,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -47,54 +46,51 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case ResetSudokuDetailState => - operational(id, InitialDetailState, fullyReduced = false) + case ResetSudokuDetailState => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -103,4 +99,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index e452c6a8f..a9751144f 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .zipWithIndex convertFromCellsToComplete(cellsIn) diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index 4a199bacb..b09fa4245 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -16,20 +16,24 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = - Behaviors.setup[CommandAndResponses] { context => - Behaviors.withTimers { timers => - new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = + Behaviors + .setup[CommandAndResponses] { context => + Behaviors.withTimers { timers => + new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + } } - }.narrow // Restrict the actor's [external] protocol to its set of commands + .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): +class SudokuProblemSender private ( + sudokuSolver: ActorRef[SudokuSolver.Command], + context: ActorContext[SudokuProblemSender.CommandAndResponses], + timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender.* private val initialSudokuField = rowUpdates.toSudokuField @@ -59,18 +63,17 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms - def sending(): Behavior[CommandAndResponses] = + def sending(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case SendNewSudoku => context.log.debug("sending new sudoku problem") diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 6e36e459a..7968e5f84 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index 88a77ef0a..50c4f28cb 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,8 +10,9 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) export Command.InitialRowUpdates // My Responses @@ -24,8 +25,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[CommandAndResponses]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -37,21 +37,19 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[CommandAndResponses] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ).narrow + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) + .narrow -class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], - buffer: StashBuffer[SudokuSolver.CommandAndResponses] -): +class SudokuSolver private ( + context: ActorContext[SudokuSolver.CommandAndResponses], + buffer: StashBuffer[SudokuSolver.CommandAndResponses]): import CellMappings.* import SudokuSolver.* @@ -62,17 +60,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), "sudoku-progress-tracker") def idle(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -125,9 +120,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons Behaviors.same case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index 37032ae5f..4e072f9b8 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index 48ce7517c..4971a329e 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import scala.collection.Factory private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -14,15 +14,14 @@ type Sudoku = Vector[ReductionSet] opaque type CellUpdates = Vector[(Int, Set[Int])] object CellUpdates: - def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates *) + def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates*) val cellUpdatesEmpty: CellUpdates = Vector.empty[(Int, Set[Int])] -extension[A] (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)) - */ + /** 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(factory: Factory[(Int, Set[Int]), A]): A = updates.to(factory) def foldLeft(z: A)(op: (A, (Int, Set[Int])) => A): A = updates.foldLeft(z)(op) @@ -31,8 +30,7 @@ extension[A] (updates: CellUpdates) def size: Int = updates.size -extension (update: (Int, Set[Int])) - def +: (updates: CellUpdates): CellUpdates = update +: updates +extension (update: (Int, Set[Int])) def +:(updates: CellUpdates): CellUpdates = update +: updates final case class SudokuField(sudoku: Sudoku) @@ -42,9 +40,10 @@ 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) + 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)) @@ -67,20 +66,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -92,11 +89,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 98aee3f96..4f22da0d6 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -19,63 +19,65 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(CellUpdates((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = val cellUpdates = - stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap} + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap } Update(CellUpdates(cellUpdates*), detailParentProbe.ref) detailProcessor ! update1 val reducedUpdate1 = val cellUpdates = - stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap) BlockUpdate(2, CellUpdates(cellUpdates*)) @@ -84,38 +86,14 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -123,4 +101,4 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } diff --git a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 852ffcf98..7d89d0d12 100644 --- a/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_020_opaque_type_aliases_alt/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -13,5 +13,5 @@ trait SudokuTestHelpers: } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) CellUpdates(updates*) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo diff --git a/exercises/exercise_021_multiversal_equality/.scalafmt.conf b/exercises/exercise_021_multiversal_equality/.scalafmt.conf new file mode 100644 index 000000000..932d50b0b --- /dev/null +++ b/exercises/exercise_021_multiversal_equality/.scalafmt.conf @@ -0,0 +1,51 @@ +version = 3.7.2 +runner.dialect = scala3 + +style = defaultWithAlign +indentOperator.preset = akka +maxColumn = 120 +rewrite.rules = [RedundantParens, AvoidInfix] +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.breakChainOnFirstMethodDot = false +optIn.configStyleArguments = false +danglingParentheses.defnSite = false +danglingParentheses.callSite = false +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs + message +] +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} diff --git a/exercises/exercise_021_multiversal_equality/IDE_setup.md b/exercises/exercise_021_multiversal_equality/IDE_setup.md new file mode 100644 index 000000000..1a2af48a0 --- /dev/null +++ b/exercises/exercise_021_multiversal_equality/IDE_setup.md @@ -0,0 +1,19 @@ +# Setting up IntelliJ Idea or Visual Code Studio for Scala development + +## Setting up IntelliJ Idea for Scala development + +- If you haven't already installed the IntelliJ Idea, follow the [Getting started](https://www.jetbrains.com/help/idea/installation-guide.html) instructions on the JetBrains website. The Community Edition should be sufficient for this workshop. + +- After installing the IDE, install the Scala plugin for IntelliJ Idea by following [these instructions](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html#scala_plugin). + +## Setting up Visual Code Studio for Scala development + +- Download the installation binary for your system (Windows, Mac, Linux) [here](https://code.visualstudio.com/download). + +- Follow the set-up instructions [here](https://code.visualstudio.com/docs/setup/setup-overview). + +- Install the Metals (a Scala language server with rich IDE features) plugin for Visual Code Studio by following the instructions [here](https://scalameta.org/metals/docs/editors/vscode/) + +## Other Scala source editors + +There are plenty of other options for Source Code editing. For example, Metals supports Vim, Sublime Text and Emacs. For more information, have a look at the [Text Editors section](https://scalameta.org/metals/docs) in the Metals Documentation diff --git a/exercises/exercise_021_multiversal_equality/README.md b/exercises/exercise_021_multiversal_equality/README.md index 96188bb11..0c36cf43c 100644 --- a/exercises/exercise_021_multiversal_equality/README.md +++ b/exercises/exercise_021_multiversal_equality/README.md @@ -36,4 +36,30 @@ error. - Hint: The simplest solution will probably involve using `CanEqual.derived` - Run the provided tests by executing the `test` command from the `sbt` prompt - and verify that all tests pass \ No newline at end of file + and verify that all tests pass + +## Source code formatting & Markdown viewer in IntelliJ + +### Source code formatting + +[scalafmt](https://github.com/scalameta/scalafmt) based source code formatting is +in place in this project. scalafmt supports both Scala 2 and Scala 3. You can +[re]format the code by running `scalafmtAll` from the sbt prompt. As we switch from +Scala 2 to Scala 3, you need to make sure that a matching scalafmt configuration is +in place. In any of the exercises, you can run `cmtc pull-template .scalafmt.conf` +to "pull-in" the correct configuration file. + +### Markdown viewer in IntelliJ + +The font size can be a bit too small for the taste of some people. You can change the +Markdown zoom setting in IntelliJ by pasting the following CSS snippet in the +markdown setting in _" Settings" -> "Languages & Frameworks" -> "Custom CSS -> CSS rules"_ +and adjust the font-size setting to your liking: + +``` +body { + font-size: 120% !important; + } +``` + +![IntelliJ Markdown viewer settings](images/Markdown-viewer-IntelliJ.png) diff --git a/exercises/exercise_021_multiversal_equality/build.sbt b/exercises/exercise_021_multiversal_equality/build.sbt index 25d1389e7..2ad573102 100644 --- a/exercises/exercise_021_multiversal_equality/build.sbt +++ b/exercises/exercise_021_multiversal_equality/build.sbt @@ -3,7 +3,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges lazy val `moving-from-scala-2-to-scala-3` = (project in file(".")).settings( - scalaVersion := "3.3.0-RC4", + scalaVersion := "3.3.1-RC1-bin-20230508-830230f-NIGHTLY", Compile / scalacOptions ++= CompileOptions.compileOptions, libraryDependencies ++= Dependencies.dependencies, testFrameworks += new TestFramework("munit.Framework"), diff --git a/exercises/exercise_021_multiversal_equality/images/Markdown-viewer-IntelliJ.png b/exercises/exercise_021_multiversal_equality/images/Markdown-viewer-IntelliJ.png new file mode 100644 index 000000000..a1db6ebab Binary files /dev/null and b/exercises/exercise_021_multiversal_equality/images/Markdown-viewer-IntelliJ.png differ diff --git a/exercises/exercise_021_multiversal_equality/project/Build.scala b/exercises/exercise_021_multiversal_equality/project/Build.scala index 5ed67ad55..81e97095d 100644 --- a/exercises/exercise_021_multiversal_equality/project/Build.scala +++ b/exercises/exercise_021_multiversal_equality/project/Build.scala @@ -16,7 +16,7 @@ object CompileOptions { ) } -object Version { +object Versions { lazy val akkaVer = "2.6.20" lazy val logbackVer = "1.2.3" lazy val mUnitVer = "0.7.26" @@ -28,18 +28,18 @@ object Dependencies { "com.typesafe.akka" %% "akka-actor-typed", "com.typesafe.akka" %% "akka-slf4j", "com.typesafe.akka" %% "akka-stream", - ).map (_ % Version.akkaVer) + ).map (_ % Versions.akkaVer) private lazy val akkaTestkitDeps = Seq( - "com.typesafe.akka" %% "akka-actor-testkit-typed" % Version.akkaVer % Test + "com.typesafe.akka" %% "akka-actor-testkit-typed" % Versions.akkaVer % Test ) private lazy val logbackDeps = Seq ( "ch.qos.logback" % "logback-classic", - ).map (_ % Version.logbackVer) + ).map (_ % Versions.logbackVer) private lazy val munitDeps = Seq( - "org.scalameta" %% "munit" % Version.mUnitVer % Test + "org.scalameta" %% "munit" % Versions.mUnitVer % Test ) lazy val dependencies: Seq[ModuleID] = diff --git a/exercises/exercise_021_multiversal_equality/project/plugins.sbt b/exercises/exercise_021_multiversal_equality/project/plugins.sbt new file mode 100644 index 000000000..4cdd8f2a2 --- /dev/null +++ b/exercises/exercise_021_multiversal_equality/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") diff --git a/exercises/exercise_021_multiversal_equality/src/main/resources/application.conf b/exercises/exercise_021_multiversal_equality/src/main/resources/application.conf index f0509d668..14927baad 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/resources/application.conf +++ b/exercises/exercise_021_multiversal_equality/src/main/resources/application.conf @@ -1,11 +1,9 @@ akka { - log-dead-letters = on logger-startup-timeout = 30s actor { provider = local - debug { lifecycle = on unhandled = on @@ -13,7 +11,6 @@ akka { } remote { - artery { canonical { hostname = "127.0.0.1" diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala index 9847e58de..5babffb1e 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala @@ -1,32 +1,27 @@ -/** - * Copyright © 2020-2023 Lunatech Labs. +/** Copyright © 2020-2023 Lunatech Labs. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON - * THIS SOFTWARE BY Lunatech Labs. + * NO COMMERCIAL SUPPORT OR ANY OTHER FORM OF SUPPORT IS OFFERED ON THIS SOFTWARE BY Lunatech Labs. * - * See the License for the specific language governing permissions and - * limitations under the License. + * See the License for the specific language governing permissions and limitations under the License. */ package org.lunatechlabs.dotty import akka.NotUsed import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps -import akka.actor.typed.scaladsl.{ Behaviors, Routers } -import akka.actor.typed.{ ActorSystem, Behavior, Terminated } -import org.lunatechlabs.dotty.sudoku.{ SudokuProblemSender, SudokuSolver, SudokuSolverSettings } +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import akka.actor.typed.{ActorSystem, Behavior, Terminated} +import org.lunatechlabs.dotty.sudoku.{SudokuProblemSender, SudokuSolver, SudokuSolverSettings} import scala.io.StdIn -import scala.Console.{ GREEN, RESET } +import scala.Console.{GREEN, RESET} object Main: def apply(): Behavior[NotUsed] = @@ -35,13 +30,10 @@ object Main: // Start a SudokuSolver val sudokuSolver = context.spawn(SudokuSolver(sudokuSolverSettings), s"sudoku-solver") // Start a Sudoku problem sender - context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), - "sudoku-problem-sender" - ) + context.spawn(SudokuProblemSender(sudokuSolver, sudokuSolverSettings), "sudoku-problem-sender") - Behaviors.receiveSignal { - case (_, Terminated(_)) => - Behaviors.stopped + Behaviors.receiveSignal { case (_, Terminated(_)) => + Behaviors.stopped } } diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala index b7fe9d5bb..b528b39a5 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/ReductionRules.scala @@ -4,8 +4,8 @@ package org.lunatechlabs.dotty.sudoku extension (reductionSet: ReductionSet) def applyReductionRuleOne: ReductionSet = - val inputCellsGrouped = reductionSet.filter {_.size <= 7}.groupBy(identity) - val completeInputCellGroups = inputCellsGrouped filter { (set, setOccurrences) => + val inputCellsGrouped = reductionSet.filter { _.size <= 7 }.groupBy(identity) + val completeInputCellGroups = inputCellsGrouped.filter { (set, setOccurrences) => set.size == setOccurrences.length } val completeAndIsolatedValueSets = completeInputCellGroups.keys.toList @@ -16,28 +16,25 @@ extension (reductionSet: ReductionSet) } def applyReductionRuleTwo: ReductionSet = - val valueOccurrences = CELLPossibleValues.map { value => - cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { - case (acc, (index, cell)) => - if cell contains value then index +: acc else acc + val valueOccurrences = CellPossibleValues.map { value => + cellIndexesVector.zip(reductionSet).foldLeft(Vector.empty[Int]) { case (acc, (index, cell)) => + if cell contains value then index +: acc else acc } } val cellIndexesToValues = - CELLPossibleValues - .zip(valueOccurrences) - .groupBy ((value, occurrence) => occurrence ) - .filter { case (loc, occ) => loc.length == occ.length && loc.length <= 6 } + CellPossibleValues.zip(valueOccurrences).groupBy((value, occurrence) => occurrence).filter { case (loc, occ) => + loc.length == occ.length && loc.length <= 6 + } val cellIndexListToReducedValue = cellIndexesToValues.map { (index, seq) => - (index, (seq.map ((value, _) => value )).toSet) + (index, seq.map((value, _) => value).toSet) } val cellIndexToReducedValue = cellIndexListToReducedValue.flatMap { (cellIndexList, reducedValue) => cellIndexList.map(cellIndex => cellIndex -> reducedValue) } - reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { - case ((cellValue, cellIndex), acc) => - cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc + reductionSet.zipWithIndex.foldRight(Vector.empty[CellContent]) { case ((cellValue, cellIndex), acc) => + cellIndexToReducedValue.getOrElse(cellIndex, cellValue) +: acc } diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala index fae6d8a41..82e08f9de 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala @@ -21,9 +21,8 @@ object SudokuDetailProcessor: case SudokuDetailUnchanged export Response.* - def apply[DetailType <: SudokuDetailType](id: Int, - state: ReductionSet = InitialDetailState) - (using updateSender: UpdateSender[DetailType]): Behavior[Command] = + def apply[DetailType <: SudokuDetailType](id: Int, state: ReductionSet = InitialDetailState)(using + updateSender: UpdateSender[DetailType]): Behavior[Command] = Behaviors.setup { context => (new SudokuDetailProcessor[DetailType](context)).operational(id, state, fullyReduced = false) } @@ -47,54 +46,51 @@ object SudokuDetailProcessor: sender ! BlockUpdate(id, cellUpdates) def processorName(id: Int): String = s"blk-processor-$id" -class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private(context: ActorContext[SudokuDetailProcessor.Command]): +class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] private ( + context: ActorContext[SudokuDetailProcessor.Command]): import SudokuDetailProcessor.* def operational(id: Int, state: ReductionSet, fullyReduced: Boolean): Behavior[Command] = Behaviors.receiveMessage { - case Update(cellUpdates, replyTo) if ! fullyReduced => - val previousState = state - val updatedState = mergeState(state, cellUpdates) - if updatedState == previousState && cellUpdates != cellUpdatesEmpty then - replyTo ! SudokuDetailUnchanged - Behaviors.same - else - val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo - if transformedUpdatedState == state then + case Update(cellUpdates, replyTo) if !fullyReduced => + val previousState = state + val updatedState = mergeState(state, cellUpdates) + if updatedState == previousState && cellUpdates != cellUpdatesEmpty then replyTo ! SudokuDetailUnchanged Behaviors.same 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 - Behaviors.same + val transformedUpdatedState = updatedState.applyReductionRuleOne.applyReductionRuleTwo + if transformedUpdatedState == state then + replyTo ! SudokuDetailUnchanged + Behaviors.same + else + val updateSender = summon[UpdateSender[DetailType]] + updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(using replyTo) + operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState)) + + case Update(_, replyTo) => + replyTo ! SudokuDetailUnchanged + Behaviors.same - case GetSudokuDetailState(replyTo) => - replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) - Behaviors.same + case GetSudokuDetailState(replyTo) => + replyTo ! SudokuProgressTracker.SudokuDetailState(id, state) + Behaviors.same - case _: ResetSudokuDetailState.type => - operational(id, InitialDetailState, fullyReduced = false) + case _: ResetSudokuDetailState.type => + operational(id, InitialDetailState, fullyReduced = false) - } + } private def mergeState(state: ReductionSet, cellUpdates: CellUpdates): ReductionSet = - cellUpdates.foldLeft(state) { - case (stateTally, (index, updatedCellContent)) => - stateTally.updated(index, stateTally(index) & updatedCellContent) + cellUpdates.foldLeft(state) { case (stateTally, (index, updatedCellContent)) => + stateTally.updated(index, stateTally(index) & updatedCellContent) } private def stateChanges(state: ReductionSet, updatedState: ReductionSet): CellUpdates = - (state zip updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { + state.zip(updatedState).zipWithIndex.foldRight(cellUpdatesEmpty) { case (((previousCellContent, updatedCellContent), index), cellUpdates) - if updatedCellContent != previousCellContent => + if updatedCellContent != previousCellContent => (index, updatedCellContent) +: cellUpdates case (_, cellUpdates) => cellUpdates @@ -103,4 +99,3 @@ class SudokuDetailProcessor[DetailType <: SudokuDetailType: UpdateSender] privat private def isFullyReduced(state: ReductionSet): Boolean = val allValuesInState = state.flatten allValuesInState == allValuesInState.distinct - diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala index e452c6a8f..a9751144f 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuIO.scala @@ -1,36 +1,31 @@ package org.lunatechlabs.dotty.sudoku +import java.io.{BufferedReader, File, FileReader} import java.util.NoSuchElementException object SudokuIO: private def sudokuCellRepresentation(content: CellContent): String = content.toList match - case Nil => "x" + case Nil => "x" case singleValue +: Nil => singleValue.toString - case _ => " " + case _ => " " private def sudokuRowPrinter(threeRows: Vector[ReductionSet]): String = val rowSubBlocks = for row <- threeRows - rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3,3) + rowSubBlock <- row.map(el => sudokuCellRepresentation(el)).sliding(3, 3) rPres = rowSubBlock.mkString - yield rPres - rowSubBlocks.sliding(3,3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") + rowSubBlocks.sliding(3, 3).map(_.mkString("", "|", "")).mkString("|", "|\n|", "|\n") def sudokuPrinter(result: SudokuSolver.SudokuSolution): String = - result.sudoku - .sliding(3,3) - .map(sudokuRowPrinter) - .mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") + result.sudoku.sliding(3, 3).map(sudokuRowPrinter).mkString("\n+---+---+---+\n", "+---+---+---+\n", "+---+---+---+") /* * FileLineTraversable code taken from "Scala in Depth" by Joshua Suereth */ - import java.io.{BufferedReader, File, FileReader} - class FileLineTraversable(file: File) extends Iterable[String]: val fr = new FileReader(file) val input = new BufferedReader(fr) @@ -60,8 +55,7 @@ object SudokuIO: throw new IllegalStateException(e.toString) override def next(): String = - if ! hasNext then - throw new NoSuchElementException("No more lines in file") + if !hasNext then throw new NoSuchElementException("No more lines in file") val currentLine = cachedLine.get cachedLine = None currentLine @@ -77,18 +71,16 @@ object SudokuIO: (index, Set(c.toString.toInt)) +: cellUpdates case (cellUpdates, _) => cellUpdates } - yield (row, updates) - def readSudokuFromFile(sudokuInputFile: java.io.File): Vector[(Int, CellUpdates)] = val dataLines = new FileLineTraversable(sudokuInputFile).toVector val cellsIn = dataLines - .map { inputLine => """\|""".r replaceAllIn(inputLine, "")} // Remove 3x3 separator character - .filter (_ != "---+---+---") // Remove 3x3 line separator - .map ("""^[1-9 ]{9}$""".r findFirstIn(_)) // Input data should only contain values 1-9 or ' ' - .collect { case Some(x) => x} + .map { inputLine => """\|""".r.replaceAllIn(inputLine, "") } // Remove 3x3 separator character + .filter(_ != "---+---+---") // Remove 3x3 line separator + .map("""^[1-9 ]{9}$""".r.findFirstIn(_)) // Input data should only contain values 1-9 or ' ' + .collect { case Some(x) => x } .zipWithIndex convertFromCellsToComplete(cellsIn) diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala index fb1f952c7..46b28fb41 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProblemSender.scala @@ -2,8 +2,8 @@ package org.lunatechlabs.dotty.sudoku import java.io.File -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, TimerScheduler } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProblemSender: @@ -16,20 +16,24 @@ object SudokuProblemSender: private val rowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = SudokuIO .readSudokuFromFile(new File("sudokus/001.sudoku")) - .map ((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) + .map((rowIndex, update) => SudokuDetailProcessor.RowUpdate(rowIndex, update)) - def apply(sudokuSolver: ActorRef[SudokuSolver.Command], - sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = - Behaviors.setup[CommandAndResponses] { context => - Behaviors.withTimers { timers => - new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + def apply( + sudokuSolver: ActorRef[SudokuSolver.Command], + sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = + Behaviors + .setup[CommandAndResponses] { context => + Behaviors.withTimers { timers => + new SudokuProblemSender(sudokuSolver, context, timers, sudokuSolverSettings).sending() + } } - }.narrow // Restrict the actor's [external] protocol to its set of commands + .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): +class SudokuProblemSender private ( + sudokuSolver: ActorRef[SudokuSolver.Command], + context: ActorContext[SudokuProblemSender.CommandAndResponses], + timers: TimerScheduler[SudokuProblemSender.CommandAndResponses], + sudokuSolverSettings: SudokuSolverSettings): import SudokuProblemSender.* private val initialSudokuField = rowUpdates.toSudokuField @@ -59,18 +63,17 @@ class SudokuProblemSender private (sudokuSolver: ActorRef[SudokuSolver.Command], initialSudokuField.flipVertically.mirrorOnMainDiagonal, initialSudokuField.flipVertically.rotateCW, initialSudokuField.columnSwap(4, 5).columnSwap(0, 2).rowSwap(3, 4), - initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal - ).map(_.toRowUpdates) - ) + initialSudokuField.rotateCW.rotateCW.mirrorOnMainDiagonal).map(_.toRowUpdates)) .flatten .iterator private val problemSendInterval = sudokuSolverSettings.ProblemSender.SendInterval - timers.startTimerAtFixedRate(SendNewSudoku, - problemSendInterval + timers.startTimerAtFixedRate( + SendNewSudoku, + problemSendInterval ) // on a 5 node RPi 4 based cluster in steady state, this can be lowered to about 6ms - def sending(): Behavior[CommandAndResponses] = + def sending(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case _: SendNewSudoku.type => context.log.debug("sending new sudoku problem") diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala index 6e36e459a..7968e5f84 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuProgressTracker.scala @@ -1,7 +1,7 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.{ ActorRef, Behavior } +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior} object SudokuProgressTracker: @@ -15,28 +15,25 @@ object SudokuProgressTracker: case Result(sudoku: Sudoku) export Response.* - def apply(rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], - sudokuSolver: ActorRef[Response] - ): Behavior[Command] = + def apply( + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + sudokuSolver: ActorRef[Response]): Behavior[Command] = Behaviors.setup { context => - new SudokuProgressTracker(rowDetailProcessors, context, sudokuSolver) - .trackProgress(updatesInFlight = 0) + 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] -): + rowDetailProcessors: Map[Int, ActorRef[SudokuDetailProcessor.Command]], + context: ActorContext[SudokuProgressTracker.Command], + sudokuSolver: ActorRef[SudokuProgressTracker.Response]): import SudokuProgressTracker.* def trackProgress(updatesInFlight: Int): Behavior[Command] = Behaviors.receiveMessage { case NewUpdatesInFlight(updateCount) if updatesInFlight - 1 == 0 => - rowDetailProcessors.foreach ((_, processor) => - processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self) - ) + rowDetailProcessors.foreach((_, processor) => + processor ! SudokuDetailProcessor.GetSudokuDetailState(context.self)) collectEndState() case NewUpdatesInFlight(updateCount) => trackProgress(updatesInFlight + updateCount) @@ -45,16 +42,14 @@ class SudokuProgressTracker private ( Behaviors.same } - def collectEndState(remainingRows: Int = 9, - endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState] - ): Behavior[Command] = + def collectEndState( + remainingRows: Int = 9, + endState: Vector[SudokuDetailState] = Vector.empty[SudokuDetailState]): Behavior[Command] = Behaviors.receiveMessage { case detail: SudokuDetailState if remainingRows == 1 => - sudokuSolver ! Result( - (detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { - case SudokuDetailState(_, state) => state - } - ) + sudokuSolver ! Result((detail +: endState).sortBy { case SudokuDetailState(idx, _) => idx }.map { + case SudokuDetailState(_, state) => state + }) trackProgress(updatesInFlight = 0) case detail: SudokuDetailState => collectEndState(remainingRows = remainingRows - 1, detail +: endState) diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala index c6f4f8dd8..f94f0e2c2 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolver.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import akka.actor.typed.receptionist.{ Receptionist, ServiceKey } -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer } -import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy } +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import scala.concurrent.duration.* @@ -10,8 +10,9 @@ object SudokuSolver: // SudokuSolver Protocol enum Command: - case InitialRowUpdates(rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], - replyTo: ActorRef[SudokuSolver.Response]) + case InitialRowUpdates( + rowUpdates: Vector[SudokuDetailProcessor.RowUpdate], + replyTo: ActorRef[SudokuSolver.Response]) export Command.InitialRowUpdates // My Responses @@ -24,8 +25,7 @@ object SudokuSolver: import SudokuDetailProcessor.UpdateSender def genDetailProcessors[A <: SudokuDetailType: UpdateSender]( - context: ActorContext[CommandAndResponses] - ): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = + context: ActorContext[CommandAndResponses]): Map[Int, ActorRef[SudokuDetailProcessor.Command]] = cellIndexesVector .map { index => val detailProcessorName = summon[UpdateSender[A]].processorName(index) @@ -37,21 +37,19 @@ object SudokuSolver: def apply(sudokuSolverSettings: SudokuSolverSettings): Behavior[Command] = Behaviors .supervise[CommandAndResponses] { - Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { - buffer => - Behaviors.setup { context => - new SudokuSolver(context, buffer).idle() - } + Behaviors.withStash(capacity = sudokuSolverSettings.SudokuSolver.StashBufferSize) { buffer => + Behaviors.setup { context => + new SudokuSolver(context, buffer).idle() + } } } .onFailure[Exception]( - SupervisorStrategy - .restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2) - ).narrow + SupervisorStrategy.restartWithBackoff(minBackoff = 5.seconds, maxBackoff = 1.minute, randomFactor = 0.2)) + .narrow -class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndResponses], - buffer: StashBuffer[SudokuSolver.CommandAndResponses] -): +class SudokuSolver private ( + context: ActorContext[SudokuSolver.CommandAndResponses], + buffer: StashBuffer[SudokuSolver.CommandAndResponses]): import CellMappings.* import SudokuSolver.* @@ -62,17 +60,14 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons List(rowDetailProcessors, columnDetailProcessors, blockDetailProcessors) private val progressTracker = - context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), - "sudoku-progress-tracker" - ) + context.spawn(SudokuProgressTracker(rowDetailProcessors, context.self), "sudoku-progress-tracker") def idle(): Behavior[CommandAndResponses] = Behaviors.receiveMessage { case InitialRowUpdates(rowUpdates, sender) => - rowUpdates.foreach { - case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => - rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) + rowUpdates.foreach { case SudokuDetailProcessor.RowUpdate(row, cellUpdates) => + rowDetailProcessors(row) ! SudokuDetailProcessor.Update(cellUpdates, context.self) } progressTracker ! SudokuProgressTracker.NewUpdatesInFlight(rowUpdates.size) processRequest(Some(sender), System.currentTimeMillis()) @@ -125,9 +120,7 @@ class SudokuSolver private (context: ActorContext[SudokuSolver.CommandAndRespons Behaviors.same case SudokuProgressTracker.Result(sudoku) => - context.log.info( - s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds" - ) + context.log.info(s"Sudoku processing time: ${System.currentTimeMillis() - startTime} milliseconds") requestor.get ! SudokuSolution(sudoku) resetAllDetailProcessors() buffer.unstashAll(idle()) diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala index 37032ae5f..4e072f9b8 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuSolverSettings.scala @@ -1,8 +1,8 @@ package org.lunatechlabs.dotty.sudoku -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} -import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS as Millis } +import scala.concurrent.duration.{Duration, FiniteDuration, MILLISECONDS as Millis} object SudokuSolverSettings: diff --git a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala index de174da45..154acf105 100644 --- a/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala +++ b/exercises/exercise_021_multiversal_equality/src/main/scala/org/lunatechlabs/dotty/sudoku/TopLevelDefinitions.scala @@ -3,7 +3,7 @@ package org.lunatechlabs.dotty.sudoku import scala.collection.Factory private val N = 9 -val CELLPossibleValues: Vector[Int] = (1 to N).toVector +val CellPossibleValues: Vector[Int] = (1 to N).toVector val cellIndexesVector: Vector[Int] = Vector.range(0, N) val initialCell: Set[Int] = Set.range(1, 10) val InitialDetailState = cellIndexesVector.map(_ => initialCell) @@ -14,17 +14,16 @@ type Sudoku = Vector[ReductionSet] opaque type CellUpdates = Vector[(Int, Set[Int])] object CellUpdates: - def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates *) + def apply(updates: (Int, Set[Int])*): CellUpdates = Vector(updates*) val cellUpdatesEmpty: CellUpdates = Vector.empty[(Int, Set[Int])] given CanEqual[CellUpdates, CellUpdates] = CanEqual.derived -extension[A] (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)) - */ + /** 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(factory: Factory[(Int, Set[Int]), A]): A = updates.to(factory) def foldLeft(z: A)(op: (A, (Int, Set[Int])) => A): A = updates.foldLeft(z)(op) @@ -33,8 +32,7 @@ extension[A] (updates: CellUpdates) def size: Int = updates.size -extension (update: (Int, Set[Int])) - def +: (updates: CellUpdates): CellUpdates = update +: updates +extension (update: (Int, Set[Int])) def +:(updates: CellUpdates): CellUpdates = update +: updates final case class SudokuField(sudoku: Sudoku) @@ -44,9 +42,10 @@ 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) + 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)) @@ -69,20 +68,18 @@ extension (sudokuField: SudokuField) def flipHorizontally: SudokuField = sudokuField.rotateCW.flipVertically.rotateCCW 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 - } - ) + 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 = sudokuField.rotateCW.rowSwap(col1, col2).rotateCCW def randomSwapAround: SudokuField = import scala.language.implicitConversions - val possibleCellValues = Vector(1,2,3,4,5,6,7,8,9) + 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 // with an unknown value (i.e. it can still hold all values 0 through 9). As such // a cell with value 0 should remain 0 which is why we add an entry to the generated @@ -94,11 +91,11 @@ extension (sudokuField: SudokuField) }) def toRowUpdates: Vector[SudokuDetailProcessor.RowUpdate] = - sudokuField - .sudoku + sudokuField.sudoku .map(_.zipWithIndex) .map(row => row.filterNot(_._1 == Set(0))) - .zipWithIndex.filter(_._1.nonEmpty) + .zipWithIndex + .filter(_._1.nonEmpty) .map { (c, i) => SudokuDetailProcessor.RowUpdate(i, c.map(_.swap)) } diff --git a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala index fd713298c..2eb6e203a 100644 --- a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala +++ b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/ReductionRuleSuite.scala @@ -1,67 +1,66 @@ package org.lunatechlabs.dotty.sudoku 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 - * row or column or 3x3 block). Each cell is represented by 9 characters: a space - * encodes a number not present in the cell, characters '1' through '9' encode the - * presence of that digit in the cell. - */ - val input = stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )) - - val reducedInput1 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )) + 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 row or column or 3x3 block). + * Each cell is represented by 9 characters: a space encodes a number not present in the cell, characters '1' + * through '9' encode the presence of that digit in the cell. + */ + val input = stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")) + + val reducedInput1 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")) assertEquals(applyReductionRules(input), reducedInput1) - // After first reduction round, 9 is isolated & complete - val reducedInput2 = stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput2 = stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput1), reducedInput2) // After second reduction round, 6 is isolated & complete - val reducedInput3 = stringToReductionSet(Vector( - " 23 5 ", - "1 ", - " 4 ", - " 2 5 ", - " 78 ", - " 9", - " 78 ", - " 6 ", - " 23 " - )) + val reducedInput3 = stringToReductionSet( + Vector( + " 23 5 ", + "1 ", + " 4 ", + " 2 5 ", + " 78 ", + " 9", + " 78 ", + " 6 ", + " 23 ")) assertEquals(applyReductionRules(reducedInput2), reducedInput3) @@ -69,88 +68,90 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput3), reducedInput3) } - - test("Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 67 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - "12345 ", // (1,2,3,4,5): Isolated & complete - " 2 78 ", - "12345 ", // (1,2,3,4,5): Isolated & complete - " 3 89", - "12345 ", // (1,2,3,4,5): Isolated & complete - "1 3 6 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "12345 ", - " 67 ", - "12345 ", - "12345 ", - " 78 ", - "12345 ", - " 89", - "12345 ", - " 6 9" - )) + test( + "Applying reduction rules should eliminate values in isolated complete sets of 5 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 67 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + "12345 ", // (1,2,3,4,5): Isolated & complete + " 2 78 ", + "12345 ", // (1,2,3,4,5): Isolated & complete + " 3 89", + "12345 ", // (1,2,3,4,5): Isolated & complete + "1 3 6 9")) + + val reducedInput = stringToReductionSet( + Vector( + "12345 ", + " 67 ", + "12345 ", + "12345 ", + " 78 ", + "12345 ", + " 89", + "12345 ", + " 6 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { - val input = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - "12345678 ", - "123456 89", - "1234567 9" - )) - - val reducedInput = stringToReductionSet(Vector( - "123 ", - "123 ", - "123 ", - " 456 ", - " 456 ", - " 456 ", - " 78 ", - " 89", - " 7 9" - )) + test( + "Applying reduction rules should eliminate values in 2 isolated complete sets of 3 values from occurrences in other cells (First reduction rule)") { + val input = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + "12345678 ", + "123456 89", + "1234567 9")) + + val reducedInput = stringToReductionSet( + Vector( + "123 ", + "123 ", + "123 ", + " 456 ", + " 456 ", + " 456 ", + " 78 ", + " 89", + " 7 9")) assertEquals(applyReductionRules(input), reducedInput) } - test("Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "12 5 789", // (1,2,7,8) shadowed & complete - " 345 ", // (3,4) shadowed & complete - "12 5678 ", // (1,2,7,8) shadowed & complete - " 56 9", - " 56 9", - "12 789", // (1,2,7,8) shadowed & complete - " 3456 9", // (3,4) shadowed & complete - "12 6789", // (1,2,7,8) shadowed & complete - " 9" - )) - - val reducedInput1 = stringToReductionSet(Vector( - "12 78 ", - " 34 ", - "12 78 ", - " 56 ", - " 56 ", - "12 78 ", - " 34 ", - "12 78 ", - " 9" - )) + test( + "Applying reduction rules should eliminate values in shadowed complete sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "12 5 789", // (1,2,7,8) shadowed & complete + " 345 ", // (3,4) shadowed & complete + "12 5678 ", // (1,2,7,8) shadowed & complete + " 56 9", + " 56 9", + "12 789", // (1,2,7,8) shadowed & complete + " 3456 9", // (3,4) shadowed & complete + "12 6789", // (1,2,7,8) shadowed & complete + " 9")) + + val reducedInput1 = stringToReductionSet( + Vector( + "12 78 ", + " 34 ", + "12 78 ", + " 56 ", + " 56 ", + "12 78 ", + " 34 ", + "12 78 ", + " 9")) assertEquals(applyReductionRules(input), reducedInput1) @@ -158,30 +159,32 @@ class ReductionRuleSuite extends munit.FunSuite with SudokuTestHelpers: assertEquals(applyReductionRules(reducedInput1), reducedInput1) } - test("Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { - val input = stringToReductionSet(Vector( - "123456 89", // (1,2,3,4,5,6) shadowed & complete - " 78 ", - "12345678 ", // (1,2,3,4,5,6) shadowed & complete - "123456789", // (1,2,3,4,5,6) shadowed & complete - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 89", - "123456 8 ", // (1,2,3,4,5,6) shadowed & complete - " 7 9", - "12345678 " // (1,2,3,4,5,6) shadowed & complete - )) - - val reducedInput = stringToReductionSet(Vector( - "123456 ", - " 78 ", - "123456 ", - "123456 ", - "123456 ", - " 89", - "123456 ", - " 7 9", - "123456 " - )) + test( + "Applying reduction rules should eliminate values in shadowed complete (6 value) sets from occurrences in same cells (Second reduction rule)") { + val input = stringToReductionSet( + Vector( + "123456 89", // (1,2,3,4,5,6) shadowed & complete + " 78 ", + "12345678 ", // (1,2,3,4,5,6) shadowed & complete + "123456789", // (1,2,3,4,5,6) shadowed & complete + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 89", + "123456 8 ", // (1,2,3,4,5,6) shadowed & complete + " 7 9", + "12345678 " // (1,2,3,4,5,6) shadowed & complete + )) + + val reducedInput = stringToReductionSet( + Vector( + "123456 ", + " 78 ", + "123456 ", + "123456 ", + "123456 ", + " 89", + "123456 ", + " 7 9", + "123456 ")) assertEquals(applyReductionRules(input), reducedInput) } diff --git a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala index 98aee3f96..4f22da0d6 100644 --- a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala +++ b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessorSuite.scala @@ -19,63 +19,65 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: probe.expectMessage(SudokuDetailUnchanged) } - test("Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { + test( + "Sending an update to a fresh instance of the SudokuDetailProcessor that sets one cell to a single value should result in sending an update that reflects this update") { val probe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Row](id = 0)) detailProcessor ! Update(CellUpdates((4, Set(7))), probe.ref) val expectedState1 = - SudokuDetailProcessor.RowUpdate(0, stringToIndexedUpdate( - Vector( - "123456 89", - "123456 89", - "123456 89", - "123456 89", - " 7 ", - "123456 89", - "123456 89", - "123456 89", - "123456 89" - )) - ) + SudokuDetailProcessor.RowUpdate( + 0, + stringToIndexedUpdate( + Vector( + "123456 89", + "123456 89", + "123456 89", + "123456 89", + " 7 ", + "123456 89", + "123456 89", + "123456 89", + "123456 89"))) probe.expectMessage(expectedState1) } - test("Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { + test( + "Sending a series of subsequent Updates to a SudokuDetailProcessor should result in sending updates and ultimately return no changes") { val detailParentProbe = testKit.createTestProbe[SudokuDetailProcessor.Response]() val detailProcessor = testKit.spawn(SudokuDetailProcessor[Block](id = 2)) val update1 = val cellUpdates = - stringToReductionSet(Vector( - "12345678 ", - "1 ", // 1: Isolated & complete - " 4 ", // 4: Isolated & complete - "12 45678 ", - " 78 ", // (7,8): Isolated & complete - " 89", - " 78 ", // (7,8): Isolated & complete - " 6789", - " 23 78 " - )).zipWithIndex.map { _.swap} + stringToReductionSet( + Vector( + "12345678 ", + "1 ", // 1: Isolated & complete + " 4 ", // 4: Isolated & complete + "12 45678 ", + " 78 ", // (7,8): Isolated & complete + " 89", + " 78 ", // (7,8): Isolated & complete + " 6789", + " 23 78 ")).zipWithIndex.map { _.swap } Update(CellUpdates(cellUpdates*), detailParentProbe.ref) detailProcessor ! update1 val reducedUpdate1 = val cellUpdates = - stringToReductionSet(Vector( - " 23 56 ", - "1 ", - " 4 ", - " 2 56 ", - " 78 ", - " 9", - " 78 ", - " 6 9", - " 23 " - )).zipWithIndex.map(_.swap) + stringToReductionSet( + Vector( + " 23 56 ", + "1 ", + " 4 ", + " 2 56 ", + " 78 ", + " 9", + " 78 ", + " 6 9", + " 23 ")).zipWithIndex.map(_.swap) BlockUpdate(2, CellUpdates(cellUpdates*)) @@ -84,38 +86,14 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate2 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - "", - "", - "", - "", - "", - "", - "", - " 6 ", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector("", "", "", "", "", "", "", " 6 ", ""))) detailParentProbe.expectMessage(reducedUpdate2) detailProcessor ! Update(cellUpdatesEmpty, detailParentProbe.ref) val reducedUpdate3 = - BlockUpdate(2, stringToIndexedUpdate( - Vector( - " 23 5 ", - "", - "", - " 2 5 ", - "", - "", - "", - "", - "" - )) - ) + BlockUpdate(2, stringToIndexedUpdate(Vector(" 23 5 ", "", "", " 2 5 ", "", "", "", "", ""))) detailParentProbe.expectMessage(reducedUpdate3) @@ -123,4 +101,4 @@ class SudokuDetailProcessorSuite extends munit.FunSuite with SudokuTestHelpers: detailParentProbe.expectMessage(SudokuDetailUnchanged) - } + } diff --git a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala index 852ffcf98..7d89d0d12 100644 --- a/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala +++ b/exercises/exercise_021_multiversal_equality/src/test/scala/org/lunatechlabs/dotty/sudoku/SudokuTestHelpers.scala @@ -13,5 +13,5 @@ trait SudokuTestHelpers: } yield (index, cellString.replaceAll(" ", "").map { _.toString.toInt }.toSet) CellUpdates(updates*) - def applyReductionRules(reductionSet: ReductionSet): ReductionSet = reductionSet.applyReductionRuleOne.applyReductionRuleTwo - + def applyReductionRules(reductionSet: ReductionSet): ReductionSet = + reductionSet.applyReductionRuleOne.applyReductionRuleTwo