-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 74c0c19
Showing
85 changed files
with
1,574 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
src/main/resources/*.txt filter=git-crypt diff=git-crypt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: Tests | ||
|
||
on: [push] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up JDK 21 | ||
uses: actions/setup-java@v3 | ||
with: | ||
java-version: 21 | ||
distribution: temurin | ||
- name: Unlock git-crypt | ||
run: | | ||
sudo apt-get update | ||
sudo apt-get install -y git-crypt | ||
echo ${{ secrets.GIT_CRYPT_KEY }} | base64 -d | git-crypt unlock - | ||
- name: Run tests | ||
run: sbt test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
target | ||
*.sc | ||
project/project |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
version = "3.0.5" | ||
runner.dialect = scala3 | ||
align.preset = more | ||
maxColumn = 100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# aoc2021 | ||
|
||
[![Scala CI](https://github.com/lupari/aoc2021/actions/workflows/scala.yml/badge.svg)](https://github.com/lupari/aoc2021/actions?query=workflow%3A%22Scala+CI%22) | ||
|
||
Advent of Code 2021 (http://adventofcode.com/2021) personal exercises in Scala | ||
|
||
For running these you need SBT (http://scala-sbt.org) installed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name := "aoc2021" | ||
|
||
version := "0.1" | ||
|
||
scalaVersion := "3.1.0" | ||
|
||
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.0" | ||
|
||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.10" % "test" | ||
|
||
scalacOptions += "-deprecation" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version = 1.5.5 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
|
||
object Day01: | ||
|
||
val input: List[Int] = Source.fromResource("day01.txt").getLines().map(_.toInt).toList | ||
|
||
def partOne(): Int = input.sliding(2).count(p => p.last > p.head) | ||
def partTwo(): Int = input.sliding(4).count(p => p.last > p.head) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import lib.Points.{Point, Position} | ||
|
||
object Day02: | ||
|
||
trait Cmd | ||
case class MoveHorizontal(amount: Int) extends Cmd | ||
case class MoveVertical(amount: Int) extends Cmd | ||
|
||
trait Pos: | ||
val p: Point | ||
val product: Int = p.x * p.y | ||
def moveHorizontal(n: Int): Pos | ||
def moveVertical(n: Int): Pos | ||
|
||
case class Pos1(p: Point) extends Pos: | ||
override def moveHorizontal(n: Int) = Pos1(p + Point(n, 0)) | ||
override def moveVertical(n: Int) = Pos1(p + Point(0, n)) | ||
|
||
case class Pos2(p: Point, aim: Int = 0) extends Pos: | ||
override def moveHorizontal(n: Int) = copy(p = p + Point(n, aim * n)) | ||
override def moveVertical(n: Int) = copy(aim = aim + n) | ||
|
||
private val regex = """(forward|down|up) (\d+)""".r | ||
def parse(cmd: String): Cmd = cmd match | ||
case regex(name, amount) => | ||
name match | ||
case "forward" => MoveHorizontal(amount.toInt) | ||
case "down" => MoveVertical(amount.toInt) | ||
case "up" => MoveVertical(-amount.toInt) | ||
|
||
def advance(pos: Pos, cmd: Cmd): Pos = cmd match | ||
case MoveHorizontal(n) => pos.moveHorizontal(n) | ||
case MoveVertical(n) => pos.moveVertical(n) | ||
|
||
val input: List[Cmd] = Source.fromResource("day02.txt").getLines().map(parse).toList | ||
|
||
def partOne(): Int = input.foldLeft(Pos1(Position.zero))(advance).product | ||
def partTwo(): Int = input.foldLeft(Pos2(Position.zero))(advance).product |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import scala.annotation.tailrec | ||
import lib.Numbers._ | ||
|
||
object Day03: | ||
|
||
def commonest(xs: List[Char]): Char = if xs.count(_ == '0') > xs.length / 2 then '0' else '1' | ||
def rarest(xs: List[Char]): Char = if commonest(xs) == '0' then '1' else '0' | ||
def findMatch(xs: List[String])(fn: (List[Char]) => Char): String = | ||
@tailrec | ||
def helper(xs: List[String], i: Int): String = xs match | ||
case h :: Nil => h | ||
case _ => | ||
val c = xs.transpose.map(fn)(i) | ||
helper(xs.filter(x => x(i) == c), i + 1) | ||
|
||
helper(xs, 0) | ||
|
||
val input: List[String] = Source.fromResource("day03.txt").getLines().toList | ||
|
||
def partOne(): Long = | ||
val gamma = input.transpose.map(commonest).mkString | ||
val epsilon = input.transpose.map(rarest).mkString | ||
bin2dec(gamma) * bin2dec(epsilon) | ||
|
||
def partTwo(): Long = | ||
val o = findMatch(input)(commonest) | ||
val co2 = findMatch(input)(rarest) | ||
bin2dec(o) * bin2dec(co2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import scala.annotation.tailrec | ||
|
||
object Day04: | ||
|
||
case class Player(board: List[Set[Int]]): | ||
def mark(n: Int): Player = copy(board = board.map(_ - n)) | ||
val score: Int = board.flatten.toSet.sum | ||
val hasBingo: Boolean = board.exists(_.isEmpty) | ||
|
||
def toPlayer(lines: List[List[Int]]): Player = Player((lines ++ lines.transpose).map(_.toSet)) | ||
|
||
def findScore()(fn: (List[Player]) => (Option[Player], List[Player])): Option[Int] = | ||
@tailrec | ||
def helper(xs: List[Int], ps: List[Player]): Option[Int] = xs match | ||
case Nil => None | ||
case h :: t => | ||
fn(ps.map(_.mark(h))) match | ||
case (None, ps2) => helper(t, ps2) | ||
case (Some(p), _) => Option(p.score * h) | ||
|
||
helper(numbers, players) | ||
|
||
val input: List[String] = Source.fromResource("day04.txt").getLines().toList | ||
val numbers: List[Int] = input.head.split(",").map(_.toInt).toList | ||
val players: List[Player] = | ||
input | ||
.drop(2) | ||
.grouped(6) | ||
.map(_.filter(_.nonEmpty)) | ||
.map(ls => toPlayer(ls.map(_.trim.split(" +").toList.map(_.toInt)))) | ||
.toList | ||
|
||
def partOne(): Int = findScore()(ps => (ps.find(_.hasBingo), ps)).get | ||
def partTwo(): Int = | ||
def findLoser = (ps: List[Player]) => | ||
val losers = ps.filterNot(_.hasBingo) | ||
if losers.isEmpty && ps.length == 1 then (Some(ps.head), Nil) else (None, losers) | ||
|
||
findScore()(findLoser).get |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import lib.Points.{Point, Line} | ||
|
||
object Day05: | ||
|
||
private val regex = """(\d+),(\d+) -> (\d+),(\d+)""".r | ||
def parse(s: String): Line = s match | ||
case regex(x1, y1, x2, y2) => Line(Point(x1.toInt, y1.toInt), Point(x2.toInt, y2.toInt)) | ||
|
||
def intersections(lines: List[Line]): Int = | ||
lines | ||
.flatMap(_.points()) | ||
.groupBy(identity) | ||
.values | ||
.count(_.length > 1) | ||
|
||
val input: List[Line] = Source.fromResource("day05.txt").getLines().map(parse).toList | ||
def partOne(): Int = intersections(input.filter(l => l.dx == 0 || l.dy == 0)) | ||
def partTwo(): Int = intersections(input) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
|
||
object Day06: | ||
|
||
def evolve(population: Map[Int, Long]): Map[Int, Long] = | ||
val population2 = population.filter(_._1 > 0).map(p => p._1 - 1 -> p._2) | ||
val eights = population.get(0).getOrElse(0L) | ||
val sixes = eights + population.get(7).getOrElse(0L) | ||
population2 + (6 -> sixes) + (8 -> eights) | ||
|
||
val input: List[Int] = Source.fromResource("day06.txt").mkString.split(",").map(_.toInt).toList | ||
val population: Map[Int, Long] = input.groupMapReduce(identity)(_ => 1L)(_ + _) | ||
|
||
def partOne(): Long = Iterator.iterate(population)(evolve).drop(80).next.values.sum | ||
def partTwo(): Long = Iterator.iterate(population)(evolve).drop(256).next.values.sum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import lib.Math._ | ||
|
||
object Day07: | ||
|
||
def fuelCost(n: Int)(costFn: (Int, Int) => Int): Int = crabs.map(costFn(n, _)).sum | ||
|
||
val crabs: List[Int] = Source.fromResource("day07.txt").mkString.split(",").map(_.toInt).toList | ||
|
||
def partOne(): Int = fuelCost(median(crabs))((a, b) => (a - b).abs) | ||
def partTwo(): Int = | ||
val avg = mean(crabs) | ||
Seq(avg.floor.toInt, avg.ceil.toInt) | ||
.map(fuelCost(_)((a, b) => arithmeticSeries(a, b, normalized = true))) | ||
.min |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
|
||
object Day08: | ||
|
||
type Digit = Set[Char] | ||
case class Entry(pattern: Seq[Digit], output: Seq[Digit]) | ||
|
||
def find06(s1: Digit, s2: Digit, five: Digit) = | ||
if (five &~ s1).nonEmpty then (s1, s2) else (s2, s1) | ||
|
||
def find25(s1: Digit, s2: Digit, nine: Digit) = | ||
if (s1 &~ nine).nonEmpty then (s1, s2) else (s2, s1) | ||
|
||
def find3(s1: Digit, s2: Digit, s3: Digit) = | ||
s1 &~ (s2 | s3) match | ||
case s if s.isEmpty => (s1, (s2, s3)) | ||
case _ => | ||
if (s2 &~ (s1 | s3)).isEmpty then (s2, (s1, s3)) else (s3, (s1, s2)) | ||
|
||
def find9(s1: Digit, s2: Digit, s3: Digit, three: Digit) = | ||
three &~ s1 match | ||
case s if s.isEmpty => (s1, (s2, s3)) | ||
case _ => | ||
if (three &~ s2).isEmpty then (s2, (s1, s3)) else (s3, (s1, s2)) | ||
|
||
def decode(entry: Entry): Int = | ||
val Seq(f1, f2, f3) = entry.pattern.filter(_.size == 5) | ||
val Seq(s1, s2, s3) = entry.pattern.filter(_.size == 6) | ||
val (three, (f4, f5)) = find3(f1, f2, f3) | ||
val (nine, (s4, s5)) = find9(s1, s2, s3, three) | ||
val (two, five) = find25(f4, f5, nine) | ||
val (zero, six) = find06(s4, s5, five) | ||
val cipher = Map(0 -> zero, 2 -> two, 3 -> three, 5 -> five, 6 -> six, 9 -> nine) | ||
|
||
def num(d: Digit): Option[Int] = predef.get(d.size).orElse(cipher.find(_._2 == d).map(_._1)) | ||
|
||
entry.output.flatMap(num).mkString.toInt | ||
|
||
def parse(s: String): Entry = | ||
def _parse(xs: String) = xs.split(" ").map(_.toSet).toSeq | ||
val (pattern, output) = (s.takeWhile(_ != '|'), s.dropWhile(_ != '|')) | ||
Entry(_parse(pattern), _parse(output)) | ||
|
||
val entries: List[Entry] = Source.fromResource("day08.txt").getLines().map(parse).toList | ||
val predef: Map[Int, Int] = Map(2 -> 1, 3 -> 7, 4 -> 4, 7 -> 8) | ||
|
||
def partOne(): Int = entries.flatMap(_.output).count(o => predef.contains(o.size)) | ||
def partTwo(): Int = entries.map(decode).sum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import scala.annotation.tailrec | ||
import lib.Points.Point | ||
import lib.Graphs | ||
|
||
object Day09: | ||
|
||
import lib.GridExtensions._ | ||
|
||
def isLowPoint(p: Point): Boolean = p.neighbors.forall(n => grid(n) > grid(p)) | ||
|
||
lazy val basins: List[Set[Point]] = | ||
@tailrec | ||
def helper(xs: Set[Point], acc: List[Set[Point]]): List[Set[Point]] = xs match | ||
case s if s.isEmpty => acc | ||
case s => | ||
val basin = Graphs.bfs(s.head)(_.neighbors.filterNot(grid(_) == 9)).keySet | ||
helper(s -- basin, acc :+ basin) | ||
|
||
helper(lowPoints.keySet, Nil) | ||
|
||
val grid: Grid[Int] = | ||
Source.fromResource("day09.txt").mkString.toList.toIntGrid.withDefaultValue(9) | ||
val lowPoints: Grid[Int] = grid.filter(kv => isLowPoint(kv._1)) | ||
|
||
def partOne(): Int = lowPoints.map(_._2 + 1).sum | ||
def partTwo(): Int = basins.map(_.size).sorted.takeRight(3).product |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package challenge | ||
|
||
import scala.io.Source | ||
import scala.annotation.tailrec | ||
|
||
object Day10: | ||
|
||
type Inspection = Either[Char, List[Char]] | ||
case class Chunk(closure: Char, errorScore: Int, completionScore: Int) | ||
val chunks = Map( | ||
'(' -> Chunk(')', 3, 1), | ||
'[' -> Chunk(']', 57, 2), | ||
'{' -> Chunk('}', 1197, 3), | ||
'<' -> Chunk('>', 25137, 4) | ||
) | ||
|
||
def errorScore(c: Char): Int = chunks.find(_._2.closure == c).get._2.errorScore | ||
def autocomplete(xs: List[Char]): Long = | ||
xs.reverse.foldLeft(0L)((a, b) => a * 5 + chunks(b).completionScore) | ||
|
||
def inspect(s: String): Inspection = | ||
@tailrec | ||
def helper(xs: List[Char], unclosed: List[Char]): Inspection = xs match | ||
case Nil => Right(unclosed) | ||
case h :: t => | ||
chunks.find(_._2.closure == h) match | ||
case None => | ||
helper(t, unclosed :+ h) | ||
case Some(x, _) => | ||
if unclosed.last == x then helper(t, unclosed.init) | ||
else Left(h) | ||
|
||
helper(s.toList.tail, List(s.head)) | ||
|
||
val input: List[String] = Source.fromResource("day10.txt").getLines().toList | ||
val lines: List[Inspection] = input.map(inspect) | ||
|
||
def partOne(): Int = lines.collect({ case Left(e) => errorScore(e) }).sum | ||
def partTwo(): Long = | ||
val scores = lines.collect({ case Right(l) => autocomplete(l) }) | ||
scores.sorted.drop(scores.length / 2).head |
Oops, something went wrong.