Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lupari committed Dec 3, 2024
0 parents commit 74c0c19
Show file tree
Hide file tree
Showing 85 changed files with 1,574 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/main/resources/*.txt filter=git-crypt diff=git-crypt
22 changes: 22 additions & 0 deletions .github/workflows/scala.yml
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target
*.sc
project/project
4 changes: 4 additions & 0 deletions .scalafmt.conf
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
7 changes: 7 additions & 0 deletions README.md
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.
11 changes: 11 additions & 0 deletions build.sbt
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"
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 1.5.5
Binary file added src/main/resources/day01.txt
Binary file not shown.
Binary file added src/main/resources/day02.txt
Binary file not shown.
Binary file added src/main/resources/day03.txt
Binary file not shown.
Binary file added src/main/resources/day04.txt
Binary file not shown.
Binary file added src/main/resources/day05.txt
Binary file not shown.
Binary file added src/main/resources/day06.txt
Binary file not shown.
Binary file added src/main/resources/day07.txt
Binary file not shown.
Binary file added src/main/resources/day08.txt
Binary file not shown.
Binary file added src/main/resources/day09.txt
Binary file not shown.
Binary file added src/main/resources/day10.txt
Binary file not shown.
Binary file added src/main/resources/day11.txt
Binary file not shown.
Binary file added src/main/resources/day12.txt
Binary file not shown.
Binary file added src/main/resources/day13.txt
Binary file not shown.
Binary file added src/main/resources/day14.txt
Binary file not shown.
Binary file added src/main/resources/day15.txt
Binary file not shown.
Binary file added src/main/resources/day16.txt
Binary file not shown.
Binary file added src/main/resources/day17.txt
Binary file not shown.
Binary file added src/main/resources/day18.txt
Binary file not shown.
Binary file added src/main/resources/day19.txt
Binary file not shown.
Binary file added src/main/resources/day20.txt
Binary file not shown.
Binary file added src/main/resources/day21.txt
Binary file not shown.
Binary file added src/main/resources/day22.txt
Binary file not shown.
Binary file added src/main/resources/day24.txt
Binary file not shown.
Binary file added src/main/resources/day25.txt
Binary file not shown.
10 changes: 10 additions & 0 deletions src/main/scala/challenge/Day01.scala
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)
41 changes: 41 additions & 0 deletions src/main/scala/challenge/Day02.scala
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
31 changes: 31 additions & 0 deletions src/main/scala/challenge/Day03.scala
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)
42 changes: 42 additions & 0 deletions src/main/scala/challenge/Day04.scala
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
21 changes: 21 additions & 0 deletions src/main/scala/challenge/Day05.scala
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)
17 changes: 17 additions & 0 deletions src/main/scala/challenge/Day06.scala
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
17 changes: 17 additions & 0 deletions src/main/scala/challenge/Day07.scala
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
50 changes: 50 additions & 0 deletions src/main/scala/challenge/Day08.scala
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
29 changes: 29 additions & 0 deletions src/main/scala/challenge/Day09.scala
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
41 changes: 41 additions & 0 deletions src/main/scala/challenge/Day10.scala
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
Loading

0 comments on commit 74c0c19

Please sign in to comment.