Skip to content

Commit

Permalink
Day 6: cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
horothesun committed Apr 1, 2024
1 parent 7b7bfed commit b44543b
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 79 deletions.
161 changes: 82 additions & 79 deletions src/main/scala/Day6.scala
Original file line number Diff line number Diff line change
@@ -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 🧪🔬

Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/Day6Suite.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import munit.ScalaCheckSuite
import org.scalacheck.Gen
import org.scalacheck.Prop.*
import Day6.*
import Day6Suite.*

class Day6Suite extends ScalaCheckSuite:
Expand Down

0 comments on commit b44543b

Please sign in to comment.