Commit 9d2f3e12 authored by Alexander Gehrke's avatar Alexander Gehrke
Browse files

Restructure: one namespace per day

parent 5fee5e2a
package aoc2020
def day1(input: List[Int]): String =
List(2, 3).map(groupSize => input.groupWithSum(2020, groupSize).product)
.mkString("\n")
extension (input: List[Int]) def groupWithSum(sum: Int, groupSize: Int = 2): List[Int] =
input.combinations(groupSize).filter(_.sum == sum).next
object day1 extends (List[Int] => String) {
def apply(input: List[Int]): String =
List(2, 3).map(groupSize => input.groupWithSum(2020, groupSize).product)
.mkString("\n")
extension (input: List[Int]) def groupWithSum(sum: Int, groupSize: Int = 2): List[Int] =
input.combinations(groupSize).filter(_.sum == sum).next
}
package aoc2020
import aoc2020.lib._
object day10 extends (List[Int] => String) {
def apply(ratings: List[Int]): String =
val adapters = (0 :: (ratings.max + 3) :: ratings).sorted
val joltages = adapters.sliding(2,1).toSeq
.groupMapReduce{case List(low, high) => high - low}(_ => 1)(_ + _)
def day10(ratings: List[Int]): String =
val adapters = (0 :: (ratings.max + 3) :: ratings).sorted
val joltages = adapters.sliding(2,1).toSeq
.groupMapReduce{case List(low, high) => high - low}(_ => 1)(_ + _)
val variants = adapterVariants(0, adapters.tail)
val variants = adapterVariants(0, adapters.tail)
s"Joltage differences:\n 1 Jolt: ${joltages(1)}\n 3 Jolts: ${joltages(3)}\n"+
s" Product: ${joltages(1) * joltages(3)}\n" +
s"\nPossible variants: ${variants}"
s"Joltage differences:\n 1 Jolt: ${joltages(1)}\n 3 Jolts: ${joltages(3)}\n"+
s" Product: ${joltages(1) * joltages(3)}\n" +
s"\nPossible variants: ${variants}"
val adapterVariants: ((Int, List[Int])) => Long = memoize((prev, remaining) => {
remaining match {
case h :: t if h < prev + 4 => adapterVariants(h, t) + adapterVariants(prev, t)
case last :: Nil => 1
case _ => 0
}
})
val adapterVariants: ((Int, List[Int])) => Long = memoize((prev, remaining) => {
remaining match {
case h :: t if h < prev + 4 => adapterVariants(h, t) + adapterVariants(prev, t)
case last :: Nil => 1
case _ => 0
}
})
}
package aoc2020
import Coords._
def day11(seatMap: Vector[Vector[Boolean]]): String =
val height = seatMap.size
val width = seatMap(0).size
val coords: Set[Coord] = for {
y <- (0 until height).toSet
x <- 0 until width
if seatMap(y)(x)
} yield Coord(y, x)
val visibilityLOS: Coord => List[Coord] = _.lineOfSight(height, width)(coords)
"Basic rules (neighbours < 4)\n" +
gameOfSeats(occupiedAfterRound(3)(_.neighbours))(coords, Set.empty).toString +
"\nLOS rules (neighbours < 5)\n" +
gameOfSeats(occupiedAfterRound(4)(visibilityLOS))(coords, Set.empty).toString
@annotation.tailrec
def gameOfSeats(occupationRule: Set[Coord] => Coord => Boolean)(coords: Set[Coord], states: Set[Coord]): Int =
val newStates = coords.filter(occupationRule(states))
if newStates == states then newStates.size
else gameOfSeats(occupationRule)(coords, newStates)
def occupiedAfterRound(maxNeighbours: Int)(visible: Coord => List[Coord])(occupied: Set[Coord])(c: Coord) =
val occNeighbours = visible(c).count(occupied)
occNeighbours == 0 || (occupied(c) && occNeighbours <= maxNeighbours)
object Coords {
opaque type Coord = (Int, Int)
extension (c: Coord)
def neighbours: List[Coord] = List(
(c._1 - 1, c._2),
(c._1 + 1, c._2),
(c._1 , c._2 - 1),
(c._1 - 1, c._2 - 1),
(c._1 + 1, c._2 - 1),
(c._1 , c._2 + 1),
(c._1 - 1, c._2 + 1),
(c._1 + 1, c._2 + 1),
)
def lineOfSight(height: Int, width: Int)(seats: Set[Coord]): List[Coord] =
def line(dirY: Int, dirX: Int, pos: Coord): Coord =
val npos = (pos._1 + dirY, pos._2 + dirX)
if (npos._1 >= height || npos._1 < 0 || npos._2 >= width || npos._2 < 0 || seats(npos))
npos
else
line(dirY, dirX, npos)
List(
line(-1, 0, c),
line(+1, 0, c),
line( 0, -1, c),
line( 0, +1, c),
line(+1, +1, c),
line(+1, -1, c),
line(-1, +1, c),
line(-1, -1, c),
)
object day11 extends (Vector[Vector[Boolean]] => String) {
import Coords._
def apply(seatMap: Vector[Vector[Boolean]]): String =
val height = seatMap.size
val width = seatMap(0).size
val coords: Set[Coord] = for {
y <- (0 until height).toSet
x <- 0 until width
if seatMap(y)(x)
} yield Coord(y, x)
val visibilityLOS: Coord => List[Coord] = _.lineOfSight(height, width)(coords)
"Basic rules (neighbours < 4)\n" +
gameOfSeats(occupiedAfterRound(3)(_.neighbours))(coords, Set.empty).toString +
"\nLOS rules (neighbours < 5)\n" +
gameOfSeats(occupiedAfterRound(4)(visibilityLOS))(coords, Set.empty).toString
@annotation.tailrec
def gameOfSeats(occupationRule: Set[Coord] => Coord => Boolean)(coords: Set[Coord], states: Set[Coord]): Int =
val newStates = coords.filter(occupationRule(states))
if newStates == states then newStates.size
else gameOfSeats(occupationRule)(coords, newStates)
def occupiedAfterRound(maxNeighbours: Int)(visible: Coord => List[Coord])(occupied: Set[Coord])(c: Coord) =
val occNeighbours = visible(c).count(occupied)
occNeighbours == 0 || (occupied(c) && occNeighbours <= maxNeighbours)
def Coord(x: Int, y: Int): Coord = (x,y)
def stateToString(height: Int, width: Int)(seats: Set[Coord], states: Set[Coord]):String =
Vector.tabulate(height, width)((y,x) =>
if (!seats((y,x))) '.'
else if (states((y,x))) '#'
else 'L'
object Coords {
opaque type Coord = (Int, Int)
extension (c: Coord)
def neighbours: List[Coord] = List(
(c._1 - 1, c._2),
(c._1 + 1, c._2),
(c._1 , c._2 - 1),
(c._1 - 1, c._2 - 1),
(c._1 + 1, c._2 - 1),
(c._1 , c._2 + 1),
(c._1 - 1, c._2 + 1),
(c._1 + 1, c._2 + 1),
)
def lineOfSight(height: Int, width: Int)(seats: Set[Coord]): List[Coord] =
def line(dirY: Int, dirX: Int, pos: Coord): Coord =
val npos = (pos._1 + dirY, pos._2 + dirX)
if (npos._1 >= height || npos._1 < 0 || npos._2 >= width || npos._2 < 0 || seats(npos))
npos
else
line(dirY, dirX, npos)
List(
line(-1, 0, c),
line(+1, 0, c),
line( 0, -1, c),
line( 0, +1, c),
line(+1, +1, c),
line(+1, -1, c),
line(-1, +1, c),
line(-1, -1, c),
)
.map(_.mkString)
.mkString("\n")
}
def Coord(x: Int, y: Int): Coord = (x,y)
def stateToString(height: Int, width: Int)(seats: Set[Coord], states: Set[Coord]):String =
Vector.tabulate(height, width)((y,x) =>
if (!seats((y,x))) '.'
else if (states((y,x))) '#'
else 'L'
)
.map(_.mkString)
.mkString("\n")
}
}
......@@ -3,80 +3,82 @@ import aoc2020.lib._
import aoc2020.lib.Vectors._
import aoc2020.lib.Directions._
def day12(input: List[String]): String =
val directions = Instruction.parse(input)
val path1 = Ship(East, Vec2D(0,0)).path(directions)
val path2 = WaypointShip(Vec2D(0,0), Vec2D(10,1)).path(directions)
// Output
val path1Tikz = tikzPath(path1.map(_.pos), opts = "thick")
val path2Tikz = tikzPath(path2.map(_.ship), scale = 0.001, opts = "thick")
val finalPos1 = path1.last
val finalPos2 = path2.last
object day12 extends (List[String] => String) {
def apply(input: List[String]): String =
val directions = Instruction.parse(input)
val path1 = Ship(East, Vec2D(0,0)).path(directions)
val path2 = WaypointShip(Vec2D(0,0), Vec2D(10,1)).path(directions)
s""" Final position (direct orders): ${finalPos1.pos} (L₁: ${finalPos1.pos.manhattan}), facing ${finalPos1.look.str}
| Final position (waypoint): ${finalPos2.ship} (L₁: ${finalPos2.ship.manhattan}), waypoint at ${finalPos2.nav}
|
| Tikz path for direct orders:
| $path1Tikz
|
| Tikz path for waypoint navigation:
| $path2Tikz
|""".stripMargin
// Output
val path1Tikz = tikzPath(path1.map(_.pos), opts = "thick")
val path2Tikz = tikzPath(path2.map(_.ship), scale = 0.001, opts = "thick")
val finalPos1 = path1.last
val finalPos2 = path2.last
s""" Final position (direct orders): ${finalPos1.pos} (L₁: ${finalPos1.pos.manhattan}), facing ${finalPos1.look.str}
| Final position (waypoint): ${finalPos2.ship} (L₁: ${finalPos2.ship.manhattan}), waypoint at ${finalPos2.nav}
|
| Tikz path for direct orders:
| $path1Tikz
|
| Tikz path for waypoint navigation:
| $path2Tikz
|""".stripMargin
enum Instruction {
case Rot(deg: Dir)
case Move(dir: Dir, dist: Int)
case Forward(dist: Int)
}
import Instruction._
object Instruction {
def parse(input: List[String]): List[Instruction] =
input.map(s => (s(0), s.substring(1).nn.toInt) match {
case ('L', deg) => Rot(Dir(deg))
case ('R', deg) => Rot(Dir(-deg))
case ('F', dist) => Forward(dist)
case (dir, dist) => Move(dir.cardinal, dist)
})
}
trait Nav[T] { self: T =>
def follow(directions: List[Instruction]): T =
directions.foldLeft(self)(_ follow _)
enum Instruction {
case Rot(deg: Dir)
case Move(dir: Dir, dist: Int)
case Forward(dist: Int)
}
import Instruction._
object Instruction {
def parse(input: List[String]): List[Instruction] =
input.map(s => (s(0), s.substring(1).nn.toInt) match {
case ('L', deg) => Rot(Dir(deg))
case ('R', deg) => Rot(Dir(-deg))
case ('F', dist) => Forward(dist)
case (dir, dist) => Move(dir.cardinal, dist)
})
}
def path(directions: List[Instruction]): List[T] =
directions.scanLeft(self)(_ follow _)
trait Nav[T] { self: T =>
def follow(directions: List[Instruction]): T =
directions.foldLeft(self)(_ follow _)
def follow(command: Instruction): T & Nav[T]
}
def path(directions: List[Instruction]): List[T] =
directions.scanLeft(self)(_ follow _)
/* Part 1: Navigate by interpreting instructions as relative ship movements */
case class Ship(look: Dir, pos: Vec2D[Int]) extends Nav[Ship] {
def rotate(deg: Dir) = copy(look = look + deg)
def move(dir: Dir, dist: Int) = copy(pos = pos.move(dir, dist))
def follow(command: Instruction): T & Nav[T]
}
/* Part 1: Navigate by interpreting instructions as relative ship movements */
case class Ship(look: Dir, pos: Vec2D[Int]) extends Nav[Ship] {
def rotate(deg: Dir) = copy(look = look + deg)
def move(dir: Dir, dist: Int) = copy(pos = pos.move(dir, dist))
def follow(command: Instruction): Ship = command match {
case Rot(deg) => rotate(deg)
case Forward(dist) => move(look, dist)
case Move(dir, dist) => move(dir, dist)
def follow(command: Instruction): Ship = command match {
case Rot(deg) => rotate(deg)
case Forward(dist) => move(look, dist)
case Move(dir, dist) => move(dir, dist)
}
}
}
/* Part 2: Navigate by interpreting instructions as moving a waypoint relative
* to the ship */
case class WaypointShip(ship: Vec2D[Int], nav: Vec2D[Int]) extends Nav[WaypointShip] {
def rotate(deg: Dir) = copy(nav = nav.rot(deg))
def move(dir: Dir, dist: Int) = copy(nav = nav.move(dir, dist))
def forward(factor: Int) = copy(ship = ship + nav * factor)
/* Part 2: Navigate by interpreting instructions as moving a waypoint relative
* to the ship */
case class WaypointShip(ship: Vec2D[Int], nav: Vec2D[Int]) extends Nav[WaypointShip] {
def rotate(deg: Dir) = copy(nav = nav.rot(deg))
def move(dir: Dir, dist: Int) = copy(nav = nav.move(dir, dist))
def forward(factor: Int) = copy(ship = ship + nav * factor)
def follow(command: Instruction): WaypointShip = command match {
case Rot(deg) => rotate(deg)
case Forward(dist) => forward(dist)
case Move(dir, dist) => move(dir, dist)
def follow(command: Instruction): WaypointShip = command match {
case Rot(deg) => rotate(deg)
case Forward(dist) => forward(dist)
case Move(dir, dist) => move(dir, dist)
}
}
}
def tikzPath(points: List[Vec2D[Int]], scale: Double = 0.1, opts: String = ""): String =
points.map(p => f"(${p.x * scale}%.4f, ${p.y * scale}%.4f)").mkString(s"\\draw[$opts] ","--", ";")
def tikzPath(points: List[Vec2D[Int]], scale: Double = 0.1, opts: String = ""): String =
points.map(p => f"(${p.x * scale}%.4f, ${p.y * scale}%.4f)").mkString(s"\\draw[$opts] ","--", ";")
}
......@@ -3,63 +3,65 @@ import aoc2020.lib._
import scala.math.{min,max}
import cats._, cats.implicits.given
def day13(input: List[String]): String =
input match {
case timestamp :: busses :: Nil =>
val idsWithOffsets = busses.split(",").nn
.zipWithIndex
.filter(_._1 != "x")
.map { (id, offset) => (id.toLong, offset.toLong) }
object day13 extends (List[String] => String) {
def apply(input: List[String]): String =
input match {
case timestamp :: busses :: Nil =>
val idsWithOffsets = busses.split(",").nn
.zipWithIndex
.filter(_._1 != "x")
.map { (id, offset) => (id.toLong, offset.toLong) }
val (next, wait) = findEarliest(timestamp.toLong, idsWithOffsets.map(_._1))
val (next, wait) = findEarliest(timestamp.toLong, idsWithOffsets.map(_._1))
val contestTimestamp = findContinuousDepartures(idsWithOffsets)
s"Next bus: $next in $wait minutes (solution: ${next * wait})\nPlanning Contest timestamp: $contestTimestamp"
val contestTimestamp = findContinuousDepartures(idsWithOffsets)
s"Next bus: $next in $wait minutes (solution: ${next * wait})\nPlanning Contest timestamp: $contestTimestamp"
case _ => "Expected two lines of input"
}
case _ => "Expected two lines of input"
}
def findEarliest(timestamp: Long, departures: List[Long]): (Long, Long) =
departures.map(id => (id, id - timestamp % id)).minBy(_._2)
def findEarliest(timestamp: Long, departures: List[Long]): (Long, Long) =
departures.map(id => (id, id - timestamp % id)).minBy(_._2)
import scala.math.BigInt
def findContinuousDepartures(idsWithOffsets: List[(Long,Long)]): BigInt =
import scala.math.BigInt
def findContinuousDepartures(idsWithOffsets: List[(Long,Long)]): BigInt =
// naive apporach, much too slow
//val first :: later = idsWithOffsets
//LazyList.iterate(0L)(_ + first).find(start => {
// later.forall((id, offset) => (start + offset) % id == 0)
// }).get
/* using https://en.wikipedia.org/wiki/Chinese_remainder_theorem#Generalization_to_non-coprime_moduli
* the solution can be found in one iteration */
val mods = idsWithOffsets.map((id, off) => (BigInt(id), BigInt((id - off) % id)))
// naive apporach, much too slow
//val first :: later = idsWithOffsets
//LazyList.iterate(0L)(_ + first).find(start => {
// later.forall((id, offset) => (start + offset) % id == 0)
// }).get
mods.tail.foldLeft(mods.head) { case ((mod, time), (nmod, offset)) =>
val (_, s, t) = extendedEuclid(mod, nmod)
val modprod = mod * nmod
val ntime = (time * s * nmod + offset * t * mod).mod(modprod)
(modprod, ntime)
}._2
/* using https://en.wikipedia.org/wiki/Chinese_remainder_theorem#Generalization_to_non-coprime_moduli
* the solution can be found in one iteration */
val mods = idsWithOffsets.map((id, off) => (BigInt(id), BigInt((id - off) % id)))
/** extended euclidian algorithm
* https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
* @return (gcd, Bézout coeffients s, t) */
def extendedEuclid(a: BigInt, b: BigInt): (BigInt,BigInt,BigInt) =
@annotation.tailrec
def rec(r: BigInt, rp: BigInt, s: BigInt, sp: BigInt, t: BigInt, tp: BigInt): (BigInt, BigInt, BigInt) =
val q = rp / r
val rn = rp - r * q
val sn = sp - s * q
val tn = tp - t * q
if rn == 0 then (r, s, t)
else rec(rn, r, sn, s, tn, t)
rec(a, b, 0, 1, 1, 0)
mods.tail.foldLeft(mods.head) { case ((mod, time), (nmod, offset)) =>
val (_, s, t) = extendedEuclid(mod, nmod)
val modprod = mod * nmod
val ntime = (time * s * nmod + offset * t * mod).mod(modprod)
(modprod, ntime)
}._2
def lcm(nums: Vector[Long]): Long =
def rec(mi: Vector[Long]): Long =
val (m, i) = mi.zipWithIndex.min
val miNext = mi.updated(i, m + nums(i))
if miNext.distinct.size == 1 then miNext.head
else rec(miNext)
rec(nums)
/** extended euclidian algorithm
* https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
* @return (gcd, Bézout coeffients s, t) */
def extendedEuclid(a: BigInt, b: BigInt): (BigInt,BigInt,BigInt) =
@annotation.tailrec
def rec(r: BigInt, rp: BigInt, s: BigInt, sp: BigInt, t: BigInt, tp: BigInt): (BigInt, BigInt, BigInt) =
val q = rp / r
val rn = rp - r * q
val sn = sp - s * q
val tn = tp - t * q
if rn == 0 then (r, s, t)
else rec(rn, r, sn, s, tn, t)
rec(a, b, 0, 1, 1, 0)
def lcm(nums: Vector[Long]): Long =
def rec(mi: Vector[Long]): Long =
val (m, i) = mi.zipWithIndex.min
val miNext = mi.updated(i, m + nums(i))
if miNext.distinct.size == 1 then miNext.head
else rec(miNext)
rec(nums)
}
......@@ -2,63 +2,66 @@ package aoc2020
import java.lang.Long.parseLong
import cats._
import cats.implicits.given
import Mask.Mask
def day14(input: List[String]): String =
val s"mask = $firstMask" = input.head
val maskedWrites = run(input)(s => MaskedMem(Mask(s)))
val maskedAddrs = run(input)(s => MaskedAddr(Mask.floating(s)))
object day14 extends (List[String] => String) {
val sumOfMemoryMW = maskedWrites.mem.values.sum
val sumOfMemoryMA = maskedAddrs.mem.values.sum
s"""Sum of memory (masked writes): $sumOfMemoryMW
|Sum of memory (masked addresses): $sumOfMemoryMA
""".stripMargin
import Mask.Mask
def apply(input: List[String]): String =
val s"mask = $firstMask" = input.head
val maskedWrites = run(input)(s => MaskedMem(Mask(s)))
val maskedAddrs = run(input)(s => MaskedAddr(Mask.floating(s)))
def run(input: List[String])(memInit: String => Mem): Mem =
val s"mask = $firstMask" = input.head
input.tail.foldLeft(memInit(firstMask)){
case (mem, s"mask = $mask") => mem.updateMask(mask)
case (mem, s"mem[$addr] = $value") => mem.update(addr.toInt, value.toLong)
}
val sumOfMemoryMW = maskedWrites.mem.values.sum
val sumOfMemoryMA = maskedAddrs.mem.values.sum
s"""Sum of memory (masked writes): $sumOfMemoryMW
|Sum of memory (masked addresses): $sumOfMemoryMA
""".stripMargin
def run(input: List[String])(memInit: String => Mem): Mem =
val s"mask = $firstMask" = input.head
input.tail.foldLeft(memInit(firstMask)){
case (mem, s"mask = $mask") => mem.updateMask(mask)
case (mem, s"mem[$addr] = $value") => mem.update(addr.toInt, value.toLong)
}
trait Mem {
def mem: Map[Long, Long]
def update(addr: Long, value: Long): Mem
def updateMask(maskCode: String): Mem
}
case class MaskedMem(mask: Mask, mem: Map[Long, Long] = Map()) extends Mem {
def update(addr: Long, value: Long): MaskedMem =
copy(mem = mem.updated(addr, mask(value)))
trait Mem {
def mem: Map[Long, Long]
def update(addr: Long, value: Long): Mem
def updateMask(maskCode: String): Mem
}
def updateMask(maskCode: String): MaskedMem =
copy(mask = Mask(maskCode))
}
case class MaskedMem(mask: Mask, mem: Map[Long, Long] = Map()) extends Mem {
def update(addr: Long, value: Long): MaskedMem =
copy(mem = mem.updated(addr, mask(value)))
case class MaskedAddr(masks: List[Mask], mem: Map[Long, Long] = Map()) extends Mem {
def update(addr: Long, value: Long): MaskedAddr =
copy(mem = mem ++ masks.map(m => m(addr) -> value))
def updateMask(maskCode: String): MaskedMem =
copy(mask = Mask(maskCode))
}
def updateMask(maskCode: String): MaskedAddr =
copy(masks = Mask.floating(maskCode))
}
case class MaskedAddr(masks: List[Mask], mem: Map[Long, Long] = Map()) extends Mem {
def update(addr: Long, value: Long): MaskedAddr =
copy(mem = mem ++ masks.map(m => m(addr) -> value))