diff --git a/.gitignore b/.gitignore index 4bf9145..5ed3eca 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ metals.sbt /.worksheet/ .direnv + +src/main/resources diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..8134e97 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala3 \ No newline at end of file diff --git a/README.md b/README.md index 102c5ca..6a2b369 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -## sbt project compiled with Scala 3 +## Advent of Code 2024 ### Usage -This is a normal sbt project. You can compile code with `sbt compile`, run it with `sbt run`, and `sbt console` will start a Scala 3 REPL. +- Put input in `src/main/resources/days/dayXX.txt` +- Run SBT `sbt` +- In SBT shell, run day: `run XX` -For more information on the sbt-dotty plugin, see the -[scala3-example-project](https://github.com/scala/scala3-example-project/blob/main/README.md). diff --git a/build.sbt b/build.sbt index 08412cc..b2d685f 100644 --- a/build.sbt +++ b/build.sbt @@ -5,8 +5,7 @@ lazy val root = project .settings( name := "AOC2024", version := "0.1.0-SNAPSHOT", - scalaVersion := scala3Version, - + fork := true, libraryDependencies += "org.scalameta" %% "munit" % "1.0.0" % Test ) diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index bd01e94..c48535e 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,5 +1,10 @@ -@main def hello(): Unit = - println("Hello world!") - println(msg) +import days._ -def msg = "I was compiled by Scala 3. :)" +@main def main(dayNumber: Int): Unit = { + dayNumber match { + case 1 => Day1.solve() + case 2 => Day2.solve() + case 3 => Day3.solve() + case _ => println(s"Day $dayNumber is not yet implemented.") + } +} diff --git a/src/main/scala/days/Day.scala b/src/main/scala/days/Day.scala new file mode 100644 index 0000000..37b52b3 --- /dev/null +++ b/src/main/scala/days/Day.scala @@ -0,0 +1,31 @@ +package days + +import scala.io.Source + +trait Day { + val number: Int + + def getInput(): Option[String] = { + val resourcePath = getClass.getResource(s"day${number}.txt") + if (resourcePath != null) { + Some(Source.fromURL(resourcePath).mkString) + } else { + None + } + } + + def solve(): Unit = { + getInput() match { + case Some(input) => { + println(s"Day: $number") + println(s"Part 1: ${part1(input)}") + println(s"Part 2: ${part2(input)}") + } + case None => + println(s"Missing day $number input") + } + } + + def part1(input: String): String + def part2(input: String): String +} diff --git a/src/main/scala/days/Day1.scala b/src/main/scala/days/Day1.scala new file mode 100644 index 0000000..2058a3b --- /dev/null +++ b/src/main/scala/days/Day1.scala @@ -0,0 +1,35 @@ +package days + +object Day1 extends Day { + val number: Int = 1; + + def inputToLists(input: String): (List[Int], List[Int]) = + input.linesIterator + .map(l => { + val line = l.split(" ") + (line(0).toInt, line.last.toInt) + }) + .foldLeft((Nil, Nil): Tuple2[List[Int], List[Int]]) { + case ((accL, accR), (l, r)) => (l :: accL, r :: accR) + } + + def part1(input: String) = { + val (leftList, rightList) = inputToLists(input) + leftList + .sorted() + .zip(rightList.sorted()) + .map((a, b) => (a - b).abs) + .sum + .toString() + } + + def part2(input: String) = { + val (leftList, rightList) = inputToLists(input) + val counts = + rightList.sorted + .groupBy((n: Int) => n) + .mapValues(_.length) + + leftList.map(n => counts.getOrElse(n, 0) * n).sum().toString() + } +} diff --git a/src/main/scala/days/Day2.scala b/src/main/scala/days/Day2.scala new file mode 100644 index 0000000..3817bee --- /dev/null +++ b/src/main/scala/days/Day2.scala @@ -0,0 +1,56 @@ +package days + +object Day2 extends Day { + val number: Int = 2; + + def checkFnP1(input: List[Int], order: Option[Boolean]): Boolean = + input match { + case Nil => true + case _ :: Nil => true + case a :: b :: t => { + val diff = a - b + if (diff < -3 || diff > 3 || diff == 0) { + return false + } + val curOrder = Some(diff > 0) + val newOrder: Option[Boolean] = (order, curOrder) match { + case (None, a) => a + case (a, b) if a == b => a + case _ => return false + } + checkFnP1(b :: t, newOrder) + } + } + + def checkFnP2( + input: List[Int] + ): Boolean = { + // Wanted to try a recursive approach like p1 but I gave up.. :( + if (checkFnP1(input, None)) { + return true + } + (0 until (input.length)).iterator + .map { n => + { + val (before, after) = input.splitAt(n) + val newInput = before ++ after.tail + checkFnP1(newInput, None) + } + } + .dropWhile(_ == false) + .nextOption + .getOrElse(false) + } + + def part1(input: String) = + input.linesIterator + .map(s => checkFnP1(s.split(" ").map(_.toInt).toList, None)) + .count(_ == true) + .toString() + + def part2(input: String) = + input.linesIterator + .map(s => checkFnP2(s.split(" ").map(_.toInt).toList)) + .count(_ == true) + .toString() +} diff --git a/src/main/scala/days/Day3.scala b/src/main/scala/days/Day3.scala new file mode 100644 index 0000000..17341d8 --- /dev/null +++ b/src/main/scala/days/Day3.scala @@ -0,0 +1,32 @@ +package days + +import scala.util.matching.Regex + +object Day3 extends Day { + val number: Int = 3; + + def part1(input: String) = + "mul\\(([0-9]+),([0-9]+)\\)".r + .findAllMatchIn(input) + .map(m => m.group(1).toInt * m.group(2).toInt) + .sum() + .toString() + + def part2(input: String) = + "(do)\\(\\)|(don't)\\(\\)|mul\\(([0-9]+),([0-9]+)\\)".r + .findAllMatchIn(input) + .foldLeft((true, 0)) { + // Activate flag if "do()" matched + case ((_, acc), m) if m.group(1) != null => (true, acc) + // Deactivate flag if "dont() matched" + case ((_, acc), m) if m.group(2) != null => (false, acc) + // Ignore mul() if deactivated + case ((false, acc), _) => (false, acc) + // Add mul() result to the accumulator + case ((true, acc), m) => + (true, acc + m.group(3).toInt * m.group(4).toInt) + } + ._2 + .toString() + +} diff --git a/src/main/scala/days/Day4.scala b/src/main/scala/days/Day4.scala new file mode 100644 index 0000000..e066479 --- /dev/null +++ b/src/main/scala/days/Day4.scala @@ -0,0 +1,9 @@ +package days + +object Day4 extends Day { + val number: Int = 4; + + def part1(input: String) = "pouet" + + def part2(input: String) = "pouet" +}