From b44543bf63e2bb4173ede024c7a0c0f45efeb1f1 Mon Sep 17 00:00:00 2001 From: horothesun Date: Mon, 1 Apr 2024 21:37:33 +0100 Subject: [PATCH] Day 6: cleanup --- src/main/scala/Day6.scala | 161 +++++++++++++++++---------------- src/test/scala/Day6Suite.scala | 1 + 2 files changed, 83 insertions(+), 79 deletions(-) diff --git a/src/main/scala/Day6.scala b/src/main/scala/Day6.scala index e8b8256..456555b 100644 --- a/src/main/scala/Day6.scala +++ b/src/main/scala/Day6.scala @@ -1,83 +1,86 @@ import cats.implicits.* import scala.math.Ordering.Implicits.infixOrderingOps import scala.math.Numeric.Implicits.infixNumericOps -import RaceOutcome.* - -case class Time(millis: Long): - def inc: Time = Time(millis + 1) - def dec: Time = Time(millis - 1) - -object Time: - given Numeric[Time] = Numeric[Long].imap(Time.apply)(_.millis) - def zero: Time = Time(0) - -case class Distance(millimeters: Long) -object Distance: - given Numeric[Distance] = Numeric[Long].imap(Distance.apply)(_.millimeters) - given Ordering[Distance] = Ordering.by(_.millimeters) - -case class Race(allowance: Time, record: Distance) - -enum RaceOutcome: - case Win - case Loss(travelled: Distance) - -enum Bounds: - case Within, Outside - -def getRaceOutcome(race: Race, hold: Time): RaceOutcome = - val travelled = getTravelledDistance(race.allowance, hold) - if (travelled > race.record) Win else Loss(travelled) - -def getTravelledDistance(allowance: Time, hold: Time): Distance = - val raceDuration = allowance - hold - val speed = hold.millis - Distance(speed * raceDuration.millis) - -def getTotalOutcomes(allowance: Time): Long = 1 + allowance.millis - -def getAllPossibleDistances(allowance: Time): List[Distance] = - List - .range[Long](0, getTotalOutcomes(allowance)) - .map(holdMillis => getTravelledDistance(allowance, hold = Time(holdMillis))) - -def countWaysToWin_bruteForce(race: Race): Long = getAllPossibleDistances(race.allowance).count(_ > race.record) - -def getMultipliedWaysToWin_bruteForce(rs: List[Race]): Long = rs.map(countWaysToWin_bruteForce).product - -def countWaysToLoseLeft(race: Race): Long = - countWaysToLose( - startHold = Time.zero, - holdBounds = hold => if (hold <= race.allowance) Bounds.Within else Bounds.Outside, - nextHold = _.inc, - race - ) - -def countWaysToLoseRight(race: Race): Long = - countWaysToLose( - startHold = race.allowance, - holdBounds = hold => if (hold >= Time.zero) Bounds.Within else Bounds.Outside, - nextHold = _.dec, - race - ) - -def countWaysToLose(startHold: Time, holdBounds: Time => Bounds, nextHold: Time => Time, race: Race): Long = - List - .unfold[Distance, Time](startHold) { hold => - holdBounds(hold) match - case Bounds.Outside => None - case Bounds.Within => - getRaceOutcome(race, hold) match - case Win => None - case Loss(travelled) => Some((travelled, nextHold(hold))) - } - .length - -def countWaysToWin(race: Race): Long = - val totalOutcomes = getTotalOutcomes(race.allowance) - val lossesFromLeft = countWaysToLoseLeft(race) - val allLosses = lossesFromLeft + (if (lossesFromLeft == totalOutcomes) 0 else countWaysToLoseRight(race)) - totalOutcomes - allLosses +import Day6.* +import Day6.RaceOutcome.* + +object Day6: + + case class Time(millis: Long): + def inc: Time = Time(millis + 1) + def dec: Time = Time(millis - 1) + + object Time: + given Numeric[Time] = Numeric[Long].imap(Time.apply)(_.millis) + def zero: Time = Time(0) + + case class Distance(millimeters: Long) + object Distance: + given Numeric[Distance] = Numeric[Long].imap(Distance.apply)(_.millimeters) + given Ordering[Distance] = Ordering.by(_.millimeters) + + case class Race(allowance: Time, record: Distance) + + enum RaceOutcome: + case Win + case Loss(travelled: Distance) + + enum Bounds: + case Within, Outside + + def getRaceOutcome(race: Race, hold: Time): RaceOutcome = + val travelled = getTravelledDistance(race.allowance, hold) + if (travelled > race.record) Win else Loss(travelled) + + def getTravelledDistance(allowance: Time, hold: Time): Distance = + val raceDuration = allowance - hold + val speed = hold.millis + Distance(speed * raceDuration.millis) + + def getTotalOutcomes(allowance: Time): Long = 1 + allowance.millis + + def getAllPossibleDistances(allowance: Time): List[Distance] = + List + .range[Long](0, getTotalOutcomes(allowance)) + .map(holdMillis => getTravelledDistance(allowance, hold = Time(holdMillis))) + + def countWaysToWin_bruteForce(race: Race): Long = getAllPossibleDistances(race.allowance).count(_ > race.record) + + def getMultipliedWaysToWin_bruteForce(rs: List[Race]): Long = rs.map(countWaysToWin_bruteForce).product + + def countWaysToLoseLeft(race: Race): Long = + countWaysToLose( + startHold = Time.zero, + holdBounds = hold => if (hold <= race.allowance) Bounds.Within else Bounds.Outside, + nextHold = _.inc, + race + ) + + def countWaysToLoseRight(race: Race): Long = + countWaysToLose( + startHold = race.allowance, + holdBounds = hold => if (hold >= Time.zero) Bounds.Within else Bounds.Outside, + nextHold = _.dec, + race + ) + + def countWaysToLose(startHold: Time, holdBounds: Time => Bounds, nextHold: Time => Time, race: Race): Long = + List + .unfold[Distance, Time](startHold) { hold => + holdBounds(hold) match + case Bounds.Outside => None + case Bounds.Within => + getRaceOutcome(race, hold) match + case Win => None + case Loss(travelled) => Some((travelled, nextHold(hold))) + } + .length + + def countWaysToWin(race: Race): Long = + val totalOutcomes = getTotalOutcomes(race.allowance) + val lossesFromLeft = countWaysToLoseLeft(race) + val allLosses = lossesFromLeft + (if (lossesFromLeft == totalOutcomes) 0 else countWaysToLoseRight(race)) + totalOutcomes - allLosses // recursion schemes 🧪🔬 @@ -87,7 +90,7 @@ import higherkindness.droste.data.* import higherkindness.droste.data.list.* import higherkindness.droste.scheme.* -object RecursionSchemesOption: +object Day6RecursionSchemesOption: type FAlgebra[A] = Algebra[Option, A] type FCoalgebra[A] = Coalgebra[Option, A] @@ -130,7 +133,7 @@ object RecursionSchemesOption: hyloM(countAlg.lift[Eval], losingDistancesRightCoAlg(race).lift[Eval]).apply(race.allowance).value // export SBT_OPTS="-Xmx2G" -object RecursionSchemesListF: +object Day6RecursionSchemesListF: type FAlgebra[A, B] = Algebra[[T] =>> ListF[A, T], B] type FCoalgebra[A, B] = Coalgebra[[T] =>> ListF[A, T], B] diff --git a/src/test/scala/Day6Suite.scala b/src/test/scala/Day6Suite.scala index e73594c..c02661c 100644 --- a/src/test/scala/Day6Suite.scala +++ b/src/test/scala/Day6Suite.scala @@ -1,6 +1,7 @@ import munit.ScalaCheckSuite import org.scalacheck.Gen import org.scalacheck.Prop.* +import Day6.* import Day6Suite.* class Day6Suite extends ScalaCheckSuite: