├── 2015 ├── .gitignore ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ ├── resources │ │ └── day2.txt │ └── scala │ │ ├── Day1.scala │ │ ├── Day10.scala │ │ ├── Day11.scala │ │ ├── Day12.scala │ │ ├── Day13.scala │ │ ├── Day14.scala │ │ ├── Day15.scala │ │ ├── Day16.scala │ │ ├── Day17.scala │ │ ├── Day18.scala │ │ ├── Day19.scala │ │ ├── Day2.scala │ │ ├── Day20.scala │ │ ├── Day21.scala │ │ ├── Day22.scala │ │ ├── Day23.scala │ │ ├── Day24.scala │ │ ├── Day25.scala │ │ ├── Day3.scala │ │ ├── Day4.scala │ │ ├── Day5.scala │ │ ├── Day6.scala │ │ ├── Day7.scala │ │ ├── Day8.scala │ │ └── Day9.scala │ └── test │ └── scala │ ├── Day1Tests.scala │ ├── Day2Tests.scala │ └── Day3Tests.scala ├── 2016 ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ ├── resources │ │ ├── day10.txt │ │ ├── day11-real-2.txt │ │ ├── day11-real.txt │ │ ├── day11-test.txt │ │ ├── day12.txt │ │ ├── day15-test.txt │ │ ├── day15.txt │ │ ├── day20.txt │ │ ├── day21.txt │ │ ├── day3.txt │ │ ├── day4.txt │ │ ├── day6.txt │ │ ├── day7.txt │ │ ├── day8.txt │ │ └── day9.txt │ └── scala │ │ └── com │ │ └── kubukoz │ │ └── adventofcode2016 │ │ ├── Commons.scala │ │ ├── Day1.scala │ │ ├── Day10.scala │ │ ├── Day11.scala │ │ ├── Day12.scala │ │ ├── Day13.scala │ │ ├── Day14.scala │ │ ├── Day15.scala │ │ ├── Day16.scala │ │ ├── Day17.scala │ │ ├── Day18.scala │ │ ├── Day2.scala │ │ ├── Day20.scala │ │ ├── Day21.scala │ │ ├── Day3.scala │ │ ├── Day4.scala │ │ ├── Day5.scala │ │ ├── Day6.scala │ │ ├── Day7.scala │ │ ├── Day8.scala │ │ ├── Day9.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── kubukoz │ └── adventofcode2016 │ ├── Day10Tests.scala │ ├── Day11Tests.scala │ ├── Day12Tests.scala │ ├── Day13Tests.scala │ ├── Day14Tests.scala │ ├── Day15Tests.scala │ ├── Day16Tests.scala │ ├── Day17Tests.scala │ ├── Day1Tests.scala │ ├── Day20Tests.scala │ ├── Day2Tests.scala │ ├── Day3Tests.scala │ ├── Day4Tests.scala │ ├── Day5Tests.scala │ ├── Day6Tests.scala │ ├── Day7Tests.scala │ ├── Day8Tests.scala │ └── Day9Tests.scala ├── 2017 ├── .gitignore ├── .travis.yml ├── README.md ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ ├── resources │ │ ├── day1.txt │ │ ├── day11.txt │ │ ├── day12.txt │ │ ├── day13.txt │ │ ├── day16.txt │ │ ├── day18.txt │ │ ├── day19.txt │ │ ├── day2.txt │ │ ├── day20.txt │ │ ├── day21.txt │ │ ├── day22.txt │ │ ├── day23.txt │ │ ├── day24.txt │ │ ├── day25.txt │ │ ├── day4.txt │ │ ├── day5.txt │ │ ├── day6.txt │ │ ├── day7.txt │ │ ├── day8.txt │ │ └── day9.txt │ └── scala │ │ └── com │ │ └── kubukoz │ │ └── adventofcode2017 │ │ ├── Commons.scala │ │ ├── Day1.scala │ │ ├── Day10.scala │ │ ├── Day11.scala │ │ ├── Day12.scala │ │ ├── Day13.scala │ │ ├── Day14.scala │ │ ├── Day15.scala │ │ ├── Day16.scala │ │ ├── Day17.scala │ │ ├── Day18.scala │ │ ├── Day19.scala │ │ ├── Day2.scala │ │ ├── Day20.scala │ │ ├── Day21.scala │ │ ├── Day22.scala │ │ ├── Day23.scala │ │ ├── Day24.scala │ │ ├── Day25.scala │ │ ├── Day3.scala │ │ ├── Day4.scala │ │ ├── Day5.scala │ │ ├── Day6.scala │ │ ├── Day7.scala │ │ ├── Day8.scala │ │ ├── Day9.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── kubukoz │ └── adventofcode2016 │ ├── Day10Tests.scala │ ├── Day11Tests.scala │ ├── Day13Tests.scala │ ├── Day1Tests.scala │ ├── Day2Tests.scala │ ├── Day3Tests.scala │ ├── Day6Tests.scala │ ├── Day7Tests.scala │ └── Day9Tests.scala ├── 2018 ├── .gitignore ├── .travis.yml ├── README.md ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ ├── resources │ │ ├── day1.txt │ │ ├── day2.txt │ │ ├── day3.txt │ │ ├── day4.txt │ │ ├── day5.txt │ │ ├── day6.txt │ │ └── deriving.conf │ └── scala │ │ └── com │ │ └── kubukoz │ │ └── adventofcode2018 │ │ ├── Commons.scala │ │ ├── Day1.scala │ │ ├── Day2.scala │ │ ├── Day3.scala │ │ ├── Day4.scala │ │ ├── Day5.scala │ │ ├── Day6.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── kubukoz │ └── adventofcode2018 │ ├── Day2Tests.scala │ ├── Day4Tests.scala │ └── Day5Tests.scala ├── 2019 ├── README.md ├── haskell │ ├── .gitignore │ ├── Setup.hs │ ├── app │ │ └── Main.hs │ ├── data │ │ ├── day0.txt │ │ ├── day1.txt │ │ └── day3.txt │ ├── package.yaml │ ├── src │ │ ├── Core.hs │ │ ├── Day0.hs │ │ ├── Day1.hs │ │ └── Lib.hs │ ├── stack.yaml │ ├── stack.yaml.lock │ └── test │ │ └── Spec.hs └── scala │ ├── .gitignore │ ├── .travis.yml │ ├── README.md │ ├── build.sbt │ ├── files │ ├── day2.txt │ ├── day3.txt │ ├── day5.txt │ └── day6.txt │ ├── project │ ├── build.properties │ ├── metals.sbt │ └── plugins.sbt │ └── src │ ├── main │ └── scala │ │ └── com │ │ └── kubukoz │ │ └── aoc │ │ ├── Util.scala │ │ ├── day2 │ │ └── Day2.scala │ │ ├── day3 │ │ └── Day3.scala │ │ ├── day4 │ │ └── Day4.scala │ │ ├── day5 │ │ └── Day5.scala │ │ └── day6 │ │ └── Day6.scala │ └── test │ └── scala │ └── com │ └── kubukoz │ ├── Day2Tests.scala │ └── aoc │ ├── day3 │ └── Day3Tests.scala │ ├── day4 │ └── Day4Tests.scala │ └── day5 │ └── Day5Tests.scala ├── 2020 ├── README.md └── scala │ ├── .gitignore │ ├── build.sbt │ ├── cats │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── kubukoz │ │ └── aoc │ │ ├── Util.scala │ │ ├── day0 │ │ └── Day0.scala │ │ ├── day1 │ │ └── Day1.scala │ │ ├── day10 │ │ └── Day10.scala │ │ ├── day14 │ │ └── Day14.scala │ │ ├── day15 │ │ └── Day15.scala │ │ ├── day16 │ │ └── day16.worksheet.sc │ │ ├── day18 │ │ └── day18.worksheet.sc │ │ ├── day2 │ │ └── fast.worksheet.sc │ │ ├── day3 │ │ └── day3.fast.worksheet.sc │ │ ├── day4 │ │ └── day4.worksheet.sc │ │ ├── day5 │ │ └── day5.worksheet.sc │ │ ├── day6 │ │ └── day6.worksheet.sc │ │ ├── day7 │ │ └── day7.worksheet.sc │ │ ├── day8 │ │ └── day8.worksheet.sc │ │ └── day9 │ │ └── day9.worksheet.sc │ ├── files │ ├── day0.txt │ ├── day1.txt │ ├── day10.txt │ ├── day14.txt │ ├── day16.txt │ ├── day18.txt │ ├── day2.txt │ ├── day3.txt │ ├── day4.txt │ ├── day5.txt │ ├── day6.txt │ ├── day7.txt │ ├── day8.txt │ └── day9.txt │ ├── project │ ├── build.properties │ └── plugins.sbt │ └── zio │ └── src │ └── main │ └── scala │ └── com │ └── kubukoz │ └── aoc │ ├── ZUtil.scala │ ├── ZUtilApp.scala │ ├── day0 │ └── Day0ZIO.scala │ └── package.scala ├── 2021 ├── .gitattributes ├── .gitignore ├── .scalafmt.conf ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ ├── resources │ │ ├── day1.txt │ │ ├── day10-example.txt │ │ ├── day10.txt │ │ ├── day11-example.txt │ │ ├── day11.txt │ │ ├── day12-example.txt │ │ ├── day12.txt │ │ ├── day13-example.txt │ │ ├── day13.txt │ │ ├── day14-example.txt │ │ ├── day14.txt │ │ ├── day15-example.txt │ │ ├── day15.txt │ │ ├── day16.txt │ │ ├── day17-example.txt │ │ ├── day17.txt │ │ ├── day18-example.txt │ │ ├── day18.txt │ │ ├── day19-example.txt │ │ ├── day19.txt │ │ ├── day2.txt │ │ ├── day20-example.txt │ │ ├── day20.txt │ │ ├── day21-example.txt │ │ ├── day21.txt │ │ ├── day22-example.txt │ │ ├── day22.txt │ │ ├── day23-example.txt │ │ ├── day23.txt │ │ ├── day3.txt │ │ ├── day4-example.txt │ │ ├── day4.txt │ │ ├── day5-example.txt │ │ ├── day5.txt │ │ ├── day6-example.txt │ │ ├── day6.txt │ │ ├── day7-example.txt │ │ ├── day7.txt │ │ ├── day8-example.txt │ │ ├── day8.txt │ │ ├── day9-example.txt │ │ └── day9.txt │ └── scala │ │ └── aoc │ │ ├── day1.scala │ │ ├── day10.scala │ │ ├── day11.scala │ │ ├── day12.scala │ │ ├── day13.scala │ │ ├── day14.scala │ │ ├── day15.scala │ │ ├── day16 │ │ ├── Bit.scala │ │ ├── OpType.scala │ │ ├── Packet.scala │ │ ├── ParserApi.scala │ │ └── day16.scala │ │ ├── day17.scala │ │ ├── day18 │ │ ├── BinaryTree.scala │ │ ├── Direction.scala │ │ ├── Parser.scala │ │ ├── day18.scala │ │ └── package.scala │ │ ├── day19 │ │ ├── Day19.scala │ │ ├── Permutation.scala │ │ └── ScannerDescriptor.scala │ │ ├── day2.scala │ │ ├── day20.scala │ │ ├── day21.scala │ │ ├── day22.scala │ │ ├── day23.scala │ │ ├── day3.scala │ │ ├── day4.scala │ │ ├── day5.scala │ │ ├── day6.scala │ │ ├── day7.scala │ │ ├── day8.scala │ │ ├── day9.scala │ │ └── lib.scala │ └── test │ └── scala │ └── aoc │ └── day19 │ └── Day19Tests.scala ├── 2022 ├── .envrc ├── .formatter.exs ├── .gitignore ├── README.md ├── flake.lock ├── flake.nix ├── lib │ └── day1.ex ├── mix.exs ├── scala │ ├── .scalafmt.conf │ ├── day1.scala │ └── macros.scala └── test │ ├── day1_test.exs │ └── test_helper.exs ├── 2024 └── README.md └── .git-crypt ├── .gitattributes └── keys └── default └── 0 └── CB05F72DE9D243FF29D264C1A1DC9B6A8B59D4D6.gpg /.git-crypt/.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not edit this file. To specify the files to encrypt, create your own 2 | # .gitattributes file in the directory where your files are. 3 | * !filter !diff 4 | *.gpg binary 5 | -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/CB05F72DE9D243FF29D264C1A1DC9B6A8B59D4D6.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/.git-crypt/keys/default/0/CB05F72DE9D243FF29D264C1A1DC9B6A8B59D4D6.gpg -------------------------------------------------------------------------------- /2015/.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea/ 2 | **/*.iml 3 | **/target/ -------------------------------------------------------------------------------- /2015/build.sbt: -------------------------------------------------------------------------------- 1 | name := "advent-of-code" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.12.12" 6 | 7 | resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-stream" % "2.4.14", 11 | "org.scalatest" %% "scalatest" % "3.0.1" % "test" 12 | ) 13 | -------------------------------------------------------------------------------- /2015/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.4.4 2 | -------------------------------------------------------------------------------- /2015/project/plugins.sbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2015/project/plugins.sbt -------------------------------------------------------------------------------- /2015/src/main/scala/Day10.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | 3 | object Day10 { 4 | val rounds = 55 5 | val input = "3" 6 | 7 | @tailrec 8 | def getNext(s: String, prefix: String = ""): String = s.length match { 9 | case 0 => prefix 10 | case l => 11 | val char = s.head 12 | val howMany = s.takeWhile(_ == char).length 13 | getNext(s.substring(howMany), prefix + howMany + char) 14 | } 15 | 16 | def main(args: Array[String]) { 17 | 18 | (1 to rounds).foldLeft(input) { (res, i) => 19 | println(s"$i round") 20 | getNext(res) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /2015/src/main/scala/Day11.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | 3 | object Day11 { 4 | def main(args: Array[String]) { 5 | val input = "hxbxwxba" 6 | def substrings(s: String, state: List[String] = Nil): List[String] = s.length match { 7 | case z if z < 2 => state.distinct 8 | case _ => substrings(s.tail, s.substring(0, 2) :: state) 9 | } 10 | 11 | def atLeastTwoAndNotOverlapping(s: String, in: String) = allIndices(s, in).nonEmpty 12 | def allIndices(s: String, in: String, delta: Int = 0): List[Int] = in.indexOf(s) match { 13 | case -1 => Nil 14 | case i => (delta + i) :: allIndices(s, in.substring(i + 1), delta + i + 1) 15 | } 16 | 17 | def atLeastOneStraight(word: String) = word.sliding(3, 1).exists(s => s.head + 1 == s(1) && s(1) + 1 == s(2)) 18 | 19 | val exclusions = "iol" 20 | def isValid(word: String) = { 21 | val r1 = !exclusions.exists(word contains _) 22 | val r2 = substrings(word).count(p => p.head == p.last) >= 2 23 | val r3 = atLeastOneStraight(word) 24 | // substrings(word).foreach{ss => 25 | // println(ss, allIndices(ss, word)) 26 | // } 27 | r1 && r2 && r3 28 | } 29 | 30 | @tailrec 31 | def nextWord(word: String, after: String = ""): String = { 32 | word.length match { 33 | case 0 => after 34 | case _ if word.last == 'z' => nextWord(word.init, "a" + after) 35 | case _ => word.init + (word.last + 1).toChar + after 36 | } 37 | } 38 | 39 | @tailrec 40 | def getValidPassword(old: String): String = nextWord(old) match { 41 | case newP if isValid(newP) => newP 42 | case _ => getValidPassword(nextWord(old)) 43 | } 44 | 45 | val result = getValidPassword(input) 46 | println(s"first new: $result") 47 | 48 | println(s"second new: ${getValidPassword(result)}") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /2015/src/main/scala/Day15.scala: -------------------------------------------------------------------------------- 1 | object Day15 { 2 | val input = 3 | """Frosting: capacity 4, durability -2, flavor 0, texture 0, calories 5 4 | |Candy: capacity 0, durability 5, flavor -1, texture 0, calories 8 5 | |Butterscotch: capacity -1, durability 0, flavor 5, texture 0, calories 6 6 | |Sugar: capacity 0, durability 0, flavor -2, texture 2, calories 1""".stripMargin.split("\n") 7 | 8 | val numPat = "(-?[0-9]+)" 9 | val pattern = s"([A-z]+): capacity $numPat, durability $numPat, flavor $numPat, texture $numPat, calories $numPat".r 10 | 11 | def main(args: Array[String]) { 12 | val ingredients = input.map { case pattern(name, cap, dur, flav, text, cal) => Ingredient(name, cap.toInt, dur.toInt, flav.toInt, text.toInt, cal.toInt) }.toList 13 | val spoons = 100 14 | val combinations = (0 to 100).combinations(ingredients.size).filter(_.sum <= spoons).flatMap(_.permutations).map { seq => 15 | Recipe(seq.indices.map { i => 16 | ingredients(i) -> seq(i) 17 | }.toMap) 18 | }.toList 19 | println("Part 1: " + combinations.map(_.totalScore).max) 20 | println("Part 2: " + combinations.filter(_.calorieSum == 500).map(_.totalScore).max) 21 | } 22 | } 23 | 24 | case class Ingredient(name: String, capacity: Int, duration: Int, flavor: Int, texture: Int, calories: Int) 25 | 26 | case class Recipe(ingredients: Map[Ingredient, Int]) { 27 | val capSum = ingredients.map { case (i, am) => i.capacity * am }.sum 28 | val durSum = ingredients.map { case (i, am) => i.duration * am }.sum 29 | val flavorSum = ingredients.map { case (i, am) => i.flavor * am }.sum 30 | val textureSum = ingredients.map { case (i, am) => i.texture * am }.sum 31 | val calorieSum = ingredients.map { case (i, am) => i.calories * am }.sum 32 | 33 | def totalScore = List(capSum, durSum, flavorSum, textureSum).map(_ max 0).product 34 | } -------------------------------------------------------------------------------- /2015/src/main/scala/Day17.scala: -------------------------------------------------------------------------------- 1 | import scala.language.postfixOps 2 | 3 | object Day17 { 4 | def main(args: Array[String]) { 5 | val input = 6 | """33 7 | |14 8 | |18 9 | |20 10 | |45 11 | |35 12 | |16 13 | |35 14 | |1 15 | |13 16 | |18 17 | |13 18 | |50 19 | |44 20 | |48 21 | |6 22 | |24 23 | |41 24 | |30 25 | |42""".stripMargin.split("\n").map(_.toInt) 26 | val target = 150 27 | 28 | val allCombsBecauseImLazy = (2 to input.length).flatMap(input.indices.combinations(_).map(_.map(input.apply))) 29 | val validCombs = allCombsBecauseImLazy.filter(_.sum == target) 30 | println("Part 1: " + validCombs.size) 31 | println("Part 2: " + validCombs.count(_.size == validCombs.map(_.size).min)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /2015/src/main/scala/Day2.scala: -------------------------------------------------------------------------------- 1 | object Day2 { 2 | def wrappingPaper(input: Array[String]) = 3 | input.map(toIntArray) 4 | .map(l => l.indices.combinations(2).map(_.map(l).product).toList) 5 | .map(areas => 2 * areas.sum + areas.min).sum 6 | 7 | def ribbon(input: Array[String]) = 8 | input.map(toIntArray) 9 | .map(list => list.sorted.take(2).sum * 2 + list.product).sum 10 | 11 | private def toIntArray(s: String) = s.split("x").map(_.toInt) 12 | 13 | 14 | def main(args: Array[String]) { 15 | val source = io.Source.fromURL(getClass.getResource("/day2.txt")) 16 | val input = source.getLines().toArray 17 | 18 | source.close() 19 | 20 | println(wrappingPaper(input)) 21 | println(ribbon(input)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /2015/src/main/scala/Day20.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.mutable 2 | 3 | object Day20 { 4 | def main(args: Array[String]) { 5 | val input = 36000000 6 | 7 | val uses = mutable.Map.empty[Int, Int] 8 | 9 | val houses = Stream.iterate(1)(_+1) 10 | 11 | houses.find { i => 12 | val sum = (1 to i).filter { elf => 13 | if (uses.getOrElseUpdate(elf, 0) < 50) { 14 | uses(elf) += 1 15 | i % elf == 0 16 | } else false 17 | }.map(_ * 11).sum 18 | sum >= input 19 | } foreach println 20 | } 21 | } -------------------------------------------------------------------------------- /2015/src/main/scala/Day25.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | 3 | object Day25 { 4 | @tailrec 5 | def point(x: Int, y: Int, mem: Int = 0): Int = { 6 | require(x >= 1 && y >= 1) 7 | x == 1 match { 8 | case true if y == 1 => mem + 1 9 | case true => point(y - 1, 1, mem+1) 10 | case _ => point(x - 1, y + 1, mem+1) 11 | } 12 | } 13 | 14 | @tailrec 15 | def num(i: Int, temp: Long = 20151125): Long = i match{ 16 | case 1 => temp 17 | case _ => num(i-1, compute(temp)) 18 | } 19 | 20 | def compute(i: Long): Long = (i * 252533) % 33554393 21 | 22 | def main(args: Array[String]) { 23 | //part 1 24 | val (x, y) = (3029, 2947) 25 | val index = point(x, y) 26 | println(index) 27 | println(num(index)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /2015/src/main/scala/Day4.scala: -------------------------------------------------------------------------------- 1 | import java.security.MessageDigest 2 | 3 | object Day4 { 4 | def md5(s: String) = 5 | MessageDigest.getInstance("MD5").digest(s.getBytes).map("%02X".format(_)).mkString 6 | 7 | def matches(s: String, zeroes: Int) = s.startsWith("0" * zeroes) 8 | 9 | def main(args: Array[String]) { 10 | val input = "iwrupvqb" 11 | val fiveZeroes = Stream.iterate(0)(_ + 1).find(key => matches(md5(input + key), 5)) 12 | println(fiveZeroes) 13 | println(Stream.iterate(fiveZeroes.getOrElse(0))(_ + 1).find(key => matches(md5(input + key), 6))) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2015/src/main/scala/Day9.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | object Day9 { 4 | def main(args: Array[String]) { 5 | val input = 6 | """AlphaCentauri to Snowdin = 66 7 | |AlphaCentauri to Tambi = 28 8 | |AlphaCentauri to Faerun = 60 9 | |AlphaCentauri to Norrath = 34 10 | |AlphaCentauri to Straylight = 34 11 | |AlphaCentauri to Tristram = 3 12 | |AlphaCentauri to Arbre = 108 13 | |Snowdin to Tambi = 22 14 | |Snowdin to Faerun = 12 15 | |Snowdin to Norrath = 91 16 | |Snowdin to Straylight = 121 17 | |Snowdin to Tristram = 111 18 | |Snowdin to Arbre = 71 19 | |Tambi to Faerun = 39 20 | |Tambi to Norrath = 113 21 | |Tambi to Straylight = 130 22 | |Tambi to Tristram = 35 23 | |Tambi to Arbre = 40 24 | |Faerun to Norrath = 63 25 | |Faerun to Straylight = 21 26 | |Faerun to Tristram = 57 27 | |Faerun to Arbre = 83 28 | |Norrath to Straylight = 9 29 | |Norrath to Tristram = 50 30 | |Norrath to Arbre = 60 31 | |Straylight to Tristram = 27 32 | |Straylight to Arbre = 81 33 | |Tristram to Arbre = 90""".stripMargin.split("\n") 34 | 35 | val pattern = "(.+) to (.+) = (.+)".r 36 | 37 | val passages = input.map { 38 | case pattern(from, to, dist) => Route(from, to, dist.toInt) 39 | }.toList 40 | 41 | def passageLength(from: String, to: String) = { 42 | val s = passages.collect { 43 | case Route(f, t, length) if f == from && t == to => length 44 | case Route(t, f, length) if f == from && t == to => length 45 | } 46 | if (s.isEmpty) println(s"NO KURWA. $from to $to") 47 | s.head 48 | } 49 | val cities = passages.flatMap(s => List(s.from, s.to)).distinct 50 | 51 | val allRoutes: List[List[String]] = cities.permutations.toList 52 | 53 | val lengths: List[Int] = for { 54 | route <- allRoutes 55 | } yield (for { 56 | cityIndex <- route.indices 57 | city = route(cityIndex) 58 | } yield 59 | if (cityIndex == 0) 0 60 | else passageLength(route(cityIndex - 1), city)).sum 61 | 62 | 63 | println(s"min: ${lengths.min}, max: ${lengths.max}") 64 | } 65 | } 66 | 67 | case class Route(from: String, to: String, dist: Int) -------------------------------------------------------------------------------- /2015/src/test/scala/Day1Tests.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{FlatSpec, Matchers} 2 | import Day1._ 3 | 4 | class Day1Tests extends FlatSpec with Matchers { 5 | "Part 1" should "give valid results" in { 6 | calculateLevel("(())") shouldBe 0 7 | calculateLevel("()()") shouldBe 0 8 | calculateLevel("(((") shouldBe 3 9 | calculateLevel("(()(()(") shouldBe 3 10 | calculateLevel("))(((((") shouldBe 3 11 | calculateLevel("())") shouldBe -1 12 | calculateLevel("))(") shouldBe -1 13 | calculateLevel(")))") shouldBe -3 14 | calculateLevel(")())())") shouldBe -3 15 | calculateLevel(input) shouldBe 232 16 | } 17 | 18 | "Part 2" should "give valid results too" in { 19 | findFirstBelow1(")") shouldBe 1 20 | findFirstBelow1("()())") shouldBe 5 21 | findFirstBelow1(input) shouldBe 1783 22 | } 23 | } -------------------------------------------------------------------------------- /2015/src/test/scala/Day2Tests.scala: -------------------------------------------------------------------------------- 1 | import Day2._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Day2Tests extends FlatSpec with Matchers { 5 | "Wrapping paper" should "be calculated well" in { 6 | wrappingPaper(Array("2x3x4")) shouldBe 58 7 | } 8 | 9 | "Ribbon" should "be calculated well too" in { 10 | ribbon(Array("2x3x4")) shouldBe 34 11 | } 12 | } -------------------------------------------------------------------------------- /2015/src/test/scala/Day3Tests.scala: -------------------------------------------------------------------------------- 1 | import Day3._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | class Day3Tests extends FlatSpec with Matchers { 5 | "Part 1" should "be valid" in { 6 | visitedByOne("^v^v^v^v^v") shouldBe 2 7 | } 8 | "Part 2" should "be valid" in { 9 | visitedByTwo("^v^v^v^v^v") shouldBe 11 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2016/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | **/target/ 3 | **/lib/** 4 | -------------------------------------------------------------------------------- /2016/.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.0 4 | jdk: 5 | - oraclejdk8 6 | -------------------------------------------------------------------------------- /2016/README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2016 2 | 3 | [![Build Status](https://travis-ci.org/kubukoz/advent-of-code-2016.svg?branch=master)](https://travis-ci.org/kubukoz/advent-of-code-2016) 4 | 5 | My solutions for the Advent of Code tasks in December, 2016. Probably all will be built in Scala. 6 | -------------------------------------------------------------------------------- /2016/build.sbt: -------------------------------------------------------------------------------- 1 | name := "advent-of-code-2016" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.12.4" 6 | 7 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-stream" % "2.4.14", 11 | "org.typelevel" %% "cats" % "0.8.1", 12 | "org.scalatest" %% "scalatest" % "3.0.1" % "test" 13 | ) 14 | -------------------------------------------------------------------------------- /2016/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /2016/project/plugins.sbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2016/project/plugins.sbt -------------------------------------------------------------------------------- /2016/src/main/resources/day11-real-2.txt: -------------------------------------------------------------------------------- 1 | The first floor contains a polonium generator, a thulium generator, a thulium-compatible microchip, a promethium generator, a ruthenium generator, a ruthenium-compatible microchip, a cobalt generator, a cobalt-compatible microchip, an elerium generator, an elerium-compatible microchip, a dilithium generator and a dilithium-compatible microchip. 2 | The second floor contains a polonium-compatible microchip and a promethium-compatible microchip. 3 | The third floor contains nothing relevant. 4 | The fourth floor contains nothing relevant. -------------------------------------------------------------------------------- /2016/src/main/resources/day11-real.txt: -------------------------------------------------------------------------------- 1 | The first floor contains a polonium generator, a thulium generator, a thulium-compatible microchip, a promethium generator, a ruthenium generator, a ruthenium-compatible microchip, a cobalt generator, and a cobalt-compatible microchip. 2 | The second floor contains a polonium-compatible microchip and a promethium-compatible microchip. 3 | The third floor contains nothing relevant. 4 | The fourth floor contains nothing relevant. -------------------------------------------------------------------------------- /2016/src/main/resources/day11-test.txt: -------------------------------------------------------------------------------- 1 | The first floor contains a cobalt-compatible microchip and a elerium-compatible microchip. 2 | The second floor contains a cobalt generator. 3 | The third floor contains a elerium generator. 4 | The fourth floor contains nothing relevant. -------------------------------------------------------------------------------- /2016/src/main/resources/day12.txt: -------------------------------------------------------------------------------- 1 | cpy 1 a 2 | cpy 1 b 3 | cpy 26 d 4 | jnz c 2 5 | jnz 1 5 6 | cpy 7 c 7 | inc d 8 | dec c 9 | jnz c -2 10 | cpy a c 11 | inc a 12 | dec b 13 | jnz b -2 14 | cpy c b 15 | dec d 16 | jnz d -6 17 | cpy 16 c 18 | cpy 12 d 19 | inc a 20 | dec d 21 | jnz d -2 22 | dec c 23 | jnz c -5 -------------------------------------------------------------------------------- /2016/src/main/resources/day15-test.txt: -------------------------------------------------------------------------------- 1 | Disc #1 has 5 positions; at time=0, it is at position 4. 2 | Disc #2 has 2 positions; at time=0, it is at position 1. -------------------------------------------------------------------------------- /2016/src/main/resources/day15.txt: -------------------------------------------------------------------------------- 1 | Disc #1 has 17 positions; at time=0, it is at position 5. 2 | Disc #2 has 19 positions; at time=0, it is at position 8. 3 | Disc #3 has 7 positions; at time=0, it is at position 1. 4 | Disc #4 has 13 positions; at time=0, it is at position 7. 5 | Disc #5 has 5 positions; at time=0, it is at position 1. 6 | Disc #6 has 3 positions; at time=0, it is at position 0. -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Commons.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import java.security.MessageDigest 4 | 5 | object Commons { 6 | private val md = MessageDigest.getInstance("MD5") 7 | 8 | def md5(s: String): String = md5With(s, md) 9 | 10 | def md5Separated(s: String): String = md5With(s, MessageDigest.getInstance("MD5")) 11 | 12 | private def md5With(s: String, md: MessageDigest) = 13 | "%1$032X".format(BigInt(1, md.digest(s.getBytes))) 14 | } 15 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day12.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | /** 4 | * Created by kubukoz on 25/12/2016. 5 | */ 6 | object Day12 { 7 | type State = Map[Char, Int] 8 | 9 | def transformInput(instructions: List[String], startMap: State): State = { 10 | 11 | val cpyNumber = """cpy (\d+) (\w)""".r 12 | val cpyRegister = """cpy (\w) (\w)""".r 13 | val incRegister = """inc (\w)""".r 14 | val decRegister = """dec (\w)""".r 15 | val jnz = """jnz (.+) (-?\d+)""".r 16 | 17 | val instructionCount = instructions.length 18 | 19 | def applyInstruction(i: Int, state: State): State = { 20 | if (i >= instructionCount) state 21 | else instructions(i) match { 22 | case cpyNumber(num, reg) => 23 | applyInstruction(i + 1, state.updated(reg.head, num.toInt)) 24 | 25 | case cpyRegister(from, to) => 26 | applyInstruction(i + 1, state.updated(to.head, state(from.head))) 27 | 28 | case incRegister(reg) => 29 | applyInstruction(i + 1, state.updated(reg.head, state(reg.head) + 1)) 30 | 31 | case decRegister(reg) => 32 | applyInstruction(i + 1, state.updated(reg.head, state(reg.head) - 1)) 33 | 34 | case jnz(sth, _) if sth == "0" || state.get(sth.head).contains(0) => 35 | applyInstruction(i + 1, state) 36 | 37 | case jnz(_, steps) => 38 | applyInstruction(i + steps.toInt, state) 39 | } 40 | } 41 | 42 | applyInstruction(0, startMap) 43 | } 44 | 45 | def main(args: Array[String]): Unit = { 46 | val input = fileLines("/day12.txt") 47 | val zeroMap = ('a' to 'd').map(_ -> 0).toMap 48 | 49 | println(transformInput(input, zeroMap)('a')) 50 | println(transformInput(input, zeroMap + ('c' -> 1))('a')) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day13.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import scala.annotation.tailrec 4 | import scala.language.postfixOps 5 | 6 | object Day13 { 7 | type State = (Int, Int) 8 | 9 | def findShortestRouteTo(x: Int, y: Int, salt: Int): Int = { 10 | def isOpen(x: Int, y: Int): Boolean = { 11 | (x * x + 3 * x + 2 * x * y + y + y * y + salt).toBinaryString.count(_ == '1') % 2 == 0 12 | } 13 | 14 | def nextPossibilities: State => Set[State] = { 15 | case (oldX, oldY) => 16 | for { 17 | (newX, newY) <- Set((oldX - 1, oldY), (oldX + 1, oldY), (oldX, oldY - 1), (oldX, oldY + 1)) 18 | if newX >= 0 19 | if newY >= 0 20 | if isOpen(newX, newY) 21 | } yield (newX, newY) 22 | } 23 | 24 | val isComplete = (x, y) == _ 25 | 26 | @tailrec 27 | def goRec(possibilities: Set[State], alreadySeen: Set[State], depth: Int): Int = { 28 | if (possibilities.exists(isComplete)) depth 29 | else goRec(possibilities.flatMap(nextPossibilities) -- alreadySeen, alreadySeen ++ possibilities, depth + 1) 30 | } 31 | 32 | goRec(nextPossibilities((1, 1)), Set.empty, 1) 33 | } 34 | 35 | def main(args: Array[String]): Unit = { 36 | println(findShortestRouteTo(x = 31, y = 39, salt = 1352)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day14.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Commons._ 4 | 5 | object Day14 { 6 | def md5Times(s: String, times: Int): String = (0 until times).foldLeft(s) { case (a, _) => md5(a).toLowerCase } 7 | 8 | def keys(salt: String, times: Int): Stream[Int] = { 9 | val hashes = Stream.iterate(0)(_ + 1).map(i => md5Times(salt + i, times)) 10 | 11 | hashes.zipWithIndex.collect { 12 | case (firstTripleInHash(charAsFive), key) 13 | if hashes.slice(key + 1, key + 1001) 14 | .exists(_.contains(charAsFive)) => key 15 | } 16 | } 17 | 18 | object firstTripleInHash { 19 | def unapply(hash: String): Option[String] = { 20 | hash.sliding(3).find(_.distinct.length == 1).map(_.head.toString * 5) 21 | } 22 | } 23 | 24 | def hasSame(count: Int)(hash: String): Boolean = hash.sliding(count).exists(_.distinct.length == 1) 25 | 26 | def main(args: Array[String]): Unit = { 27 | println(keys("ahsbgdzn", times = 1)(63)) 28 | println(keys("ahsbgdzn", times = 2017)(63)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day15.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Day15 { 6 | private val discPat = """Disc #. has (\d+) positions; at time=0, it is at position (\d+)\.""".r 7 | 8 | def findFirstHole(input: List[String]): Int = { 9 | val discs = input.map { 10 | case discPat(positions, startPosition) => Disc(positions.toInt, startPosition.toInt) 11 | } 12 | 13 | @tailrec 14 | def go(state: State, i: Int): Int = 15 | if (state.canPassThrough) i - 1 16 | else go(state.next, i + 1) 17 | 18 | go(State(discs), 0) 19 | } 20 | 21 | case class State(discs: List[Disc]) extends AnyVal { 22 | def canPassThrough: Boolean = discs match { 23 | case Disc(_, 0) :: t => State(t).next.canPassThrough 24 | case Nil => true 25 | case _ => false 26 | } 27 | 28 | def next: State = copy(discs.map(_.next)) 29 | } 30 | 31 | case class Disc(positions: Int, currentPosition: Int) { 32 | def next: Disc = copy(currentPosition = (currentPosition + 1) % positions) 33 | } 34 | 35 | def main(args: Array[String]): Unit = { 36 | val input = fileLines("/day15.txt") 37 | 38 | println(findFirstHole(input)) 39 | println(findFirstHole(input :+ "Disc #7 has 11 positions; at time=0, it is at position 0.")) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day16.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Day16 { 6 | def generate(a: String): String = { 7 | a + 0 + a.reverse.map { case '1' => '0' case '0' => '1' } 8 | } 9 | 10 | @tailrec 11 | def fill(s: String, toLength: Int): String = 12 | if (s.length >= toLength) s.take(toLength) 13 | else fill(generate(s), toLength) 14 | 15 | @tailrec 16 | def checksum(s: String): String = { 17 | val newString = s.sliding(2, 2).map { 18 | case "00" | "11" => '1' 19 | case _ => '0' 20 | }.mkString 21 | 22 | if (newString.length % 2 == 0) checksum(newString) else newString 23 | } 24 | 25 | def transform(input: String, diskSize: Int) = checksum(fill(input, diskSize)) 26 | 27 | def main(args: Array[String]): Unit = { 28 | 29 | val input = "10010000000110000" 30 | 31 | println(transform(input, 272)) 32 | println(transform(input, 35651584)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day17.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Commons.{md5Separated => md5} 4 | 5 | import scala.annotation.tailrec 6 | import scala.language.postfixOps 7 | 8 | object Day17 { 9 | private val mapBounds = (0 to 3).toSet 10 | 11 | case class Position(pos: (Int, Int)) extends AnyVal { 12 | def x: Int = pos._1 13 | 14 | def y: Int = pos._2 15 | 16 | def fitsBounds: Boolean = { 17 | Set(x, y).forall(mapBounds contains) 18 | } 19 | } 20 | 21 | case class State(position: Position, history: String) { 22 | def isComplete: Boolean = position == Position(3, 3) 23 | 24 | val moves = List((0, -1), (0, 1), (-1, 0), (1, 0)) 25 | 26 | def maybeGo(moveIndex: Int, charAtHash: Char): Option[State] = { 27 | val (dx, dy) = moves(moveIndex) 28 | val newPosition = Position(position.x + dx, position.y + dy) 29 | 30 | //noinspection SimplifyBooleanMatch because it ignores guards 31 | newPosition.fitsBounds match { 32 | case true if 'B' to 'F' contains charAtHash => 33 | Some(State(newPosition, history + "UDLR".apply(moveIndex))) 34 | case _ => None 35 | } 36 | } 37 | } 38 | 39 | private def nextPossibilities(input: String, state: State): Set[State] = { 40 | val hash = md5(input + state.history) 41 | for { 42 | move <- mapBounds 43 | newState <- state.maybeGo(move, hash.charAt(move)) 44 | } yield newState 45 | } 46 | 47 | def findShortestPath(input: String): String = { 48 | @tailrec 49 | def goRec(possibilities: Set[State], depth: Int): String = { 50 | if (possibilities.exists(_.isComplete)) possibilities.filter(_.isComplete).head.history 51 | else goRec(possibilities.flatMap(nextPossibilities(input, _)), depth + 1) 52 | } 53 | 54 | goRec(Set(State(Position(0, 0), "")), 0) 55 | } 56 | 57 | def findLongestPath(input: String): Int = { 58 | def goRec(possibilities: Set[State], depth: Int): Int = { 59 | val (complete, incomplete) = possibilities.partition(_.isComplete) 60 | 61 | val completeMax = if (complete.nonEmpty) depth else 0 62 | val incompleteMax = if (incomplete.isEmpty) 0 else goRec(incomplete.flatMap(nextPossibilities(input, _)), depth + 1) 63 | 64 | completeMax max incompleteMax 65 | } 66 | 67 | goRec(Set(State(Position(0, 0), "")), 0) 68 | } 69 | 70 | def main(args: Array[String]): Unit = { 71 | val input = "mmsxrhfx" 72 | 73 | println(findShortestPath(input)) 74 | println(findLongestPath(input)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day18.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import scala.language.postfixOps 4 | 5 | object Day18 { 6 | 7 | sealed trait TrapOrSafe extends Product with Serializable 8 | 9 | case object Trap extends TrapOrSafe 10 | 11 | case object Safe extends TrapOrSafe 12 | 13 | def generateTrapOrSafe: List[TrapOrSafe] => TrapOrSafe = { 14 | case List(Trap, Trap, Safe) => Trap 15 | case List(Safe, Trap, Trap) => Trap 16 | case List(Trap, Safe, Safe) => Trap 17 | case List(Safe, Safe, Trap) => Trap 18 | case _ => Safe 19 | } 20 | 21 | def main(args: Array[String]): Unit = { 22 | val input = "^.....^.^^^^^.^..^^.^.......^^..^^^..^^^^..^.^^.^.^....^^...^^.^^.^...^^.^^^^..^^.....^.^...^.^.^^.^" 23 | 24 | val parse: Char => TrapOrSafe = { 25 | case '^' => Trap 26 | case _ => Safe 27 | } 28 | 29 | val firstFloor = input.map(parse) 30 | 31 | def countSafeInRows(rowCount: Int) = { 32 | val (_, safes) = (1 until rowCount).foldLeft((firstFloor, firstFloor.count(_ == Safe))) { 33 | case ((previousFloor, sumSoFar), _) => 34 | val newFloor = previousFloor.indices.map { i => 35 | val dependencies = (-1 to 1).toList.map(i +) 36 | .map(previousFloor.lift(_).getOrElse(Safe)) 37 | 38 | generateTrapOrSafe(dependencies) 39 | } 40 | 41 | (newFloor, sumSoFar + newFloor.count(_ == Safe)) 42 | } 43 | 44 | safes 45 | } 46 | 47 | println(countSafeInRows(40)) 48 | println(countSafeInRows(400000)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day20.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import scala.annotation.tailrec 4 | import scala.collection.immutable.NumericRange 5 | 6 | object Day20 { 7 | def parse(input: List[String]): List[NumericRange[Long]] = { 8 | val pat = """(\d+)-(\d+)""".r 9 | input.map { 10 | case pat(from, to) => from.toLong to to.toLong by 1 11 | } 12 | } 13 | 14 | def findAllOutside(ranges: List[NumericRange[Long]], 15 | max: Long): List[Long] = { 16 | /** 17 | * Since my input only generated 101 valid numbers, 18 | * storing them in a list is still a viable solution, hence `mem` 19 | * */ 20 | @tailrec 21 | def go(i: Long, mem: List[Long]): List[Long] = { 22 | if (i > max) mem.reverse 23 | else ranges.find(_.contains(i)) match { 24 | case None => go(i + 1, i :: mem) 25 | case Some(range) => 26 | //most important piece - skip whole range 27 | go(range.end + 1, mem) 28 | } 29 | } 30 | 31 | go(0, Nil) 32 | } 33 | 34 | def main(args: Array[String]): Unit = { 35 | val input = fileLines("/day20.txt") 36 | val parsed: List[NumericRange[Long]] = parse(input) 37 | 38 | val results = findAllOutside(parsed, max = 4294967295L) 39 | println("Head: " + results.head) 40 | println("Length: " + results.size) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day3.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | object Day3 { 4 | private def toMatrix(s: List[String]) = s.map(_.split("""\s+""").filterNot(_.trim.isEmpty).map(_.toInt)) 5 | 6 | private def isTriangle(a: Int, b: Int, c: Int) = a + b > c && a + c > b && b + c > a 7 | 8 | def countTriangles(s: List[String]): Int = toMatrix(s).count { 9 | case Array(a, b, c) => isTriangle(a, b, c) 10 | } 11 | 12 | def countTrianglesVertical(s: List[String]): Int = { 13 | toMatrix(s).grouped(3).map { 14 | _.transpose.count { 15 | case Seq(a, b, c) => isTriangle(a, b, c) 16 | } 17 | }.sum 18 | } 19 | 20 | def main(args: Array[String]): Unit = { 21 | val input = fileLines("/day3.txt") 22 | 23 | println(countTriangles(input)) 24 | println(countTrianglesVertical(input)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day4.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | case class Room(letters: String, sectorId: Int, checksum: String) { 4 | def isReal: Boolean = { 5 | val lettersByCount = letters.filterNot(_ == '-').groupBy(identity).mapValues(_.length) 6 | 7 | implicit val ord: Ordering[(Char, Int)] = Ordering.by { case (letter, count) => (-count, letter) } 8 | 9 | val calculatedChecksum = lettersByCount.toList.sorted(ord).map(_._1).take(checksum.length).mkString 10 | checksum == calculatedChecksum 11 | } 12 | 13 | def rotatedLetters: Room = { 14 | val chars = 'a' to 'z' 15 | val newLetters = letters.map { 16 | case '-' => ' ' 17 | case ch => chars((chars.indexOf(ch) + sectorId) % chars.size) 18 | } 19 | 20 | copy(letters = newLetters) 21 | } 22 | } 23 | 24 | object Room { 25 | private val roomPattern = """(.+)\-(\d+)\[(.{5})\]""".r 26 | 27 | val fromString: String => Room = { 28 | case roomPattern(letters, sectorId, checksum) => Room(letters, sectorId.toInt, checksum) 29 | } 30 | } 31 | 32 | object Day4 { 33 | def main(args: Array[String]): Unit = { 34 | val input = fileLines("/day4.txt") 35 | 36 | val realRooms = input.map(Room.fromString).filter(_.isReal) 37 | 38 | println("part 1") 39 | println(realRooms.map(_.sectorId).sum) 40 | println("part 2") 41 | realRooms.map(_.rotatedLetters).find(_.letters == "northpole object storage").map(_.sectorId) foreach println 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day5.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | import com.kubukoz.adventofcode2016.Commons._ 3 | 4 | object Day5 { 5 | private val rounds = 8 6 | 7 | private def findPassword(s: String, validateMd5: String => Boolean, fun: (Int, String) => (Int, Char)): String = { 8 | @annotation.tailrec 9 | def go(mem: Map[Int, Char], lastNum: Int): Map[Int, Char] = { 10 | @annotation.tailrec 11 | def findNextNumber(current: Int): (Int, String) = { 12 | val result = md5(s + current) 13 | if (result.startsWith("00000") && validateMd5(result)) (current, result) 14 | else findNextNumber(current + 1) 15 | } 16 | 17 | val (nextNum, nextNumMD5) = findNextNumber(lastNum) 18 | val (index, char) = fun(mem.size, nextNumMD5) 19 | 20 | val newMem = if (mem.isDefinedAt(index)) mem else mem + (index -> char) 21 | 22 | if (newMem.size == rounds) newMem 23 | else go(newMem, nextNum + 1) 24 | } 25 | 26 | val theMap = go(Map.empty, 0) 27 | (0 until rounds).map(theMap).mkString 28 | } 29 | 30 | def findEasyPassword(s: String): String = findPassword(s, _ => true, (i, s) => (i, s(5))) 31 | 32 | def findHardPassword(s: String): String = 33 | findPassword(s, _.charAt(5).asDigit < rounds, (_, s) => (s(5).asDigit, s(6))) 34 | 35 | def main(args: Array[String]): Unit = { 36 | val input = "ffykfhsq" 37 | println(findEasyPassword(input)) 38 | println(findHardPassword(input)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day6.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | object Day6 { 4 | private def findPasswordByFrequency(input: List[String], selectFun: List[(Char, Int)] => (Char, Int)): String = { 5 | val charsByColumn = input.map { 6 | _.map(List(_)) 7 | }.reduce { (a, b) => 8 | (a zip b).map { case (chars, chars2) => chars ::: chars2 } 9 | } 10 | 11 | val charsWithCounts = charsByColumn.map { list => list.map(char => (char, list.count(_ == char))) } 12 | 13 | charsWithCounts.map(chList => selectFun(chList)._1).mkString 14 | } 15 | 16 | def findPasswordByMostPopular(input: List[String]): String = findPasswordByFrequency(input, _.maxBy(_._2)) 17 | 18 | def findPasswordByLeastPopular(input: List[String]): String = findPasswordByFrequency(input, _.minBy(_._2)) 19 | 20 | def main(args: Array[String]): Unit = { 21 | val input = fileLines("/day6.txt") 22 | 23 | println(findPasswordByMostPopular(input)) 24 | println(findPasswordByLeastPopular(input)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day7.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | object Day7 { 4 | val input: List[String] = fileLines("/day7.txt") 5 | 6 | private val bracketPattern = """\[(\w+)\]""".r 7 | 8 | def supportsTLS(s: String): Boolean = { 9 | hasAbba(s) && !bracketPattern.findAllMatchIn(s).map(_.group(1)).exists(hasAbba) 10 | } 11 | 12 | private def hasAbba(s: String): Boolean = s.sliding(4).exists(isAbbaOrAba) 13 | 14 | def supportsSSL(s: String): Boolean = { 15 | val brackets = bracketPattern.findAllMatchIn(s).toList 16 | 17 | s.split(bracketPattern.regex).flatMap(abas).exists { aba => 18 | brackets.exists { m => hasBab(aba, m.group(1)) } 19 | } 20 | } 21 | 22 | private def abas(s: String): Seq[String] = s.sliding(3).filter(isAbbaOrAba).toList 23 | 24 | private def hasBab(aba: String, s: String): Boolean = s.contains(aba(1) + aba.take(2)) 25 | 26 | private def isAbbaOrAba(s: String) = s(0) != s(1) && s.reverse == s 27 | 28 | def main(args: Array[String]): Unit = { 29 | println(input.count(supportsTLS)) 30 | println(input.count(supportsSSL)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day8.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | object Day8 { 4 | type Board = List[List[Boolean]] 5 | val hash = '#' 6 | val dot = '.' 7 | 8 | private val startText: String = List.fill(6, 50)(dot).map(_.mkString).mkString("\n") 9 | 10 | private val rectPattern = """rect (\d+)x(\d+)""".r 11 | private val rotateColPattern = """rotate column x=(\d+) by (\d+)""".r 12 | private val rotateRowPattern = """rotate row y=(\d+) by (\d+)""".r 13 | 14 | def transformBoard(start: String, input: List[String]): String = { 15 | def switchRect(board: Board, a: Int, b: Int) = { 16 | board.take(b).map { line => 17 | line.take(a).map(!_) ::: line.drop(a) 18 | } ::: board.drop(b) 19 | } 20 | 21 | def rotateRow(board: Board, y: Int, by: Int) = { 22 | val row = board(y) 23 | 24 | val width = row.length 25 | val (rowLeft, rowRight) = row.splitAt(width - by) 26 | board.take(y) ::: (rowRight ::: rowLeft) :: board.drop(y + 1) 27 | } 28 | 29 | val startBoard = start.split("\n").map(_.map(_ == hash).toList).toList 30 | 31 | val result: Board = input.foldLeft(startBoard) { 32 | case (board, rectPattern(a, b)) => switchRect(board, a.toInt, b.toInt) 33 | case (board, rotateRowPattern(y, by)) => rotateRow(board, y.toInt, by.toInt) 34 | case (board, rotateColPattern(x, by)) => rotateRow(board.transpose, x.toInt, by.toInt).transpose 35 | } 36 | 37 | result.map { 38 | _.map { 39 | case true => hash 40 | case _ => dot 41 | }.mkString 42 | }.mkString("\n") 43 | } 44 | 45 | def main(args: Array[String]): Unit = { 46 | val board = transformBoard(startText, fileLines("/day8.txt")) 47 | println(board) 48 | println(board.count(_ == hash)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/Day9.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | case class Result(start: Long, end: Long, howMany: Int, multiplier: Int) 4 | 5 | object Day9 { 6 | private val markerPat = """\((\d+)x(\d+)\)""".r 7 | 8 | private def findPatternInString(source: String): Option[Result] = markerPat.findFirstMatchIn(source).map { matched => 9 | Result(matched.start, matched.end, matched.group(1).toInt, matched.group(2).toInt) 10 | } 11 | 12 | private def decompressInternal(str: String, transformNested: String => Long): Long = findPatternInString(str).map { 13 | case Result(start, end, howMany, multiplier) => 14 | val (toDecompress, after) = str.substring(end.toInt).splitAt(howMany) 15 | 16 | val decompressed = transformNested(toDecompress) * multiplier 17 | 18 | start + decompressed + decompressInternal(after, transformNested) 19 | }.getOrElse(str.length) 20 | 21 | val decompress: String => Long = decompressInternal(_, _.length) 22 | val decompressRec: String => Long = decompressInternal(_, decompressRec) 23 | 24 | def main(args: Array[String]): Unit = { 25 | val input = fileLines("/day9.txt") 26 | println(decompress(input.mkString)) 27 | println(decompressRec(input.mkString)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /2016/src/main/scala/com/kubukoz/adventofcode2016/package.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz 2 | 3 | package object adventofcode2016 { 4 | def fileLines(fileName: String): List[String] = { 5 | val source = io.Source.fromURL(getClass.getResource(fileName)) 6 | val result = source.getLines().toList 7 | source.close() 8 | result 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day10Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import Day10._ 5 | 6 | class Day10Tests extends FlatSpec with Matchers { 7 | "State transforming" should "work" in { 8 | val input = """value 5 goes to bot 2 9 | |bot 2 gives low to bot 1 and high to bot 0 10 | |value 3 goes to bot 1 11 | |bot 1 gives low to output 1 and high to bot 0 12 | |bot 0 gives low to output 2 and high to output 0 13 | |value 2 goes to bot 2""".stripMargin.split("\n").toList 14 | 15 | runBots(input) shouldBe ProgramState( 16 | Map( 17 | 2 -> Bot(List(2, 5)), 18 | 1 -> Bot(List(2, 3)), 19 | 0 -> Bot(List(3, 5)) 20 | ), 21 | Map( 22 | 0 -> 5, 23 | 1 -> 2, 24 | 2 -> 3 25 | ) 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day12Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class Day12Tests extends FlatSpec with Matchers { 6 | "transformInput" should "work" in { 7 | Day12.transformInput( 8 | """cpy 41 a 9 | |inc a 10 | |inc a 11 | |dec a 12 | |jnz a 2 13 | |dec a""".stripMargin.split("\n").toList, Map('a' -> 0))('a') shouldBe 42 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day13Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class Day13Tests extends FlatSpec with Matchers { 6 | "findShortestRouteTo" should "work" in { 7 | Day13.findShortestRouteTo(7, 4, 10) shouldBe 11 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day14Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import Day14._ 5 | 6 | class Day14Tests extends FlatSpec with Matchers { 7 | "tripleProducer" should "work" in { 8 | keys("abc", 1).take(2).toList shouldBe List(39, 92) 9 | } 10 | 11 | it should "find the 64th key" in { 12 | keys("abc", 1)(63) shouldBe 22728 13 | } 14 | 15 | "md5 times 2017" should "work" in { 16 | md5Times("abc0", 2017) shouldBe "a107ff634856bb300138cac6568c0f24" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day15Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day15.findFirstHole 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day15Tests extends FlatSpec with Matchers { 7 | private val testInput = fileLines("/day15-test.txt") 8 | "findFirstHole" should "return 5 for the two test wheels" in { 9 | findFirstHole(testInput) shouldBe 5 10 | } 11 | 12 | it should "return 85 when another wheel is added" in { 13 | findFirstHole(testInput :+ "Disc #3 has 11 positions; at time=0, it is at position 0.") shouldBe 85 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day16Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day16._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day16Tests extends FlatSpec with Matchers { 7 | "transform" should "work" in { 8 | generate("1") shouldBe "100" 9 | generate("0") shouldBe "001" 10 | generate("11111") shouldBe "11111000000" 11 | generate("111100001010") shouldBe "1111000010100101011110000" 12 | } 13 | 14 | "fill" should "work for the given case" in { 15 | fill("10000", 20) shouldBe "10000011110010000111" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day17Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day17._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day17Tests extends FlatSpec with Matchers { 7 | "finding the shortest path" should "work for ihgpwlah" in { 8 | findShortestPath("ihgpwlah") shouldBe "DDRRRD" 9 | } 10 | 11 | it should "work for kglvqrro" in { 12 | findShortestPath("kglvqrro") shouldBe "DDUDRLRRUDRD" 13 | } 14 | 15 | it should "work for ulqzkmiv" in { 16 | findShortestPath("ulqzkmiv") shouldBe "DRURDRUDDLLDLUURRDULRLDUUDDDRR" 17 | } 18 | 19 | "finding the longest path" should "work for ihgpwlah" in { 20 | findLongestPath("ihgpwlah") shouldBe 370 21 | } 22 | 23 | it should "work for kglvqrro" in { 24 | findLongestPath("kglvqrro") shouldBe 492 25 | } 26 | 27 | it should "work for ulqzkmiv" in { 28 | findLongestPath("ulqzkmiv") shouldBe 830 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day1Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day1._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day1Tests extends FlatSpec with Matchers { 7 | "The first part" should "work for the given cases" in { 8 | findDistanceToLast("R2, L3") shouldBe 5 9 | findDistanceToLast("R2, R2, R2") shouldBe 2 10 | findDistanceToLast("R5, L5, R5, R3") shouldBe 12 11 | } 12 | 13 | "The second part" should "work for the given case" in { 14 | findDistanceToFirstRepeated("R8, R4, R4, R8") shouldBe 4 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day20Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day20._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day20Tests extends FlatSpec with Matchers { 7 | "the sample case" should "work" in { 8 | val parsed = parse( 9 | """|5-8 10 | |0-2 11 | |4-7""".stripMargin.split("\n").toList) 12 | 13 | findAllOutside(parsed, max = 9) shouldBe List(3, 9) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day2Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day2._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day2Tests extends FlatSpec with Matchers { 7 | "The code for the sample case" should "be valid" in { 8 | findCode( 9 | """ULL 10 | |RRDDD 11 | |LURDL 12 | |UUUUD""".stripMargin, keypadStr1 13 | ) shouldBe "1985" 14 | } 15 | 16 | it should "be valid in the new keyboard layout too" in { 17 | findCode( 18 | """ULL 19 | |RRDDD 20 | |LURDL 21 | |UUUUD""".stripMargin, keypadStr2 22 | ) shouldBe "5DB3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day3Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day3._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day3Tests extends FlatSpec with Matchers { 7 | "The code for the sample case" should "be valid" in { 8 | countTriangles(List("""5 10 25""")) shouldBe 0 9 | } 10 | 11 | it should "count vertically too" in { 12 | countTrianglesVertical( 13 | """101 301 501 14 | |102 302 502 15 | |103 303 503 16 | |201 401 601 17 | |202 402 602 18 | |203 403 603""".stripMargin.split("\n").toList) shouldBe 6 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day4Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class Day4Tests extends FlatSpec with Matchers { 6 | "Room checking" should "work for case 1" in { 7 | Room.fromString("aaaaa-bbb-z-y-x-123[abxyz]").isReal shouldBe true 8 | } 9 | it should "work for case 2" in { 10 | Room.fromString("a-b-c-d-e-f-g-h-987[abcde]").isReal shouldBe true 11 | } 12 | it should "work for case 3" in { 13 | Room.fromString("not-a-real-room-404[oarel]").isReal shouldBe true 14 | } 15 | it should "work for case 4" in { 16 | Room.fromString("totally-real-room-200[decoy]").isReal shouldBe false 17 | } 18 | 19 | "Rotation" should "work" in { 20 | Room.fromString("qzmt-zixmtkozy-ivhz-343[asdfg]").rotatedLetters.letters shouldBe "very encrypted name" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day5Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class Day5Tests extends FlatSpec with Matchers { 6 | "Finding the password" should "work" in { 7 | // Day5.findEasyPassword("abc") shouldBe "18F47A30" 8 | } 9 | it should "work in part 2 too" in { 10 | // Day5.findHardPassword("abc") shouldBe "05ACE8E3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day6Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import Day6._ 5 | 6 | class Day6Tests extends FlatSpec with Matchers { 7 | private val testInput = 8 | """eedadn 9 | |drvtee 10 | |eandsr 11 | |raavrd 12 | |atevrs 13 | |tsrnev 14 | |sdttsa 15 | |rasrtv 16 | |nssdts 17 | |ntnada 18 | |svetve 19 | |tesnvt 20 | |vntsnd 21 | |vrdear 22 | |dvrsen 23 | |enarar""".stripMargin.split("\n").toList 24 | 25 | "Decoding" should "work" in { 26 | findPasswordByMostPopular(testInput) shouldBe "easter" 27 | } 28 | 29 | it should "work in reverse too" in { 30 | findPasswordByLeastPopular(testInput) shouldBe "advent" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day7Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day7._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day7Tests extends FlatSpec with Matchers { 7 | "supportsTLS" should "work properly for case 1" in { 8 | supportsTLS("abba[mnop]qrst") shouldBe true 9 | } 10 | 11 | it should "work for case 2" in { 12 | supportsTLS("abcd[bddb]xyyx") shouldBe false 13 | } 14 | 15 | it should "work for case 3" in { 16 | supportsTLS("aaaa[qwer]tyui") shouldBe false 17 | } 18 | 19 | it should "work for case 4" in { 20 | supportsTLS("ioxxoj[asdfgh]zxcvbn") shouldBe true 21 | } 22 | 23 | it should "work for case 5" in { 24 | supportsTLS("asdf[dupa]oxxo") shouldBe true 25 | } 26 | 27 | it should "work for case 6" in { 28 | supportsTLS("asdf[dupa]oxxo[yolo]swag") shouldBe true 29 | } 30 | 31 | it should "work for case 7" in { 32 | supportsTLS("asdf[ollo]oxxo[yolo]swag") shouldBe false 33 | } 34 | 35 | it should "count the input" in { 36 | input.count(supportsTLS) shouldBe 118 37 | } 38 | /* 39 | 40 | "supportsSSL" should "work properly for case 1" in { 41 | supportsSSL("aba[bab]xyz") shouldBe true 42 | } 43 | 44 | it should "work for case 2" in { 45 | supportsSSL("xyx[xyx]xyx") shouldBe false 46 | } 47 | 48 | it should "work for case 3" in { 49 | supportsSSL("aaa[kek]eke") shouldBe true 50 | } 51 | 52 | it should "work for case 4" in { 53 | supportsSSL("zazbz[bzb]cdb") shouldBe true 54 | } 55 | 56 | it should "work for my case" in { 57 | supportsSSL("asdf[babXaba]dupa") shouldBe false 58 | } 59 | 60 | it should "count the input" in { 61 | input.count(supportsSSL) shouldBe 260 62 | } 63 | */ 64 | 65 | it should "work for the weird case" in { 66 | supportsSSL("xtugntiubziynpzbju[onxffxfoxibzzzd]wineojjetzitpemflx[jlncrpyrujpoxluwyc]fxvfnhyqsiwndzoh[lkwwatmiesspwcqulnc]cbimtxmazbbzlvjf") shouldBe false 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day8Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import Day8._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day8Tests extends FlatSpec with Matchers { 7 | 8 | private val start = 9 | """....... 10 | |....... 11 | |.......""".stripMargin 12 | 13 | private val out1 = 14 | """###.... 15 | |###.... 16 | |.......""".stripMargin 17 | 18 | "The transformer" should "understand rect" in { 19 | transformBoard(start, "rect 3x2" :: Nil) shouldBe out1 20 | } 21 | 22 | private val out2 = 23 | """#.#.... 24 | |###.... 25 | |.#.....""".stripMargin 26 | 27 | it should "understand rotating the column" in { 28 | transformBoard(out1, "rotate column x=1 by 1" :: Nil) shouldBe out2 29 | } 30 | private val out3 = 31 | """....#.# 32 | |###.... 33 | |.#.....""".stripMargin 34 | 35 | it should "understand rotating the row" in { 36 | transformBoard(out2, "rotate row y=0 by 4" :: Nil) shouldBe out3 37 | } 38 | private val out4 = 39 | """.#..#.# 40 | |#.#.... 41 | |.#.....""".stripMargin 42 | 43 | it should "understand rotating the column again" in { 44 | transformBoard(out3, "rotate column x=1 by 1" :: Nil) shouldBe out4 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /2016/src/test/scala/com/kubukoz/adventofcode2016/Day9Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2016.Day9._ 4 | import org.scalatest.concurrent.ScalaFutures 5 | import org.scalatest.{FlatSpec, Matchers} 6 | 7 | class Day9Tests extends FlatSpec with Matchers with ScalaFutures { 8 | "Decompressing" should "work without markers" in { 9 | decompress("ADVENT") shouldBe "ADVENT".length 10 | } 11 | 12 | it should "work with case 1" in { 13 | decompress("A(1x5)BC") shouldBe "ABBBBBC".length 14 | } 15 | 16 | it should "work with case 2" in { 17 | decompress("(3x3)XYZ") shouldBe "XYZXYZXYZ".length 18 | } 19 | 20 | it should "work with case 3" in { 21 | decompress("A(2x2)BCD(2x2)EFG") shouldBe "ABCBCDEFEFG".length 22 | } 23 | 24 | it should "work with case 4" in { 25 | decompress("(6x1)(1x3)A") shouldBe "(1x3)A".length 26 | } 27 | 28 | it should "work with case 5" in { 29 | decompress("X(8x2)(3x3)ABCY") shouldBe "X(3x3)ABC(3x3)ABCY".length 30 | } 31 | 32 | "Decompressing recursively" should "work" in { 33 | decompressRec("(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN") shouldBe 445 34 | } 35 | 36 | it should "work indeed" in { 37 | decompressRec("(27x12)(20x12)(13x14)(7x10)(1x12)A") shouldBe 241920 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /2017/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | **/target/ 3 | **/lib/** 4 | -------------------------------------------------------------------------------- /2017/.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.4 4 | jdk: 5 | - oraclejdk8 6 | -------------------------------------------------------------------------------- /2017/README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2017 2 | 3 | [![Build Status](https://travis-ci.org/kubukoz/advent-of-code-2017.svg?branch=master)](https://travis-ci.org/kubukoz/advent-of-code-2017) 4 | 5 | My solutions for the Advent of Code tasks in December, 2017. Probably all will be built in Scala. 6 | -------------------------------------------------------------------------------- /2017/build.sbt: -------------------------------------------------------------------------------- 1 | name := "advent-of-code-2017" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.12.4" 6 | 7 | libraryDependencies ++= Seq( 8 | "org.typelevel" %% "cats-core" % "1.0.0-RC1", 9 | "com.beachape" %% "enumeratum" % "1.5.12", 10 | "com.lihaoyi" %% "fastparse" % "1.0.0", 11 | "com.softwaremill.quicklens" %% "quicklens" % "1.4.11", 12 | "org.scalatest" %% "scalatest" % "3.0.1" % "test" 13 | ) 14 | -------------------------------------------------------------------------------- /2017/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.0.2 2 | -------------------------------------------------------------------------------- /2017/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC12") 2 | addSbtPlugin("com.lucidchart" % "sbt-scalafmt-coursier" % "1.12") 3 | addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0") 4 | -------------------------------------------------------------------------------- /2017/src/main/resources/day1.txt: -------------------------------------------------------------------------------- 1 | 494751136895345894732582362629576539599184296195318162664695189393364372585778868512194863927652788149779748657989318645936221887731542718562643272683862627537378624843614831337441659741281289638765171452576466381314558821636595394981788588673443769343597851883955668818165723174939893841654914556681324133667446412138511724424292394454166623639872425168644336248217213826339741267546823779383343362789527461579565822966859349777937921933694912369552152772735167832762563719664315456987186713541153781499646178238762644186484381142249926194743713139262596264878458636595896487362658672224346241358667234115974528626523648311919886566497837217169673923935143386823757293148719377821517314629812886912412829924484513493885672343964151252433622341141661523814465991516961684511941471572895453711624986269342398786175846925783918686856442684489873327497698963658862856336682422797551251489126661954848572297228765445646745256499679451426358865477844467458533962981852292513358871483321161973583245698763531598395467675529181496911117769834127516441369261275244225978893617456524385518493112272169767775861256649728253754964675812534546226295535939697352141217337346738553495616832783757866928174519145357234834584788253893618549484385733283627199445369658339175644484859385884574943219267922729967571943843794565736975716174727852348441254492886794362934343868643337828637454277582276962353246357835493338372219824371517526474283541714897994127864461433627894831268659336264234436872715374727211764167739169341999573855627775114848275268739159272518673316753672995297888734844388928439859359992475637439771269232916542385876779616695129412366735112593669719335783511355773814685491876721452994714318863716542473187246351548626157775143333161422867924437526253865859969947366972895674966845993244925218766937543487875485647329995285821739359369998935331986126873726737672159265827566443794515755939813676194755474477224152139987944419463371386499841415227734673733555261543871359797796529847861748979527579985757964742667473767269248335229836818297477665453189662485548925521497365877771665365728224394427883312135322325169141784 2 | -------------------------------------------------------------------------------- /2017/src/main/resources/day13.txt: -------------------------------------------------------------------------------- 1 | 0: 3 2 | 1: 2 3 | 2: 4 4 | 4: 6 5 | 6: 4 6 | 8: 6 7 | 10: 5 8 | 12: 6 9 | 14: 8 10 | 16: 8 11 | 18: 8 12 | 20: 6 13 | 22: 12 14 | 24: 8 15 | 26: 8 16 | 28: 10 17 | 30: 9 18 | 32: 12 19 | 34: 8 20 | 36: 12 21 | 38: 12 22 | 40: 12 23 | 42: 14 24 | 44: 14 25 | 46: 12 26 | 48: 12 27 | 50: 12 28 | 52: 12 29 | 54: 14 30 | 56: 12 31 | 58: 14 32 | 60: 14 33 | 62: 14 34 | 64: 14 35 | 70: 10 36 | 72: 14 37 | 74: 14 38 | 76: 14 39 | 78: 14 40 | 82: 14 41 | 86: 17 42 | 88: 18 43 | 96: 26 44 | -------------------------------------------------------------------------------- /2017/src/main/resources/day18.txt: -------------------------------------------------------------------------------- 1 | set i 31 2 | set a 1 3 | mul p 17 4 | jgz p p 5 | mul a 2 6 | add i -1 7 | jgz i -2 8 | add a -1 9 | set i 127 10 | set p 618 11 | mul p 8505 12 | mod p a 13 | mul p 129749 14 | add p 12345 15 | mod p a 16 | set b p 17 | mod b 10000 18 | snd b 19 | add i -1 20 | jgz i -9 21 | jgz a 3 22 | rcv b 23 | jgz b -1 24 | set f 0 25 | set i 126 26 | rcv a 27 | rcv b 28 | set p a 29 | mul p -1 30 | add p b 31 | jgz p 4 32 | snd a 33 | set a b 34 | jgz 1 3 35 | snd b 36 | set f 1 37 | add i -1 38 | jgz i -11 39 | snd a 40 | jgz f -16 41 | jgz a -19 42 | -------------------------------------------------------------------------------- /2017/src/main/resources/day2.txt: -------------------------------------------------------------------------------- 1 | 790 99 345 1080 32 143 1085 984 553 98 123 97 197 886 125 947 2 | 302 463 59 58 55 87 508 54 472 63 469 419 424 331 337 72 3 | 899 962 77 1127 62 530 78 880 129 1014 93 148 239 288 357 424 4 | 2417 2755 254 3886 5336 3655 5798 3273 5016 178 270 6511 223 5391 1342 2377 5 | 68 3002 3307 166 275 1989 1611 364 157 144 3771 1267 3188 3149 156 3454 6 | 1088 1261 21 1063 1173 278 1164 207 237 1230 1185 431 232 660 195 1246 7 | 49 1100 136 1491 647 1486 112 1278 53 1564 1147 1068 809 1638 138 117 8 | 158 3216 1972 2646 3181 785 2937 365 611 1977 1199 2972 201 2432 186 160 9 | 244 86 61 38 58 71 243 52 245 264 209 265 308 80 126 129 10 | 1317 792 74 111 1721 252 1082 1881 1349 94 891 1458 331 1691 89 1724 11 | 3798 202 3140 3468 1486 2073 3872 3190 3481 3760 2876 182 2772 226 3753 188 12 | 2272 6876 6759 218 272 4095 4712 6244 4889 2037 234 223 6858 3499 2358 439 13 | 792 230 886 824 762 895 99 799 94 110 747 635 91 406 89 157 14 | 2074 237 1668 1961 170 2292 2079 1371 1909 221 2039 1022 193 2195 1395 2123 15 | 8447 203 1806 6777 278 2850 1232 6369 398 235 212 992 7520 7304 7852 520 16 | 3928 107 3406 123 2111 2749 223 125 134 146 3875 1357 508 1534 4002 4417 17 | -------------------------------------------------------------------------------- /2017/src/main/resources/day22.txt: -------------------------------------------------------------------------------- 1 | ..######.###...######...# 2 | .##..##.#....#..##.#....# 3 | .##.#....###..##.###.#.#. 4 | #.#.###.#####.###.##.##.# 5 | .###.#.#.###.####..##.### 6 | ..####.##..#.#.#####...## 7 | ....##.###..#.#..#...#### 8 | .#.##.##.#..##...##.###.. 9 | .######..#..#.#####....## 10 | ###.##.###.########...### 11 | .#.#.#..#.##.#..###...#.. 12 | .#.##.#.####.#.#.....###. 13 | ##..###.###..##...#.##.## 14 | ##.#.##..#...##...#...### 15 | ##..#..###.#..##.#.#.#.#. 16 | .##.#####..##....#.#.#..# 17 | ..#.######.##...#..#.##.. 18 | #.##...#.#....###.#.##.#. 19 | .#..#.#.#..#.####..#.#### 20 | .##...##....##..#.#.###.. 21 | ..##.#.#.##..##.#.#....#. 22 | ###.###.######.#.######## 23 | ..#.####.#.#.##..####...# 24 | #.##..#.#.####...#..#..## 25 | ###.###.#..##..#.###....# 26 | -------------------------------------------------------------------------------- /2017/src/main/resources/day23.txt: -------------------------------------------------------------------------------- 1 | set b 84 2 | set c b 3 | jnz a 2 4 | jnz 1 5 5 | mul b 100 6 | sub b -100000 7 | set c b 8 | sub c -17000 9 | set f 1 10 | set d 2 11 | set e 2 12 | set g d 13 | mul g e 14 | sub g b 15 | jnz g 2 16 | set f 0 17 | sub e -1 18 | set g e 19 | sub g b 20 | jnz g -8 21 | sub d -1 22 | set g d 23 | sub g b 24 | jnz g -13 25 | jnz f 2 26 | sub h -1 27 | set g b 28 | sub g c 29 | jnz g 2 30 | jnz 1 3 31 | sub b -17 32 | jnz 1 -23 33 | -------------------------------------------------------------------------------- /2017/src/main/resources/day24.txt: -------------------------------------------------------------------------------- 1 | 50/41 2 | 19/43 3 | 17/50 4 | 32/32 5 | 22/44 6 | 9/39 7 | 49/49 8 | 50/39 9 | 49/10 10 | 37/28 11 | 33/44 12 | 14/14 13 | 14/40 14 | 8/40 15 | 10/25 16 | 38/26 17 | 23/6 18 | 4/16 19 | 49/25 20 | 6/39 21 | 0/50 22 | 19/36 23 | 37/37 24 | 42/26 25 | 17/0 26 | 24/4 27 | 0/36 28 | 6/9 29 | 41/3 30 | 13/3 31 | 49/21 32 | 19/34 33 | 16/46 34 | 22/33 35 | 11/6 36 | 22/26 37 | 16/40 38 | 27/21 39 | 31/46 40 | 13/2 41 | 24/7 42 | 37/45 43 | 49/2 44 | 32/11 45 | 3/10 46 | 32/49 47 | 36/21 48 | 47/47 49 | 43/43 50 | 27/19 51 | 14/22 52 | 13/43 53 | 29/0 54 | 33/36 55 | 2/6 56 | -------------------------------------------------------------------------------- /2017/src/main/resources/day25.txt: -------------------------------------------------------------------------------- 1 | Begin in state A. 2 | Perform a diagnostic checksum after 12919244 steps. 3 | 4 | In state A: 5 | If the current value is 0: 6 | - Write the value 1. 7 | - Move one slot to the right. 8 | - Continue with state B. 9 | If the current value is 1: 10 | - Write the value 0. 11 | - Move one slot to the left. 12 | - Continue with state C. 13 | 14 | In state B: 15 | If the current value is 0: 16 | - Write the value 1. 17 | - Move one slot to the left. 18 | - Continue with state A. 19 | If the current value is 1: 20 | - Write the value 1. 21 | - Move one slot to the right. 22 | - Continue with state D. 23 | 24 | In state C: 25 | If the current value is 0: 26 | - Write the value 1. 27 | - Move one slot to the right. 28 | - Continue with state A. 29 | If the current value is 1: 30 | - Write the value 0. 31 | - Move one slot to the left. 32 | - Continue with state E. 33 | 34 | In state D: 35 | If the current value is 0: 36 | - Write the value 1. 37 | - Move one slot to the right. 38 | - Continue with state A. 39 | If the current value is 1: 40 | - Write the value 0. 41 | - Move one slot to the right. 42 | - Continue with state B. 43 | 44 | In state E: 45 | If the current value is 0: 46 | - Write the value 1. 47 | - Move one slot to the left. 48 | - Continue with state F. 49 | If the current value is 1: 50 | - Write the value 1. 51 | - Move one slot to the left. 52 | - Continue with state C. 53 | 54 | In state F: 55 | If the current value is 0: 56 | - Write the value 1. 57 | - Move one slot to the right. 58 | - Continue with state D. 59 | If the current value is 1: 60 | - Write the value 1. 61 | - Move one slot to the right. 62 | - Continue with state A. 63 | -------------------------------------------------------------------------------- /2017/src/main/resources/day6.txt: -------------------------------------------------------------------------------- 1 | 11 11 13 7 0 15 5 5 4 4 1 1 7 1 15 11 2 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Commons.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import java.security.MessageDigest 4 | 5 | object Commons { 6 | private val md = MessageDigest.getInstance("MD5") 7 | 8 | def md5(s: String): String = md5With(s, md) 9 | 10 | def md5Separated(s: String): String = md5With(s, MessageDigest.getInstance("MD5")) 11 | 12 | private def md5With(s: String, md: MessageDigest) = 13 | "%1$032X".format(BigInt(1, md.digest(s.getBytes))) 14 | } 15 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day1.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day1 { 4 | private def sumDist(dist: Int, s: Int*): Int = { 5 | val megaList = s ++ s 6 | 7 | s.zipWithIndex.map { 8 | case (digit, i) => List(digit, megaList(i + dist)) 9 | }.filter(_.distinct.size == 1).map(_.head).sum 10 | } 11 | 12 | def sum1(s: Int*): Int = sumDist(1, s:_*) 13 | 14 | def sumHalfway(s: Int*): Int = sumDist(s.length / 2, s:_*) 15 | 16 | def inputSums(): (Int, Int) = { 17 | val input = fileLines("/day1.txt") 18 | 19 | val parsed: List[Int] = input.flatMap(_.map(_.toString.toInt)) 20 | 21 | (sum1(parsed: _*), sumHalfway(parsed:_*)) 22 | } 23 | 24 | def main(args: Array[String]): Unit = { 25 | println("Part 1, 2: " + inputSums()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day10.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day10 { 4 | 5 | case class State(values: List[Int], position: Int, skip: Int) 6 | 7 | def part1(values: List[Int], ranges: List[Int]): Int = { 8 | transformInternal(State(values, 0, 0), ranges).values.take(2).product 9 | } 10 | 11 | def part2(initialValues: List[Int], inputStr: String): String = { 12 | val input = inputStr.map(_.toInt).toList 13 | 14 | val inputWithAppendix = input ::: List(17, 31, 73, 47, 23) 15 | val sparseHash = (1 to 64).foldLeft(State(initialValues, 0, 0))((state, _) => transformInternal(state, inputWithAppendix)) 16 | 17 | val denseHash = sparseHash.values.grouped(16).map(_.reduce(_ ^ _)).toList 18 | 19 | denseHash.map(_.formatted("%02x")).mkString 20 | } 21 | 22 | private def transformInternal(state: State, allRanges: List[Int]): State = allRanges.foldLeft(state) { (state, range) => 23 | val length = state.values.size 24 | 25 | val values = state.values 26 | val position = state.position 27 | val skip = state.skip 28 | 29 | val splitPoint = position % length 30 | val (before, after) = values.splitAt(splitPoint) 31 | val newStr = after ::: before 32 | 33 | val (toReverse, toAppend) = newStr.splitAt(range) 34 | 35 | val (newBefore, newAfter) = (toReverse.reverse ::: toAppend).splitAt(length - splitPoint) 36 | 37 | val newValues = newAfter ::: newBefore 38 | 39 | State(newValues, position + skip + range, skip + 1) 40 | } 41 | 42 | def main(args: Array[String]): Unit = { 43 | val input = "129,154,49,198,200,133,97,254,41,6,2,1,255,0,191,108" 44 | 45 | val initialState = (0 to 255).toList 46 | 47 | val part1Input = input.split(",").map(_.toInt).toList 48 | 49 | println(part1(initialState, part1Input)) 50 | println(part2(initialState, input)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day11.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import enumeratum._ 4 | 5 | import scala.annotation.tailrec 6 | import scala.collection.immutable 7 | 8 | object Day11 { 9 | 10 | sealed trait Step extends EnumEntry { 11 | lazy val reverse: Step = Step.applySafe(Step.indexOf(this) + Step.values.size / 2) 12 | lazy val next: Step = Step.applySafe(Step.indexOf(this) + 1) 13 | } 14 | 15 | case class ReduceRule(combination: List[Step], result: List[Step]) 16 | 17 | object Step extends Enum[Step] { 18 | case object N extends Step 19 | case object NE extends Step 20 | case object SE extends Step 21 | case object S extends Step 22 | case object SW extends Step 23 | case object NW extends Step 24 | 25 | override def values: immutable.IndexedSeq[Step] = findValues 26 | def applySafe(i: Int): Step = values(i % values.size) 27 | 28 | val rules: Set[ReduceRule] = { 29 | val removeReverse = values.map(v => ReduceRule(List(v, v.reverse), Nil)) 30 | 31 | val reduceToAverage = values.map { v => 32 | ReduceRule(List(v, v.next.next), List(v.next)) 33 | } 34 | 35 | (reduceToAverage ++ removeReverse).toSet 36 | } 37 | } 38 | 39 | @tailrec 40 | private def reduced(steps: List[Step]): List[Step] = { 41 | val ruleToUse = Step.rules.find(_.combination.forall(steps.contains)) 42 | 43 | ruleToUse match { 44 | case Some(ReduceRule(combination, result)) => reduced(steps.diff(combination) ::: result) 45 | case _ => steps 46 | } 47 | } 48 | 49 | def distance(steps: List[Step]): Int = reduced(steps).size 50 | 51 | def distances(steps: List[Step]): List[Int] = { 52 | steps.scanLeft(List.empty[Step]) { (previousSteps, step) => 53 | reduced(step :: previousSteps) 54 | }.map(_.size) 55 | } 56 | 57 | def main(args: Array[String]): Unit = { 58 | val input = fileLines("/day11.txt").mkString("\n").split(",").map(Step.withNameInsensitive).toList 59 | println(distance(input)) 60 | println(distances(input).max) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day12.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day12 { 4 | 5 | final case class Connection(left: Int, right: Int) { 6 | def asFrom(from: Int): Option[Connection] = from match { 7 | case `left` => Some(this) 8 | case `right` => Some(Connection(right, left)) 9 | case _ => None 10 | } 11 | } 12 | 13 | private def parse(line: String): List[Connection] = { 14 | line.split("""\s*<->\s*""") match { 15 | case Array(left, rights) => 16 | rights.split("""\s*,\s*""").map(_.toInt).map(Connection(left.toInt, _)) 17 | } 18 | }.toList 19 | 20 | private def groupContaining(connections: List[Connection], target: Int): Set[Int] = { 21 | def go(from: Int, seen: Set[Int]): Set[Int] = { 22 | val directFromTarget = connections.flatMap(_.asFrom(from)).map(_.right).toSet -- seen 23 | directFromTarget ++ directFromTarget.flatMap(go(_, seen ++ directFromTarget)) 24 | } 25 | 26 | go(target, Set.empty) 27 | } 28 | 29 | def groupsIn(connections: List[Connection]): Set[Set[Int]] = { 30 | def go(elemsLeft: Set[Int], mem: Set[Set[Int]]): Set[Set[Int]] = elemsLeft.headOption match { 31 | case Some(h) => 32 | val hGroup = groupContaining(connections, h) 33 | 34 | go(elemsLeft -- hGroup, mem + hGroup) 35 | 36 | case _ => mem 37 | } 38 | 39 | go(connections.map(_.left).toSet, Set.empty) 40 | } 41 | 42 | def main(args: Array[String]): Unit = { 43 | val input = fileLines("/day12.txt").flatMap(parse) 44 | val allGroups = groupsIn(input) 45 | 46 | println(allGroups.find(_.contains(0)).get.size) 47 | println(allGroups.size) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day13.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day13 { 4 | private val pat = """(\d+): (\d+)""".r 5 | 6 | sealed trait Direction extends Product with Serializable 7 | 8 | case object Up extends Direction 9 | 10 | case object Down extends Direction 11 | 12 | case class Line(range: Int, value: Int, direction: Direction) { 13 | def nextMove: Line = { 14 | val newDirection = direction match { 15 | case Down if value == 2 => Up 16 | case Up if value == (range - 1) => Down 17 | case oldDir => oldDir 18 | } 19 | 20 | val valueDelta = direction match { 21 | case Down => -1 22 | case Up => 1 23 | } 24 | 25 | Line(range, value + valueDelta, newDirection) 26 | } 27 | } 28 | 29 | val parse: List[String] => Map[Int, Line] = _.map { 30 | case pat(depth, range) => 31 | depth.toInt -> Line(range.toInt, 1, Up) 32 | }.toMap 33 | 34 | final case class Lines(lines: Map[Int, Line]) extends AnyVal { 35 | //mapValues not used because of stack overflow 36 | def nextStep: Lines = copy(lines.map { case (k, v) => k -> v.nextMove }) 37 | 38 | def findSeverity: Int = severities.sum 39 | 40 | def nextStepsStream: Iterator[Lines] = Iterator.iterate(this)(_.nextStep) 41 | 42 | def severities: Iterator[Int] = { 43 | nextStepsStream.take(lines.keySet.max + 1).zipWithIndex.flatMap { 44 | case (state, currentIndex) => 45 | state.lines.get(currentIndex).filter(_.value == 1).map(_.range * currentIndex) 46 | } 47 | } 48 | 49 | def findSafeDelay: Int = nextStepsStream.indexWhere(_.severities.isEmpty) 50 | } 51 | 52 | def main(args: Array[String]): Unit = { 53 | val input = fileLines("/day13.txt") 54 | val parsed = Lines(parse(input)) 55 | 56 | println(parsed.findSeverity) //1612 57 | //takes about 70sec 58 | println(parsed.findSafeDelay) //3907994 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day14.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Day14 { 6 | private val knotHash: String => String = com.kubukoz.adventofcode2017.Day10.part2((0 to 255).toList, _) 7 | 8 | private type AddrSet = Set[(Int, Int)] 9 | 10 | def getOnes(input: String): AddrSet = { 11 | val zeroes = "0" * 4 12 | 13 | val hashes = (0 until 128).map(i => s"$input-$i").map(knotHash) 14 | 15 | val matrix = hashes.map( 16 | _.toList.flatMap { char => 17 | val str = Integer.parseInt(char.toString, 16).toBinaryString 18 | (zeroes + str).takeRight(4) 19 | } 20 | ) 21 | 22 | for { 23 | (row, y) <- matrix.zipWithIndex 24 | ('1', x) <- row.zipWithIndex 25 | } yield (x, y) 26 | }.toSet 27 | 28 | private val inRange: ((Int, Int), (Int, Int)) => Boolean = { 29 | case ((x1, y1), (x2, y2)) => 30 | ((x1 - x2).abs, (y1 - y2).abs) match { 31 | case (0, 1) => true 32 | case (1, 0) => true 33 | case _ => false 34 | } 35 | } 36 | 37 | private def removeRegionContaining(bitMap: AddrSet, x: Int, y: Int): AddrSet = { 38 | @tailrec 39 | def go(remaining: AddrSet, currentRegion: AddrSet): AddrSet = { 40 | remaining.find(point => currentRegion.exists(inRange(_, point))) match { 41 | case Some(point) => go(remaining - point, currentRegion + point) 42 | case None => remaining 43 | } 44 | } 45 | 46 | go(bitMap - ((x, y)), Set((x, y))) 47 | } 48 | 49 | def countRegions(bitMap: AddrSet): Int = { 50 | @tailrec 51 | def go(bitMap: AddrSet, regionCount: Int): Int = { 52 | bitMap.headOption match { 53 | case None => regionCount 54 | case Some((x, y)) => 55 | val remaining = removeRegionContaining(bitMap, x, y) 56 | 57 | println(s"found region ${regionCount + 1}") 58 | go(remaining, regionCount + 1) 59 | } 60 | } 61 | 62 | go(bitMap, 0) 63 | } 64 | 65 | def main(args: Array[String]): Unit = { 66 | val input = "ffayrhll" 67 | 68 | val bitMap = getOnes(input) 69 | 70 | println(bitMap.size) 71 | println(countRegions(bitMap)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day15.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import scala.Function.const 4 | 5 | object Day15 { 6 | private val onesMask = (math.pow(2, 16) - 1).toInt 7 | 8 | case class Generator(seed: Long, factor: Int)(pred: Long => Boolean) { 9 | def nextNumber(currentNumber: Long): Long = { 10 | (currentNumber * factor) % 2147483647 11 | } 12 | 13 | val numbers: Iterator[Long] = { 14 | val it = Iterator.iterate(seed)(nextNumber).filter(pred) 15 | it.next() //eh 16 | it.map(_ & onesMask) 17 | } 18 | } 19 | 20 | def countMatching(amount: Int, g1: Generator, g2: Generator): Int = { 21 | (g1.numbers zip g2.numbers).take(amount) 22 | .count { case (a, b) => a == b } 23 | } 24 | 25 | def main(args: Array[String]): Unit = { 26 | val g1Seed = 883 27 | val g2Seed = 879 28 | val g1 = Generator(g1Seed, 16807) _ 29 | val g2 = Generator(g2Seed, 48271) _ 30 | 31 | 32 | val million = 1000000 33 | println(countMatching(40 * million, g1(const(true)), g2(const(true)))) 34 | println(countMatching(5 * million, g1(_ % 4 == 0), g2(_ % 8 == 0))) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day16.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day16 { 4 | 5 | sealed trait DanceMove extends Product with Serializable 6 | 7 | case class Spin(x: Int) extends DanceMove 8 | 9 | case class Exchange(a: Int, b: Int) extends DanceMove 10 | 11 | case class Partner(a: Char, b: Char) extends DanceMove 12 | 13 | private def dance(string: String): DanceMove => String = { 14 | case Spin(x) => 15 | val (left, right) = string.splitAt(string.length - x) 16 | right + left 17 | 18 | case Exchange(aIndex, bIndex) => 19 | val a = string(aIndex) 20 | val b = string(bIndex) 21 | 22 | string.updated(aIndex, b).updated(bIndex, a) 23 | 24 | case Partner(a, b) => 25 | val aIndex = string.indexOf(a) 26 | val bIndex = string.indexOf(b) 27 | 28 | string.updated(aIndex, b).updated(bIndex, a) 29 | } 30 | 31 | private val spinPat = """s(\d+)""".r 32 | private val exchangePat = """x(\d+)\/(\d+)""".r 33 | private val partnerPat = """p(\w)\/(\w)""".r 34 | 35 | val parse: String => DanceMove = { 36 | case spinPat(x) => Spin(x.toInt) 37 | case exchangePat(a, b) => Exchange(a.toInt, b.toInt) 38 | case partnerPat(a, b) => Partner(a.head, b.head) 39 | } 40 | 41 | def transform(original: String, moves: List[DanceMove], dances: Int): String = { 42 | def doDances(i: Int): Stream[String] = Stream.fill(i)(moves).flatten.scanLeft(original)(dance(_)(_)) 43 | 44 | val cycleSizeOpt = doDances(dances).tail.zipWithIndex.collectFirst { case (`original`, i) => i + 1 } 45 | 46 | val finalDanceCount = cycleSizeOpt.fold(dances) { cycleSize => 47 | val movesLeft = (moves.size * dances.toLong % cycleSize).toInt 48 | (movesLeft / moves.size.toDouble).ceil.toInt 49 | } 50 | 51 | doDances(finalDanceCount).last 52 | } 53 | 54 | def main(args: Array[String]): Unit = { 55 | val input = fileLines("/day16.txt").head.split(",").map(parse).toList 56 | val startPositions = ('a' to 'p').mkString 57 | 58 | println(transform(startPositions, input, 1)) 59 | println(transform(startPositions, input, 1000000000)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day17.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Day17 { 6 | def transform1(initState: IndexedSeq[Int], jump: Int, maxSteps: Int): Int = { 7 | @tailrec 8 | def go(state: IndexedSeq[Int], currentIndex: Int, stepsDone: Int): IndexedSeq[Int] = { 9 | if(stepsDone == maxSteps) state 10 | else { 11 | val itemToInsert = stepsDone + 1 12 | 13 | val toSplit = (currentIndex + jump) % state.size + 1 14 | val (left, right) = state.splitAt(toSplit) 15 | val newInts = left ++ (itemToInsert +: right) 16 | 17 | go(newInts, left.size, itemToInsert) 18 | } 19 | } 20 | 21 | val result = go(initState, 0, 0) 22 | result(result.indexOf(2017) + 1) 23 | } 24 | 25 | def transform2(jump: Int, maxSteps: Int): Int = { 26 | @tailrec 27 | def go(state: Int, currentIndex: Int, stepsDone: Int): Int = { 28 | if (stepsDone == maxSteps) state 29 | else { 30 | val itemToInsert = stepsDone + 1 31 | val newIndex = ((currentIndex + jump) % itemToInsert) + 1 32 | val newValueAfterZero = if(newIndex == 1) itemToInsert else state 33 | 34 | go(newValueAfterZero, newIndex, itemToInsert) 35 | } 36 | } 37 | 38 | go(1, 1, 1) 39 | } 40 | 41 | def main(args: Array[String]): Unit = { 42 | val input = 369 43 | println(transform1(Vector(0), input, 2017)) //1547 44 | println(transform2(input, 50000000)) //31154878 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day2.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import cats.Order 4 | import cats.instances.int._ 5 | import cats.instances.list._ 6 | import cats.syntax.foldable._ 7 | 8 | import scala.Integral.Implicits._ 9 | import scala.Numeric.Implicits._ 10 | 11 | object Day2 { 12 | 13 | val parseLine: String => List[Int] = _.split("""\s+""").map(_.toInt).toList 14 | 15 | private def diffMaxMin[T: Order : Numeric](line: List[T]): Option[T] = { 16 | for { 17 | max <- line.maximumOption 18 | min <- line.minimumOption 19 | } yield max - min 20 | } 21 | 22 | private def divWhole[T: Integral](line: List[T]): Option[T] = { 23 | line.combinations(2).map(_.sorted).collectFirst { 24 | case a :: b :: Nil if b % a == 0 => b / a 25 | } 26 | } 27 | 28 | private def solve[LT, T: Numeric](input: List[LT])(f: LT => Option[T]): T = 29 | input.flatMap(f(_)).sum 30 | 31 | def part1[T: Order : Numeric](input: List[List[T]]): T = solve(input)(diffMaxMin(_)) 32 | 33 | def part2[T: Integral](input: List[List[T]]): T = solve(input)(divWhole(_)) 34 | 35 | def main(args: Array[String]): Unit = { 36 | val input = fileLines("/day2.txt").map(parseLine) 37 | 38 | println(part1(input)) //46402 39 | println(part2(input)) //265 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day20.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | import fastparse.all._ 3 | 4 | object Day20 { 5 | 6 | case class Coords(x: Long, y: Long, z: Long) { 7 | def +(another: Coords) = Coords(x + another.x, y + another.y, z + another.z) 8 | def sum: Long = x.abs + y.abs + z.abs 9 | } 10 | 11 | case class Info(position: Coords, velocity: Coords, acceleration: Coords) 12 | 13 | val number: Parser[Long] = P( 14 | "-".? ~ CharIn('0' to '9').rep(min = 1) 15 | ).!.map(_.toLong) 16 | 17 | val coords: Parser[Coords] = P( 18 | "<" ~ number ~ "," ~ number ~ "," ~ number ~ ">" 19 | ).map(Coords.tupled) 20 | 21 | val info: Parser[Info] = 22 | P("p=" ~ coords ~ ", v=" ~ coords ~ ", a=" ~ coords).map(Info.tupled) 23 | 24 | val updateAll: List[Info] => List[Info] = _.map { i => 25 | val newVelo = i.velocity + i.acceleration 26 | 27 | Info(i.position + newVelo, newVelo, i.acceleration) 28 | } 29 | 30 | def solve1(parsed: List[Info]): Int = { 31 | 32 | Stream.iterate(parsed)(updateAll) 33 | .map(_.zipWithIndex.minBy(_._1.position.sum)) 34 | .take(250).last._2 35 | } 36 | 37 | def solve2(parsed: List[Info]): Int = { 38 | val removeCollisions: List[Info] => List[Info] = 39 | _.groupBy(_.position) 40 | .values.filter(_.size == 1) 41 | .flatten.toList 42 | 43 | Stream.iterate(parsed)(updateAll andThen removeCollisions) 44 | .take(50).last.size 45 | } 46 | 47 | def main(args: Array[String]): Unit = { 48 | val input = fileLines("/day20.txt") 49 | val parsed = input.map(info.parse(_).get.value) 50 | 51 | println(solve1(parsed)) 52 | println(solve2(parsed)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day24.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day24 { 4 | case class Component(left: Int, right: Int){ 5 | def sum: Int = left + right 6 | def swap = Component(right, left) 7 | } 8 | 9 | private val pat = """(\d+)/(\d+)""".r 10 | private val parse: String => Component = { 11 | case pat(l, r) => Component(l.toInt, r.toInt) 12 | } 13 | 14 | def sum(components: List[Component]): Int = { 15 | components.map(_.sum).sum 16 | } 17 | 18 | def remove[T](i: Int, from: List[T]): List[T] = { 19 | val (left, _ :: right) = from.splitAt(i) 20 | left ::: right 21 | } 22 | 23 | def findCombinations(components: List[Component]): List[List[Component]] = { 24 | def go(remaining: List[Component], mem: List[List[Component]], lastElem: Component): List[List[Component]] = { 25 | if(remaining.isEmpty) mem 26 | else { 27 | val potentialNext = remaining.zipWithIndex.collect { 28 | case (h, i) if lastElem.right == h.left => (h, i) 29 | case (h, i) if lastElem.right == h.right => (h.swap, i) 30 | } 31 | 32 | if (potentialNext.isEmpty) mem else { 33 | potentialNext.flatMap { case (next, i) => 34 | go(remove(i, remaining), mem.map(next :: _), next) 35 | } 36 | } 37 | } 38 | } 39 | 40 | val startMem = components.zipWithIndex.collect { 41 | case (s@Component(0, _), i) => (s, i) 42 | case (s@Component(_, 0), i) => (s.swap, i) 43 | } 44 | 45 | startMem.flatMap { case (init, i) => 46 | go(remove(i, components), List(List(init)), init) 47 | } 48 | } 49 | 50 | def main(args: Array[String]): Unit = { 51 | val input = fileLines("/day24.txt").map(parse) 52 | 53 | val all = findCombinations(input) 54 | println(sum(all.maxBy(sum))) 55 | println(sum(all.maxBy(a => (a.size, sum(a))))) 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day3.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day3 { 4 | val segmentLengths: Stream[Int] = Stream.iterate(1)(_ + 1).flatMap(x => Stream(x, x)) 5 | 6 | //0-indexed segment number 7 | def segmentDistance(segmentNumber: Int): Int = (segmentNumber + 3) / 4 8 | 9 | def distance(number: Int): Int = { 10 | val lines: Stream[List[Int]] = segmentLengths.scanLeft(List.empty[Int]) { (previousLine, thisLength) => 11 | val previousLast = previousLine.lastOption 12 | val diff = previousLast.getOrElse(0) + 1 13 | 14 | List.tabulate(thisLength)(_ + diff) 15 | }.tail 16 | 17 | lines.zipWithIndex.collectFirst { case (line, i) if line.contains(number) => 18 | val fromCenter = { 19 | val indexInLine = line.indexOf(number) 20 | (line.size / 2 - indexInLine).abs 21 | } 22 | 23 | segmentDistance(i) + fromCenter 24 | }.get 25 | } 26 | 27 | 28 | def firstLarger(than: Int): Option[Int] = { 29 | type Coords = (Int, Int) 30 | 31 | val coords: Stream[Coords] = segmentLengths.zipWithIndex.flatMap { case (thisLength, segmentIndex) => 32 | val originalX = segmentDistance(segmentIndex) 33 | val previousSegmentDistance = segmentDistance(segmentIndex - 1) 34 | 35 | val (rotX, rotY) = segmentIndex % 4 match { 36 | case 0 => (-1, 1) 37 | case 1 => (1, 1) 38 | case 2 => (1, -1) 39 | case _ => (-1, -1) 40 | } 41 | 42 | val swapped: Coords => Coords = if(segmentIndex % 2 == 0) identity else _.swap 43 | 44 | (0 until thisLength).toStream.map { indexInLine => 45 | val originalY = indexInLine - previousSegmentDistance 46 | 47 | val rotated = (originalX * rotX, originalY * rotY) 48 | swapped(rotated) 49 | } 50 | } 51 | 52 | lazy val values: Stream[Int] = coords.zipWithIndex.map { case ((thisX, thisY), i) => 53 | val neighborSum = coords.zipWithIndex.take(i).collect { 54 | case ((x, y), j) if (thisX - x).abs <= 1 && (thisY - y).abs <= 1 => 55 | values(j) 56 | }.take(4).sum 57 | 58 | if (neighborSum > 0) neighborSum else 1 59 | } 60 | 61 | values.find(_ >= than) 62 | } 63 | 64 | def main(args: Array[String]): Unit = { 65 | println(distance(368078) == 371) 66 | 67 | println(firstLarger(368078).contains(369601)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day4.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day4 { 4 | def validate(line: String, grouping: String => String): Boolean = { 5 | line.split(" ").groupBy(grouping).forall(_._2.tail.isEmpty) 6 | } 7 | 8 | val isValid: String => Boolean = validate(_, identity) 9 | val isValid2: String => Boolean = validate(_, _.sorted) 10 | 11 | def main(args: Array[String]): Unit = { 12 | val lines = fileLines("/day4.txt") 13 | 14 | println(lines.count(isValid)) 15 | println(lines.count(isValid2)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day5.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day5 { 4 | 5 | def transform(intList: List[Int], delta: Int => Int): Int = { 6 | val length = intList.length 7 | 8 | def go(ints: Vector[Int], index: Int, steps: Int): Int = { 9 | if (index > length - 1 || index < 0) steps 10 | else { 11 | val instruction = ints(index) 12 | 13 | val newInts = ints.updated(index, delta(instruction) + instruction) 14 | val newIndex = index + instruction 15 | 16 | go(newInts, newIndex, steps + 1) 17 | } 18 | } 19 | 20 | go(intList.toVector, 0, 0) 21 | } 22 | 23 | val part1: List[Int] => Int = transform(_, _ => 1) 24 | val part2: List[Int] => Int = transform(_, x => if (x >= 3) -1 else 1) 25 | 26 | def main(args: Array[String]): Unit = { 27 | val input = fileLines("/day5.txt").map(_.toInt) 28 | println(part1(input)) 29 | println(part2(input)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day6.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Day6 { 6 | def maxBlockIndex(blocks: List[Int]): Int = { 7 | val maxValue = blocks.max 8 | blocks.zipWithIndex.collectFirst { case (`maxValue`, i) => i }.get 9 | } 10 | 11 | def findLoop(blocks: List[Int]): List[List[Int]] = { 12 | val length = blocks.size 13 | 14 | def nextCombination(blocks: List[Int]): List[Int] = { 15 | val maxIndex = maxBlockIndex(blocks) 16 | val toDistribute = blocks(maxIndex) 17 | 18 | (0 until toDistribute) 19 | .map(x => (maxIndex + x + 1) % length) 20 | .foldLeft(blocks.updated(maxIndex, 0)) { (tempBlocks, nextIndex) => 21 | tempBlocks.updated(nextIndex, tempBlocks(nextIndex) + 1) 22 | } 23 | } 24 | 25 | @tailrec 26 | def go(currentBlocks: List[Int], previousCombinations: List[List[Int]]): List[List[Int]] = { 27 | val newPrevious = currentBlocks :: previousCombinations 28 | 29 | if (previousCombinations.contains(currentBlocks)) newPrevious 30 | else go(nextCombination(currentBlocks), newPrevious) 31 | } 32 | 33 | go(blocks, Nil) 34 | } 35 | 36 | def getResults(blocks: List[Int]): (Int, Int) = { 37 | val results = findLoop(blocks) 38 | 39 | val lastResult = results.head 40 | (results.size - 1, results.tail.takeWhile(_ != lastResult).size + 1) 41 | } 42 | 43 | def main(args: Array[String]): Unit = { 44 | val numbers = fileLines("/day6.txt").flatMap(_.split("""\s+""").map(_.toInt)) 45 | val results = getResults(numbers) 46 | 47 | println(s"Part 1: ${results._1}") 48 | println(s"Part 2: ${results._2}") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day8.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | object Day8 { 4 | private val incPat = """(\w+) (\w+) (-?\d+) if (\w+) (.+) (-?\d+)""".r 5 | 6 | type Operator = (Int, Int) => Boolean 7 | val operators: String => Operator = { 8 | case ">" => _ > _ 9 | case "<" => _ < _ 10 | case ">=" => _ >= _ 11 | case "<=" => _ <= _ 12 | case "!=" => _ != _ 13 | case "==" => _ == _ 14 | } 15 | 16 | case class Increment(at: String, by: Int, ifAt: String, op: Operator, valueAt: Int) 17 | 18 | val parse: String => Increment = { 19 | case incPat(at, word, by, ifAt, opStr, valueAt) => 20 | val modifySign: Int => Int = if (word == "inc") identity else -_ 21 | Increment(at, modifySign(by.toInt), ifAt, operators(opStr), valueAt.toInt) 22 | } 23 | 24 | def transform(increments: List[Increment]): List[Map[String, Int]] = { 25 | val init = Map.empty[String, Int].withDefaultValue(0) 26 | 27 | increments.scanLeft(init) { 28 | case (previous, Increment(at, by, ifAt, op, valueAt)) => 29 | if (op(previous(ifAt), valueAt)) 30 | previous.updated(at, previous(at) + by) 31 | else previous 32 | } 33 | } 34 | 35 | def main(args: Array[String]): Unit = { 36 | val parsed = fileLines("/day8.txt").map(parse) 37 | 38 | val transformed = transform(parsed) 39 | println(transformed.last.values.max) 40 | println(transformed.flatMap(_.values).max) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/Day9.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | import fastparse.all._ 3 | 4 | object Day9 { 5 | 6 | sealed trait Thing extends Product with Serializable 7 | 8 | final case class Group(things: List[Thing]) extends Thing { 9 | def score: Int = scoreMem(0) 10 | 11 | private def scoreMem(mem: Int): Int = { 12 | val thisScore = mem + 1 13 | thisScore + groups.map(_.scoreMem(thisScore)).sum 14 | } 15 | 16 | def garbages: List[Garbage] = things.collect { 17 | case g: Garbage => g :: Nil 18 | case g: Group => g.garbages 19 | }.flatten 20 | 21 | private def groups: List[Group] = things.collect { case g: Group => g } 22 | } 23 | 24 | final case class Garbage(value: String) extends Thing 25 | 26 | 27 | private val group: P[Group] = P("{" ~ thing.rep(sep = ",") ~ "}").map(things => Group(things.toList)) 28 | private val garbage: P[Garbage] = P("<" ~ (("!" ~ AnyChar).!.map(_ => "") | CharPred(_ != '>').!).rep ~ ">").map(x => Garbage(x.mkString)) 29 | 30 | private val thing: P[Thing] = group | garbage 31 | 32 | def parse(s: String): Thing = thing.parse(s).get.value 33 | 34 | def score(s: String): Int = group.parse(s).get.value.score 35 | 36 | def main(args: Array[String]): Unit = { 37 | val input = fileLines("/day9.txt").mkString("\n") 38 | val parsed = group.parse(input).get.value 39 | 40 | println(parsed.score) 41 | println(parsed.garbages.map(_.value.length).sum) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /2017/src/main/scala/com/kubukoz/adventofcode2017/package.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz 2 | 3 | package object adventofcode2017 { 4 | def fileLines(fileName: String): List[String] = { 5 | val source = io.Source.fromURL(getClass.getResource(fileName)) 6 | val result = source.getLines().toList 7 | source.close() 8 | result 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day10Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import com.kubukoz.adventofcode2017.Day10._ 5 | 6 | class Day10Tests extends FlatSpec with Matchers { 7 | "samples" should "work" in { 8 | part1(List(0, 1, 2, 3, 4), List(3, 4, 1, 5)) shouldBe 12 9 | } 10 | 11 | "samples of part 2" should "work" in { 12 | val normalState = (0 to 255).toList 13 | part2(normalState, "") shouldBe "a2582a3a0e66e6e86e3812dcb672a272" 14 | part2(normalState, "AoC 2017") shouldBe "33efeb34ea91902bb2f59c9920caa6cd" 15 | part2(normalState, "1,2,3") shouldBe "3efbe78a8d82f29979031a4aa0b16a9d" 16 | part2(normalState, "1,2,4") shouldBe "63960835bcdc130f0b66d7ff4f6a5a8e" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day11Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2017.Day11.Step.{NE, S, SE, SW} 4 | import org.scalatest.{FlatSpec, Matchers} 5 | import com.kubukoz.adventofcode2017.Day11._ 6 | 7 | class Day11Tests extends FlatSpec with Matchers { 8 | "distance" should "work" in { 9 | distance(List(NE, NE, NE)) shouldBe 3 10 | distance(List(NE, NE, SW, SW)) shouldBe 0 11 | distance(List(NE, NE, S, S)) shouldBe 2 12 | distance(List(SE, SW, SE, SW, SW)) shouldBe 3 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day13Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import com.kubukoz.adventofcode2017.Day13._ 5 | 6 | class Day13Tests extends FlatSpec with Matchers { 7 | val input = Lines(parse( 8 | """0: 3 9 | |1: 2 10 | |4: 4 11 | |6: 4""".stripMargin.split("\n").toList)) 12 | 13 | "findSeverity" should "work" in { 14 | input.findSeverity shouldBe 24 15 | } 16 | 17 | "findSafeDelay" should "work" in { 18 | input.findSafeDelay shouldBe 10 19 | } 20 | 21 | "newValue" should "work for ranges 2, 3 and 5" in { 22 | def values(range: Int, amount: Int) = (1 to amount).scanLeft(Line(range, 1, Up))((a, _) => a.nextMove).map(_.value).toList 23 | 24 | values(5, 15) shouldBe List(1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5, 4, 3, 2) 25 | values(2, 10) shouldBe List(1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1) 26 | values(3, 10) shouldBe List(1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day1Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2017 2 | 3 | import com.kubukoz.adventofcode2017.Day1._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day1Tests extends FlatSpec with Matchers { 7 | "The first part" should "work for the given cases" in { 8 | sum1(1, 1, 2, 2) shouldBe 3 9 | sum1(1, 1, 1, 1) shouldBe 4 10 | sum1(1, 2, 3, 4) shouldBe 0 11 | sum1(9, 1, 2, 1, 2, 1, 2, 9) shouldBe 9 12 | sum1(4, 9, 4, 7, 5, 1, 1, 3, 6, 8, 4) shouldBe 5 13 | } 14 | 15 | "The second part" should "work for the given cases" in { 16 | sumHalfway(1, 2, 1, 2) shouldBe 6 17 | sumHalfway(1, 2, 2, 1) shouldBe 0 18 | sumHalfway(1, 2, 3, 4, 2, 5) shouldBe 4 19 | sumHalfway(1, 2, 3, 1, 2, 3) shouldBe 12 20 | sumHalfway(1, 2, 1, 3, 1, 4, 1, 5) shouldBe 4 21 | } 22 | 23 | "Inputs" should "be properly handled" in { 24 | inputSums() shouldBe (1150, 1064) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day2Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import cats.instances.int._ 5 | 6 | import com.kubukoz.adventofcode2017.Day2._ 7 | class Day2Tests extends FlatSpec with Matchers { 8 | "The first part" should "work for the given cases" in { 9 | val input = 10 | """5 1 9 5 11 | |7 5 3 12 | |2 4 6 8""".stripMargin.split("\n").map(parseLine).toList 13 | 14 | part1(input) shouldBe 18 15 | } 16 | 17 | "The second part" should "work for the given cases" in { 18 | val input = 19 | """5 9 2 8 20 | |9 4 7 3 21 | |3 8 6 5""".stripMargin.split("\n").map(parseLine).toList 22 | 23 | part2(input) shouldBe 9 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day3Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import com.kubukoz.adventofcode2017.Day3._ 5 | 6 | class Day3Tests extends FlatSpec with Matchers { 7 | "part 1" should "work" in { 8 | distance(1) shouldBe 0 9 | distance(12) shouldBe 3 10 | distance(23) shouldBe 2 11 | distance(1024) shouldBe 31 12 | } 13 | 14 | "segmentDistance" should "work" in { 15 | segmentDistance(0) shouldBe 0 16 | segmentDistance(1) shouldBe 1 17 | segmentDistance(2) shouldBe 1 18 | segmentDistance(3) shouldBe 1 19 | segmentDistance(4) shouldBe 1 20 | segmentDistance(5) shouldBe 2 21 | segmentDistance(6) shouldBe 2 22 | segmentDistance(7) shouldBe 2 23 | segmentDistance(8) shouldBe 2 24 | segmentDistance(9) shouldBe 3 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day6Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | import com.kubukoz.adventofcode2017.Day6._ 5 | 6 | class Day6Tests extends FlatSpec with Matchers { 7 | private val input = List(0, 2, 7, 0) 8 | 9 | "maxBlockIndex" should "work" in { 10 | maxBlockIndex(input) shouldBe 2 11 | } 12 | 13 | "the solution" should "work" in { 14 | val list = findLoop(input) 15 | list.head shouldBe List(2, 4, 1, 2) 16 | getResults(input) shouldBe (5, 4) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day7Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2017.Day7._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day7Tests extends FlatSpec with Matchers { 7 | private val input = 8 | """pbga (66) 9 | |xhth (57) 10 | |ebii (61) 11 | |havc (66) 12 | |ktlj (57) 13 | |fwft (72) -> ktlj, cntj, xhth 14 | |qoyq (66) 15 | |padx (45) -> pbga, havc, qoyq 16 | |tknk (41) -> ugml, padx, fwft 17 | |jptl (61) 18 | |ugml (68) -> gyxo, ebii, jptl 19 | |gyxo (61) 20 | |cntj (57)""".stripMargin.split("\n").toList 21 | private val tree = buildTree(input.map(parseLine)).get 22 | 23 | "buildTree" should "work" in { 24 | tree shouldBe Tree( 25 | "tknk", 26 | List( 27 | Tree( 28 | "ugml", 29 | List( 30 | Tree("gyxo", Nil, 61), 31 | Tree("jptl", Nil, 61), 32 | Tree("ebii", Nil, 61) 33 | ), 34 | 68), 35 | Tree( 36 | "padx", 37 | List( 38 | Tree("havc", Nil, 66), 39 | Tree("qoyq", Nil, 66), 40 | Tree("pbga", Nil, 66) 41 | ), 42 | 45 43 | ), 44 | Tree( 45 | "fwft", 46 | List( 47 | Tree("cntj", Nil, 57), 48 | Tree("ktlj", Nil, 57), 49 | Tree("xhth", Nil, 57) 50 | ), 51 | 72 52 | ) 53 | ), 54 | 41 55 | ) 56 | tree.name shouldBe "tknk" 57 | } 58 | 59 | "balanceOffset" should "be calculated properly" in { 60 | tree.balanceOffset.get shouldBe 60 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /2017/src/test/scala/com/kubukoz/adventofcode2016/Day9Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2016 2 | 3 | import com.kubukoz.adventofcode2017.Day9._ 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class Day9Tests extends FlatSpec with Matchers { 7 | 8 | "parsing garbage" should "work" in { 9 | parse("<>") shouldBe Garbage("") 10 | parse("") shouldBe Garbage("lt7ali7li7") 11 | parse("<<<<>") shouldBe Garbage("<<<") 12 | parse("<{!>}>") shouldBe Garbage("{}") 13 | parse("") shouldBe Garbage("") 14 | parse(">") shouldBe Garbage("") 15 | parse("""<{o"i!a,<{i""") shouldBe Garbage("""{o"i,<{i}") shouldBe Group(Garbage("{},{},{{}}") :: Nil) 24 | parse("{,,,}") shouldBe Group(List(Garbage("a"), Garbage("a"), Garbage("a"), Garbage("a"))) 25 | parse("{{},{},{},{}}") shouldBe Group(List( 26 | Group(Garbage("a") :: Nil), 27 | Group(Garbage("a") :: Nil), 28 | Group(Garbage("a") :: Nil), 29 | Group(Garbage("a") :: Nil) 30 | )) 31 | parse("{{},{},{},{}}") shouldBe Group(Group(Garbage("},{<},{<},{,,,}") shouldBe 1 40 | score("{{},{},{},{}}") shouldBe 9 41 | score("{{},{},{},{}}") shouldBe 9 42 | score("{{},{},{},{}}") shouldBe 3 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /2018/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | **/target/ 3 | **/lib/** 4 | -------------------------------------------------------------------------------- /2018/.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.7 4 | jdk: 5 | - oraclejdk8 6 | -------------------------------------------------------------------------------- /2018/README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2018 2 | 3 | [![Build Status](https://travis-ci.com/kubukoz/advent-of-code-2018.svg?branch=master)](https://travis-ci.com/kubukoz/advent-of-code-2018) 4 | 5 | My solutions for the Advent of Code tasks in December, 2018. Probably all will be built in Scala. 6 | -------------------------------------------------------------------------------- /2018/build.sbt: -------------------------------------------------------------------------------- 1 | name := "advent-of-code-2018" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.12.7" 6 | 7 | libraryDependencies ++= Seq( 8 | "org.typelevel" %% "kittens" % "1.2.0", 9 | "io.chrisdavenport" %% "cats-par" % "0.2.0", 10 | "org.tpolecat" %% "atto-fs2" % "0.6.4", 11 | "org.typelevel" %% "cats-core" % "1.5.0", 12 | "org.typelevel" %% "mouse" % "0.19", 13 | "com.github.gvolpe" %% "console4cats" % "0.5", 14 | "io.chrisdavenport" %% "cats-time" % "0.2.0", 15 | "org.typelevel" %% "cats-effect" % "1.1.0", 16 | "co.fs2" %% "fs2-io" % "1.0.0", 17 | "com.beachape" %% "enumeratum" % "1.5.12", 18 | "com.lihaoyi" %% "fastparse" % "1.0.0", 19 | "com.softwaremill.quicklens" %% "quicklens" % "1.4.11", 20 | "org.scalatest" %% "scalatest" % "3.0.1" % "test", 21 | "com.olegpy" %% "meow-mtl" % "0.2.0", 22 | "io.chrisdavenport" %% "monoids" % "0.0.2", 23 | "org.typelevel" %% "spire" % "0.16.0", 24 | "com.github.julien-truffaut" %% "monocle-macro" % "1.5.0-cats", 25 | compilerPlugin("org.spire-math" %% "kind-projector" % "0.9.8"), 26 | compilerPlugin( 27 | "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full 28 | ), 29 | compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.0-M4"), 30 | "org.scalaz" %% "deriving-macro" % "1.0.0", 31 | compilerPlugin("org.scalaz" %% "deriving-plugin" % "1.0.0") 32 | ) 33 | 34 | scalacOptions ++= Seq("-language:higherKinds", "-Ypartial-unification") 35 | -------------------------------------------------------------------------------- /2018/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.2.6 2 | -------------------------------------------------------------------------------- /2018/project/plugins.sbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2018/project/plugins.sbt -------------------------------------------------------------------------------- /2018/src/main/resources/day6.txt: -------------------------------------------------------------------------------- 1 | 165, 169 2 | 334, 217 3 | 330, 227 4 | 317, 72 5 | 304, 232 6 | 115, 225 7 | 323, 344 8 | 161, 204 9 | 316, 259 10 | 63, 250 11 | 280, 205 12 | 84, 282 13 | 271, 158 14 | 190, 296 15 | 106, 349 16 | 171, 178 17 | 203, 108 18 | 89, 271 19 | 193, 254 20 | 111, 210 21 | 341, 343 22 | 349, 311 23 | 143, 172 24 | 170, 307 25 | 128, 157 26 | 183, 315 27 | 211, 297 28 | 74, 281 29 | 119, 164 30 | 266, 345 31 | 184, 62 32 | 96, 142 33 | 134, 61 34 | 117, 52 35 | 318, 72 36 | 338, 287 37 | 61, 215 38 | 323, 255 39 | 93, 171 40 | 325, 249 41 | 183, 171 42 | 71, 235 43 | 329, 306 44 | 322, 219 45 | 151, 298 46 | 180, 255 47 | 336, 291 48 | 72, 300 49 | 223, 286 50 | 179, 257 51 | -------------------------------------------------------------------------------- /2018/src/main/resources/deriving.conf: -------------------------------------------------------------------------------- 1 | cats.Show = cats.derived.semi.show 2 | cats.kernel.Order = cats.derived.semi.order 3 | cats.kernel.Semigroup = cats.derived.semi.semigroup 4 | -------------------------------------------------------------------------------- /2018/src/main/scala/com/kubukoz/adventofcode2018/Commons.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | 3 | import java.security.MessageDigest 4 | 5 | object Commons { 6 | private val md = MessageDigest.getInstance("MD5") 7 | 8 | def md5(s: String): String = md5With(s, md) 9 | 10 | def md5Separated(s: String): String = 11 | md5With(s, MessageDigest.getInstance("MD5")) 12 | 13 | private def md5With(s: String, md: MessageDigest) = 14 | "%1$032X".format(BigInt(1, md.digest(s.getBytes))) 15 | } 16 | -------------------------------------------------------------------------------- /2018/src/main/scala/com/kubukoz/adventofcode2018/Day1.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | 3 | import cats.effect.{ExitCode, IO, IOApp, Sync} 4 | import cats.implicits._ 5 | import fs2._ 6 | 7 | object Day1 extends IOApp { 8 | 9 | def findFirstRepeated[F[_]: Sync, S](as: Stream[F, S]): F[Option[S]] = { 10 | as.zipWithScan(Set.empty[S])(_ + _) 11 | .collectFirst { case (a, seen) if seen(a) => a } 12 | .compile 13 | .last 14 | } 15 | 16 | override def run(args: List[String]): IO[ExitCode] = { 17 | import cats.effect.Console.io._ 18 | 19 | val input = fileLines[IO]("/day1.txt").takeWhile(_.nonEmpty).map(_.toInt) 20 | 21 | val task1 = input.foldMonoid.compile.last 22 | val task2 = findFirstRepeated(input.repeat.scan(0)(_ + _)) 23 | 24 | (task1, task2) 25 | .parMapN(putStrLn(_) *> putStrLn(_)) 26 | .flatten 27 | .as(ExitCode.Success) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /2018/src/main/scala/com/kubukoz/adventofcode2018/Day2.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | 3 | import cats.effect.{ExitCode, IO, IOApp, Sync} 4 | import cats.implicits._ 5 | import mouse.boolean._ 6 | 7 | object Day2 extends IOApp { 8 | def part1[F[_]: Sync](inputs: fs2.Stream[F, String]): F[Int] = { 9 | inputs 10 | .map(values) 11 | .compile 12 | .foldMonoid 13 | .map { case (twos, threes) => twos * threes } 14 | } 15 | 16 | def part2[F[_]: Sync](inputs: fs2.Stream[F, String]): F[Option[String]] = { 17 | inputs.compile.to[Stream].map { 18 | _.combinations(2).toStream.collectFirstSome { 19 | case Stream(a, b) => 20 | commonLetters(a, b).some.filter(_.length === a.length - 1) 21 | } 22 | } 23 | } 24 | 25 | private def values(input: String): (Int, Int) = { 26 | val counts: Map[Int, Char] = 27 | input.toList.groupBy(identity).map(_.map(_.size).swap) 28 | 29 | val hasTwos = counts.isDefinedAt(2) ?? 1 30 | val hasThrees = counts.isDefinedAt(3) ?? 1 31 | 32 | (hasTwos, hasThrees) 33 | } 34 | 35 | //assumes strings are of the same size 36 | private def commonLetters(a: String, b: String): String = 37 | (a zip b).collect { case (left, right) if left === right => left }.mkString 38 | 39 | import cats.effect.Console.io._ 40 | 41 | override def run(args: List[String]): IO[ExitCode] = { 42 | val inputs = fileLines[IO]("/day2.txt") 43 | 44 | val p1 = part1(inputs) 45 | val p2 = part2(inputs) 46 | 47 | (p1, p2).mapN(putStrLn(_) *> putStrLn(_)).flatten 48 | }.as(ExitCode.Success) 49 | } 50 | -------------------------------------------------------------------------------- /2018/src/main/scala/com/kubukoz/adventofcode2018/Day5.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | 3 | import cats.Defer 4 | import cats.effect._ 5 | import cats.implicits._ 6 | import cats.temp.par._ 7 | import fs2.Stream 8 | 9 | import scala.annotation.tailrec 10 | 11 | object Day5 extends IOApp { 12 | val CapsDiff: Int = 'a' - 'A' //32 13 | 14 | def uncons(s: String): Option[(Char, String)] = s match { 15 | case "" => None 16 | case _ => Some((s.head, s.tail)) 17 | } 18 | 19 | @tailrec 20 | def singleRound(str: String, 21 | prefix: String, 22 | flag: Boolean): (String, Boolean) = { 23 | uncons(str) match { 24 | case Some((head, tail)) => 25 | uncons(tail) match { 26 | case Some((second, rest)) => 27 | if ((head - second).abs == CapsDiff) { 28 | singleRound(rest, prefix, flag = true) 29 | } else { 30 | singleRound(tail, s"$prefix$head", flag) 31 | } 32 | case None => (prefix + str, flag) 33 | } 34 | case None => (prefix, flag) 35 | } 36 | } 37 | 38 | @tailrec 39 | def part1(input: String): String = { 40 | singleRound(input, "", flag = false) match { 41 | case (result, true) => 42 | part1(result) 43 | case (result, false) => result 44 | } 45 | } 46 | 47 | def part2[F[_]: Concurrent: ConsoleOut](input: String): F[Option[Int]] = { 48 | val allPolymers = 'A' to 'Z' 49 | 50 | Stream 51 | .emits(allPolymers) 52 | .covary[F] 53 | .map(char => input.replaceAll(s"[$char${char.toLower}]", "")) 54 | .mapAsyncUnordered(maxConcurrent = 4) { trimmed => 55 | Defer[F].defer(part1(trimmed).length.pure[F]) 56 | } 57 | .reduce(_ min _) 58 | .compile 59 | .last 60 | } 61 | 62 | override def run(args: List[String]): IO[ExitCode] = { 63 | implicit val console = cats.effect.Console.io 64 | import console._ 65 | 66 | fileLines[IO]("/day5.txt") 67 | .filter(_.nonEmpty) 68 | .evalTap[IO] { input => 69 | (IO(part1(input).length), part2[IO](input)) 70 | .parMapN( 71 | (p1, p2) => putStrLn(s"part 1: $p1") *> putStrLn(s"part 2: $p2") 72 | ) 73 | .flatten 74 | } 75 | .compile 76 | .drain 77 | .as(ExitCode.Success) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /2018/src/main/scala/com/kubukoz/adventofcode2018/package.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz 2 | import java.util.concurrent.{Executors, TimeUnit} 3 | 4 | import cats.{FlatMap, ~>} 5 | import cats.effect._ 6 | import cats.syntax.flatMap._ 7 | import cats.syntax.functor._ 8 | import fs2.text 9 | 10 | import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService} 11 | 12 | package object adventofcode2018 { 13 | def blockingEc[F[_]: Sync]: Resource[F, ExecutionContextExecutorService] = 14 | Resource.make( 15 | Sync[F].delay( 16 | ExecutionContext.fromExecutorService(Executors.newCachedThreadPool()) 17 | ) 18 | )(ec => Sync[F].delay(ec.shutdown())) 19 | 20 | def fileContent[F[_]: Sync: ContextShift]( 21 | fileName: String 22 | ): fs2.Stream[F, String] = 23 | fs2.Stream.resource(blockingEc).flatMap[F, String] { ec => 24 | import fs2.text 25 | 26 | fs2.io 27 | .readInputStream( 28 | Sync[F].delay(getClass.getResourceAsStream(fileName)), 29 | 4192, 30 | ec 31 | ) 32 | .through(text.utf8Decode) 33 | } 34 | 35 | def fileLines[F[_]: Sync: ContextShift]( 36 | fileName: String 37 | ): fs2.Stream[F, String] = 38 | fileContent(fileName).through(text.lines[F]) 39 | 40 | def modifyBetween[A](from: Int, 41 | length: Int)(modify: A => A)(as: List[A]): List[A] = { 42 | val (before, right) = as.splitAt(from) 43 | val (toChange, after) = right.splitAt(length) 44 | 45 | before ::: toChange.map(modify) ::: after 46 | } 47 | 48 | def measure[F[_]: Clock: FlatMap: ConsoleOut, A, E]( 49 | tag: String 50 | )(fa: F[A]): F[A] = { 51 | val now = Clock[F].monotonic(TimeUnit.MILLISECONDS) 52 | 53 | for { 54 | before <- now 55 | result <- fa 56 | after <- now 57 | _ <- ConsoleOut[F].putStrLn(s"$tag took ${after - before}ms") 58 | } yield result 59 | } 60 | 61 | def measureK[F[_]: Clock: FlatMap: ConsoleOut, E](tag: String): F ~> F = 62 | λ[F ~> F](measure(tag)(_)) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /2018/src/test/scala/com/kubukoz/adventofcode2018/Day2Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | import cats.effect.IO 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | class Day2Tests extends WordSpec with Matchers { 6 | "Part 1" when { 7 | "sample input" should { 8 | "return 12" in { 9 | val input = List( 10 | "abcdef", 11 | "bababc", 12 | "abbcde", 13 | "abcccd", 14 | "aabcdd", 15 | "abcdee", 16 | "ababab" 17 | ) 18 | 19 | Day2.part1[IO](fs2.Stream.emits(input)).unsafeRunSync() shouldBe 12 20 | } 21 | } 22 | } 23 | 24 | "Part 2" when { 25 | "sample input" should { 26 | "return fgij" in { 27 | val input = 28 | List("abcde", "fghij", "klmno", "pqrst", "fguij", "axcye", "wvxyz") 29 | 30 | Day2.part2[IO](fs2.Stream.emits(input)).unsafeRunSync() shouldBe Some("fgij") 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /2018/src/test/scala/com/kubukoz/adventofcode2018/Day4Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | import java.time.LocalTime 3 | 4 | import cats.Show 5 | import cats.data.Chain 6 | import cats.effect.{ConsoleOut, IO} 7 | import cats.effect.concurrent.Ref 8 | import cats.effect.test.TestConsole 9 | import com.kubukoz.adventofcode2018.day4data.Event 10 | import org.scalatest.{Matchers, WordSpec} 11 | 12 | class Day4Tests extends WordSpec with Matchers { 13 | "mostFrequentMinute" should { 14 | "work in case one minute is more frequent than others" in { 15 | Day4.mostFrequentMinute( 16 | List( 17 | LocalTime.of(10, 11), 18 | LocalTime.of(10, 10), 19 | LocalTime.of(11, 11), 20 | LocalTime.of(12, 11), 21 | LocalTime.of(10, 10) 22 | ) 23 | ) shouldBe Some((11, 3)) 24 | } 25 | 26 | "work in case there are only three minutes" in { 27 | Day4.mostFrequentMinute( 28 | List(LocalTime.of(11, 11), LocalTime.of(12, 10), LocalTime.of(10, 10)) 29 | ) shouldBe Some((10, 2)) 30 | } 31 | } 32 | 33 | "part1, part2" should { 34 | "work in the example" in { 35 | val raw = """[1518-11-01 00:00] Guard #10 begins shift 36 | |[1518-11-01 00:05] falls asleep 37 | |[1518-11-01 00:25] wakes up 38 | |[1518-11-01 00:30] falls asleep 39 | |[1518-11-01 00:55] wakes up 40 | |[1518-11-01 23:58] Guard #99 begins shift 41 | |[1518-11-02 00:40] falls asleep 42 | |[1518-11-02 00:50] wakes up 43 | |[1518-11-03 00:05] Guard #10 begins shift 44 | |[1518-11-03 00:24] falls asleep 45 | |[1518-11-03 00:29] wakes up 46 | |[1518-11-04 00:02] Guard #99 begins shift 47 | |[1518-11-04 00:36] falls asleep 48 | |[1518-11-04 00:46] wakes up 49 | |[1518-11-05 00:03] Guard #99 begins shift 50 | |[1518-11-05 00:45] falls asleep 51 | |[1518-11-05 00:55] wakes up""".stripMargin 52 | 53 | val test = for { 54 | events <- fs2.Stream 55 | .emits(raw.split("\n")) 56 | .through(atto.fs2.Pipes.parseN[IO, Event](Day4.parser)) 57 | .compile 58 | .toList 59 | 60 | result <- Day4.part1[IO](events) 61 | result2 <- Day4.part2[IO](events) 62 | } yield { 63 | result shouldBe Some(240) 64 | result2 shouldBe Some(4455) 65 | } 66 | 67 | test.unsafeRunSync() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /2018/src/test/scala/com/kubukoz/adventofcode2018/Day5Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.adventofcode2018 2 | 3 | import cats.effect.IO 4 | import org.scalatest.{Matchers, WordSpec} 5 | 6 | import scala.concurrent.ExecutionContext 7 | 8 | class Day5Tests extends WordSpec with Matchers { 9 | "Sample input" should { 10 | "work in part1" in { 11 | Day5.part1("dabAcCaCBAcCcaDA") shouldBe "dabCBAcaDA" 12 | 13 | implicit val cs = IO.contextShift(ExecutionContext.global) 14 | implicit val console = cats.effect.Console.io 15 | 16 | Day5.part2[IO]("dabAcCaCBAcCcaDA").unsafeRunSync() shouldBe Some(4) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /2019/README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2019 2 | 3 | My solutions for Advent Of Code in 2019. They'll be written in Haskell and quite likely Scala whenever I'm out of my sick Haskell skillz. 4 | -------------------------------------------------------------------------------- /2019/haskell/.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | advent-of-code-v2019.cabal 3 | *~ -------------------------------------------------------------------------------- /2019/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /2019/haskell/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Lib 4 | 5 | main :: IO () 6 | main = someFunc 7 | -------------------------------------------------------------------------------- /2019/haskell/data/day1.txt: -------------------------------------------------------------------------------- 1 | 83568 2 | 132382 3 | 65095 4 | 105082 5 | 138042 6 | 59055 7 | 79113 8 | 123950 9 | 59773 10 | 55031 11 | 56499 12 | 122835 13 | 123608 14 | 82848 15 | 109981 16 | 115633 17 | 126241 18 | 137240 19 | 54983 20 | 129523 21 | 101517 22 | 90879 23 | 82446 24 | 105897 25 | 108653 26 | 130530 27 | 113607 28 | 140338 29 | 125646 30 | 112605 31 | 68080 32 | 105466 33 | 93462 34 | 147116 35 | 127370 36 | 128362 37 | 83129 38 | 146946 39 | 102658 40 | 62824 41 | 52950 42 | 119301 43 | 61671 44 | 92820 45 | 139579 46 | 93816 47 | 148535 48 | 77893 49 | 80523 50 | 69543 51 | 51773 52 | 144074 53 | 100340 54 | 64565 55 | 68404 56 | 88923 57 | 144824 58 | 87836 59 | 51209 60 | 99770 61 | 111044 62 | 144978 63 | 56585 64 | 137236 65 | 73290 66 | 86608 67 | 72415 68 | 57783 69 | 130619 70 | 109599 71 | 59655 72 | 99708 73 | 118488 74 | 104989 75 | 93812 76 | 135899 77 | 110396 78 | 89346 79 | 119482 80 | 67292 81 | 143810 82 | 64085 83 | 104169 84 | 145618 85 | 104035 86 | 75765 87 | 88638 88 | 139325 89 | 89099 90 | 132807 91 | 117255 92 | 98029 93 | 114780 94 | 104708 95 | 100671 96 | 98052 97 | 141263 98 | 149844 99 | 117643 100 | 123410 101 | -------------------------------------------------------------------------------- /2019/haskell/package.yaml: -------------------------------------------------------------------------------- 1 | library: 2 | source-dirs: src 3 | tests: 4 | advent-of-code-v2019-test: 5 | source-dirs: test 6 | main: Spec.hs 7 | ghc-options: 8 | - -threaded 9 | - -rtsopts 10 | - -with-rtsopts=-N 11 | dependencies: 12 | - advent-of-code-v2019 13 | copyright: 2019 Jakub Kozłowski 14 | maintainer: kubukoz@gmail.com 15 | dependencies: 16 | - split 17 | - hspec 18 | - mtl 19 | - base >= 4.7 && < 5 20 | name: advent-of-code-v2019 21 | version: 0.1.0.0 22 | author: Jakub Kozłowski 23 | github: kubukoz/advent-of-code-v2019 24 | license: MIT 25 | executables: 26 | advent-of-code-v2019-exe: 27 | source-dirs: app 28 | main: Main.hs 29 | ghc-options: 30 | - -threaded 31 | - -rtsopts 32 | - -with-rtsopts=-N 33 | dependencies: 34 | - advent-of-code-v2019 35 | description: Please see the README on GitHub at 36 | -------------------------------------------------------------------------------- /2019/haskell/src/Core.hs: -------------------------------------------------------------------------------- 1 | module Core where 2 | import Data.Foldable 3 | 4 | newtype Day = Day { number :: Int } 5 | 6 | instance Show Day where 7 | show = show . number 8 | 9 | 10 | readWhole :: Day -> IO String 11 | readWhole day = readFile path where path = "./data/day" ++ show day ++ ".txt" 12 | 13 | -- Reads a file from the data directory by the convention day.txt 14 | readLines :: Day -> IO [String] 15 | readLines = fmap lines . readWhole 16 | 17 | -- Shows all lines of file by day 18 | showLines day = readLines day >>= traverse_ print 19 | -------------------------------------------------------------------------------- /2019/haskell/src/Day0.hs: -------------------------------------------------------------------------------- 1 | module Day0 where 2 | 3 | import Core 4 | import Data.List 5 | import Data.Foldable 6 | 7 | file = readWhole (Day 0) 8 | 9 | main = do 10 | content <- file 11 | print . getLevel $ content 12 | print . basementLevel $ content 13 | 14 | 15 | getLevel :: String -> Int 16 | getLevel = last . sums 17 | 18 | basementLevel :: String -> Maybe Int 19 | basementLevel = findIndex (0 >) . sums 20 | 21 | sums = scanl (+) 0 . map parseToDiff 22 | 23 | parseToDiff '(' = 1 24 | parseToDiff ')' = -1 25 | parseToDiff _ = 0 26 | -------------------------------------------------------------------------------- /2019/haskell/src/Day1.hs: -------------------------------------------------------------------------------- 1 | module Day1 where 2 | 3 | import Core 4 | 5 | file = readLines (Day 1) 6 | 7 | fuel n = (n `div` 3) - 2 8 | 9 | fuelRec :: Int -> Int 10 | fuelRec n = current + remaining where 11 | current = fuel n `max` 0 12 | remaining | current == 0 = 0 13 | | otherwise = fuelRec current 14 | 15 | forEachModule :: (Int -> Int) -> [String] -> Int 16 | forEachModule getFuel = sum . fmap (getFuel . read) 17 | 18 | part1 = forEachModule fuel 19 | part2 = forEachModule fuelRec 20 | 21 | main = do 22 | content <- file 23 | print (part1 content) 24 | print (part2 content) 25 | -------------------------------------------------------------------------------- /2019/haskell/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib 2 | ( someFunc 3 | ) where 4 | 5 | 6 | someFunc :: IO () 7 | someFunc = undefined 8 | -------------------------------------------------------------------------------- /2019/haskell/stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: lts-14.16 21 | 22 | # User packages to be built. 23 | # Various formats can be used as shown in the example below. 24 | # 25 | # packages: 26 | # - some-directory 27 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 28 | # subdirs: 29 | # - auto-update 30 | # - wai 31 | packages: 32 | - . 33 | # Dependency packages to be pulled from upstream that are not in the resolver. 34 | # These entries can reference officially published versions as well as 35 | # forks / in-progress versions pinned to a git hash. For example: 36 | # 37 | # extra-deps: 38 | # - acme-missiles-0.3 39 | # - git: https://github.com/commercialhaskell/stack.git 40 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 41 | # 42 | # extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | # flags: {} 46 | 47 | # Extra package databases containing global packages 48 | # extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=2.1" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /2019/haskell/stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | size: 524804 10 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/16.yaml 11 | sha256: 4d1519a4372d051d47a5eae2241cf3fb54e113d7475f89707ddb6ec06add2888 12 | original: lts-14.16 13 | -------------------------------------------------------------------------------- /2019/haskell/test/Spec.hs: -------------------------------------------------------------------------------- 1 | import Test.Hspec 2 | import Control.Exception ( evaluate ) 3 | import qualified Day0 4 | import qualified Day1 5 | 6 | main :: IO () 7 | main = hspec . sequence_ $ [day0, day1] 8 | 9 | day0 = describe "Day0" $ do 10 | it "succeeds in first part" $ Day0.getLevel `shouldReturnWithFile` 232 11 | it "succeeds in second part" $ Day0.basementLevel `shouldReturnWithFile` Just 12 | 1783 13 | where a `shouldReturnWithFile` b = fmap a Day0.file `shouldReturn` b 14 | 15 | 16 | day1 = describe "Day1" $ do 17 | it "succeeds in first part" $ Day1.part1 `shouldReturnWithLines` 3402634 18 | it "succeeds in second part" $ Day1.part2 `shouldReturnWithLines` 5101069 19 | where a `shouldReturnWithLines` b = (a <$> Day1.file) `shouldReturn` b 20 | 21 | -------------------------------------------------------------------------------- /2019/scala/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .metals/ 3 | *.class 4 | .idea 5 | .bloop 6 | target/ 7 | **/secret.conf 8 | **/.DS_Store 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /2019/scala/.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.10 4 | cache: 5 | directories: 6 | - $HOME/.sbt/1.0/dependency 7 | - $HOME/.sbt/boot/scala* 8 | - $HOME/.sbt/launchers 9 | - $HOME/.ivy2/cache 10 | - $HOME/.coursier 11 | before_cache: 12 | - du -h -d 1 $HOME/.ivy2/cache 13 | - du -h -d 2 $HOME/.sbt/ 14 | - find $HOME/.sbt -name "*.lock" -type f -delete 15 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete 16 | - rm -rf $HOME/.ivy2/local 17 | before_install: 18 | - git fetch --tags 19 | stages: 20 | - name: test 21 | - name: release 22 | if: (branch = master AND type = push) OR (tag IS present) 23 | jobs: 24 | include: 25 | # stage="test" if no stage is specified 26 | - script: 27 | - sbt test:compile test 28 | # run ci-release only if previous stages passed 29 | - stage: release 30 | script: sbt ci-release 31 | -------------------------------------------------------------------------------- /2019/scala/README.md: -------------------------------------------------------------------------------- 1 | # aoc 2019 2 | 3 | [![License](http://img.shields.io/:license-Apache%202-green.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt) 4 | -------------------------------------------------------------------------------- /2019/scala/build.sbt: -------------------------------------------------------------------------------- 1 | val compilerPlugins = List( 2 | compilerPlugin("org.typelevel" % "kind-projector" % "0.11.0" cross CrossVersion.full), 3 | compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") 4 | ) 5 | 6 | val commonSettings = Seq( 7 | scalaVersion := "2.13.1", 8 | scalacOptions ~= (_.filterNot(_ == "-Xfatal-warnings") ++ Seq("-Ymacro-annotations")), 9 | name := "scala", 10 | updateOptions := updateOptions.value.withGigahorse(false), 11 | libraryDependencies ++= Seq( 12 | "co.fs2" %% "fs2-io" % "2.1.0", 13 | "org.typelevel" %% "cats-tagless-macros" % "0.10", 14 | "dev.profunktor" %% "console4cats" % "0.8.0", 15 | "io.chrisdavenport" %% "semigroups" % "0.2.0", 16 | "io.chrisdavenport" %% "monoids" % "0.2.0", 17 | "com.olegpy" %% "meow-mtl-core" % "0.4.0", 18 | "com.olegpy" %% "meow-mtl-effects" % "0.4.0", 19 | "io.estatico" %% "newtype" % "0.4.3", 20 | "com.github.julien-truffaut" %% "monocle-macro" % "2.0.0", 21 | "org.tpolecat" %% "atto-core" % "0.7.2", 22 | "org.scalatest" %% "scalatest" % "3.1.0" % Test 23 | ) ++ compilerPlugins 24 | ) 25 | 26 | val core = project.settings(commonSettings).settings(name += "-core") 27 | 28 | val aoc = 29 | project 30 | .in(file(".")) 31 | .settings(commonSettings) 32 | .settings(skip in publish := true) 33 | .dependsOn(core) 34 | .aggregate(core) 35 | -------------------------------------------------------------------------------- /2019/scala/files/day2.txt: -------------------------------------------------------------------------------- 1 | 1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,13,1,19,1,10,19,23,1,23,9,27,1,5,27,31,2,31,13,35,1,35,5,39,1,39,5,43,2,13,43,47,2,47,10,51,1,51,6,55,2,55,9,59,1,59,5,63,1,63,13,67,2,67,6,71,1,71,5,75,1,75,5,79,1,79,9,83,1,10,83,87,1,87,10,91,1,91,9,95,1,10,95,99,1,10,99,103,2,103,10,107,1,107,9,111,2,6,111,115,1,5,115,119,2,119,13,123,1,6,123,127,2,9,127,131,1,131,5,135,1,135,13,139,1,139,10,143,1,2,143,147,1,147,10,0,99,2,0,14,0 2 | -------------------------------------------------------------------------------- /2019/scala/files/day5.txt: -------------------------------------------------------------------------------- 1 | 3,225,1,225,6,6,1100,1,238,225,104,0,1102,72,20,224,1001,224,-1440,224,4,224,102,8,223,223,1001,224,5,224,1,224,223,223,1002,147,33,224,101,-3036,224,224,4,224,102,8,223,223,1001,224,5,224,1,224,223,223,1102,32,90,225,101,65,87,224,101,-85,224,224,4,224,1002,223,8,223,101,4,224,224,1,223,224,223,1102,33,92,225,1102,20,52,225,1101,76,89,225,1,117,122,224,101,-78,224,224,4,224,102,8,223,223,101,1,224,224,1,223,224,223,1102,54,22,225,1102,5,24,225,102,50,84,224,101,-4600,224,224,4,224,1002,223,8,223,101,3,224,224,1,223,224,223,1102,92,64,225,1101,42,83,224,101,-125,224,224,4,224,102,8,223,223,101,5,224,224,1,224,223,223,2,58,195,224,1001,224,-6840,224,4,224,102,8,223,223,101,1,224,224,1,223,224,223,1101,76,48,225,1001,92,65,224,1001,224,-154,224,4,224,1002,223,8,223,101,5,224,224,1,223,224,223,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,1107,677,226,224,1002,223,2,223,1005,224,329,101,1,223,223,7,677,226,224,102,2,223,223,1005,224,344,1001,223,1,223,1107,226,226,224,1002,223,2,223,1006,224,359,1001,223,1,223,8,226,226,224,1002,223,2,223,1006,224,374,101,1,223,223,108,226,226,224,102,2,223,223,1005,224,389,1001,223,1,223,1008,226,226,224,1002,223,2,223,1005,224,404,101,1,223,223,1107,226,677,224,1002,223,2,223,1006,224,419,101,1,223,223,1008,226,677,224,1002,223,2,223,1006,224,434,101,1,223,223,108,677,677,224,1002,223,2,223,1006,224,449,101,1,223,223,1108,677,226,224,102,2,223,223,1006,224,464,1001,223,1,223,107,677,677,224,102,2,223,223,1005,224,479,101,1,223,223,7,226,677,224,1002,223,2,223,1006,224,494,1001,223,1,223,7,677,677,224,102,2,223,223,1006,224,509,101,1,223,223,107,226,677,224,1002,223,2,223,1006,224,524,1001,223,1,223,1007,226,226,224,102,2,223,223,1006,224,539,1001,223,1,223,108,677,226,224,102,2,223,223,1005,224,554,101,1,223,223,1007,677,677,224,102,2,223,223,1006,224,569,101,1,223,223,8,677,226,224,102,2,223,223,1006,224,584,1001,223,1,223,1008,677,677,224,1002,223,2,223,1006,224,599,1001,223,1,223,1007,677,226,224,1002,223,2,223,1005,224,614,101,1,223,223,1108,226,677,224,1002,223,2,223,1005,224,629,101,1,223,223,1108,677,677,224,1002,223,2,223,1005,224,644,1001,223,1,223,8,226,677,224,1002,223,2,223,1006,224,659,101,1,223,223,107,226,226,224,102,2,223,223,1005,224,674,101,1,223,223,4,223,99,226 2 | -------------------------------------------------------------------------------- /2019/scala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.4 2 | -------------------------------------------------------------------------------- /2019/scala/project/metals.sbt: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT! This file is auto-generated. 2 | // This file enables sbt-bloop to create bloop config files. 3 | 4 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.3.5") 5 | -------------------------------------------------------------------------------- /2019/scala/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.4.31") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.3.5") 3 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.10") 4 | -------------------------------------------------------------------------------- /2019/scala/src/main/scala/com/kubukoz/aoc/Util.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc 2 | 3 | import cats.effect.Sync 4 | import cats.effect.ContextShift 5 | import cats.effect.Blocker 6 | import java.nio.file.Paths 7 | 8 | object Util { 9 | 10 | def readFile[F[_]: Sync: ContextShift](name: String): F[String] = 11 | fs2.Stream 12 | .resource(Blocker[F]) 13 | .flatMap(fs2.io.file.readAll(Paths.get(name), _, 4096)) 14 | .through(fs2.text.utf8Decode[F]) 15 | .compile 16 | .string 17 | } 18 | -------------------------------------------------------------------------------- /2019/scala/src/main/scala/com/kubukoz/aoc/day4/Day4.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day4 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | import cats.effect.Console.io._ 5 | import cats.implicits._ 6 | import cats.kernel.Semigroup 7 | 8 | object Day4 extends IOApp { 9 | implicit val boolSemigroup: Semigroup[Boolean] = _ && _ 10 | 11 | object filters { 12 | val digitCount: String => Int = _.length 13 | 14 | val adjacentDigitGroupLengths: String => List[Int] = 15 | fs2.Stream.emits(_).groupAdjacentBy(identity).map(_._2.size).toList 16 | 17 | def hasAdjacentSameDigits(matchCount: Int => Boolean): String => Boolean = 18 | adjacentDigitGroupLengths.map(_.exists(matchCount)) 19 | 20 | val hasDecreasingDigits: String => Boolean = _.toSeq.sliding(2).exists(s => s.last < s.head) 21 | } 22 | 23 | import filters._ 24 | 25 | val part1: String => Boolean = 26 | digitCount.map(_ === 6) |+| 27 | hasAdjacentSameDigits(_ >= 2) |+| 28 | hasDecreasingDigits.map(!_) 29 | 30 | val part2: String => Boolean = 31 | digitCount.map(_ === 6) |+| 32 | hasAdjacentSameDigits(_ == 2) |+| 33 | hasDecreasingDigits.map(!_) 34 | 35 | def run(args: List[String]): IO[ExitCode] = { 36 | val input = 264360 to 746325 map (_.toString) 37 | 38 | putStrLn(input count part1) *> 39 | putStrLn(input count part2) 40 | } as ExitCode.Success 41 | } 42 | -------------------------------------------------------------------------------- /2019/scala/src/main/scala/com/kubukoz/aoc/day6/Day6.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day6 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | import cats.kernel.Semigroup 5 | import io.estatico.newtype.macros.newtype 6 | import cats.implicits._ 7 | import cats.effect.Console.io._ 8 | import com.kubukoz.aoc.Util 9 | import com.kubukoz.aoc.day6.data.{DirectOrbit, Orb, Tree} 10 | 11 | private[day6] object data { 12 | 13 | @newtype 14 | final case class Orb(name: String) 15 | final case class DirectOrbit(orbitted: Orb, orbitter: Orb) 16 | 17 | final case class Tree[T](head: T, children: List[Tree[T]]) { 18 | def leafDistances(n: Int): List[Int] = List(n) ::: children.flatMap(_.leafDistances(n + 1)) 19 | } 20 | } 21 | 22 | object Day6 extends IOApp { 23 | 24 | val input = """COM)B 25 | |B)C 26 | |C)D 27 | |D)E 28 | |E)F 29 | |B)G 30 | |G)H 31 | |D)I 32 | |E)J 33 | |J)K 34 | |K)L 35 | |K)YOU 36 | |I)SAN""".stripMargin 37 | 38 | val parseLine: String => DirectOrbit = _.split("\\)") match { 39 | case Array(a, b) => DirectOrbit(Orb(a), Orb(b)) 40 | } 41 | 42 | def toTree(orbits: List[DirectOrbit]): Tree[Orb] = { 43 | val roots = orbits.map(_.orbitted).toSet 44 | val orbitters = orbits.map(_.orbitter).toSet 45 | 46 | def buildTree(root: Orb): Tree[Orb] = { 47 | val directOrbitters = orbits.to(LazyList).filter(_.orbitted == root).map(_.orbitter) 48 | 49 | Tree(root, directOrbitters.map(buildTree).toList) 50 | } 51 | 52 | val topLevel = roots.find(!orbitters(_)).get 53 | 54 | buildTree(topLevel) 55 | } 56 | 57 | def run(args: List[String]): IO[ExitCode] = 58 | Util.readFile[IO]("files/day6.txt").flatMap { fileInput => 59 | // val orbits = fileInput.linesIterator.map(parseLine).toList 60 | val orbits = input.linesIterator.map(parseLine).toList 61 | val orbitTree = toTree(orbits) 62 | 63 | val orbitCount = orbitTree.leafDistances(0).sum 64 | 65 | putStrLn( 66 | orbitCount 67 | ) 68 | } as ExitCode.Success 69 | } 70 | -------------------------------------------------------------------------------- /2019/scala/src/test/scala/com/kubukoz/Day2Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz 2 | 3 | import org.scalatest.matchers.should.Matchers 4 | import org.scalatest.wordspec.AnyWordSpec 5 | import com.kubukoz.aoc.day2.Day2 6 | import com.kubukoz.aoc.day2.Interpreter 7 | import cats.effect.SyncIO 8 | 9 | class Day2Tests extends AnyWordSpec with Matchers { 10 | "part1" when { 11 | genTests( 12 | "1,9,10,3,2,3,11,0,99,30,40,50" -> 3500, 13 | "1,0,0,0,99" -> 2, 14 | "2,3,0,3,99" -> 2, 15 | "2,4,4,5,99,0" -> 2, 16 | "1,1,1,4,99,5,6,0,99" -> 30 17 | ) 18 | } 19 | 20 | def genTests(examples: (String, Int)*): Unit = examples.foreach { 21 | case (input, out) => 22 | input should { 23 | "be " + out in { 24 | Interpreter 25 | .fromInput[SyncIO](Day2.parse(input)) 26 | .flatMap(_.runProgram) 27 | .map(_ shouldBe out) 28 | .unsafeRunSync() 29 | } 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /2019/scala/src/test/scala/com/kubukoz/aoc/day3/Day3Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day3 2 | 3 | import cats.implicits._ 4 | import org.scalatest.matchers.should.Matchers 5 | import org.scalatest.wordspec.AnyWordSpec 6 | 7 | class Day3Tests extends AnyWordSpec with Matchers { 8 | 9 | "part1" should { 10 | "work with example 1" in { 11 | val input = 12 | Day3.parse("""R75,D30,R83,U83,L12,D49,R71,U7,L72 13 | |U62,R66,U55,R34,D71,R55,D58,R83""".stripMargin) 14 | 15 | Day3.part1(input) shouldBe 159.some 16 | } 17 | 18 | "work with example 2" in { 19 | val input = 20 | Day3.parse("""R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 21 | |U98,R91,D20,R16,D67,R40,U7,R15,U6,R7""".stripMargin) 22 | 23 | Day3.part1(input) shouldBe 135.some 24 | } 25 | } 26 | 27 | "part2" should { 28 | "work with example 1" in { 29 | val input = 30 | Day3.parse("""R75,D30,R83,U83,L12,D49,R71,U7,L72 31 | |U62,R66,U55,R34,D71,R55,D58,R83""".stripMargin) 32 | 33 | Day3.part2(input) shouldBe 610.some 34 | } 35 | 36 | "work with example 2" in { 37 | val input = 38 | Day3.parse("""R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 39 | |U98,R91,D20,R16,D67,R40,U7,R15,U6,R7""".stripMargin) 40 | 41 | Day3.part2(input) shouldBe 410.some 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /2019/scala/src/test/scala/com/kubukoz/aoc/day4/Day4Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day4 2 | 3 | import org.scalatest.matchers.should.Matchers 4 | import org.scalatest.wordspec.AnyWordSpec 5 | 6 | class Day4Tests extends AnyWordSpec with Matchers { 7 | "part1" should { 8 | "return true for 111111" in { 9 | Day4.part1("111111") shouldBe true 10 | } 11 | 12 | "return false for 223450" in { 13 | Day4.part1("223450") shouldBe false 14 | } 15 | 16 | "return false for 123789" in { 17 | Day4.part1("123789") shouldBe false 18 | } 19 | } 20 | 21 | "part2" should { 22 | "return true for 112233" in { 23 | Day4.part2("112233") shouldBe true 24 | } 25 | 26 | "return false for 123444" in { 27 | Day4.part2("123444") shouldBe false 28 | } 29 | 30 | "return true for 111122" in { 31 | Day4.part2("111122") shouldBe true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /2019/scala/src/test/scala/com/kubukoz/aoc/day5/Day5Tests.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day5 2 | 3 | import com.kubukoz.aoc.day5.data.{CommandHeader, Mode, Token} 4 | import org.scalatest.OptionValues 5 | import org.scalatest.matchers.should.Matchers 6 | import org.scalatest.wordspec.AnyWordSpec 7 | 8 | class Day5Tests extends AnyWordSpec with Matchers with OptionValues { 9 | 10 | private val parseCommand: String => CommandHeader = { 11 | CommandHeader.parser 12 | } 13 | "instruction parser" should { 14 | 15 | "decode if some modes are present" in { 16 | parseCommand("1102") shouldBe CommandHeader( 17 | List( 18 | Mode.Immediate, 19 | Mode.Immediate, 20 | Mode.Positional 21 | ), 22 | Token.Mult 23 | ) 24 | } 25 | 26 | "decode if all modes are present (last mode positional)" in { 27 | parseCommand("01102") shouldBe CommandHeader( 28 | List( 29 | Mode.Immediate, 30 | Mode.Immediate, 31 | Mode.Positional 32 | ), 33 | Token.Mult 34 | ) 35 | } 36 | 37 | "decode if all modes are present" in { 38 | parseCommand("10102") shouldBe CommandHeader( 39 | List( 40 | Mode.Immediate, 41 | Mode.Positional, 42 | Mode.Immediate 43 | ), 44 | Token.Mult 45 | ) 46 | } 47 | 48 | "decode if no modes are present" in { 49 | val expected = CommandHeader( 50 | List( 51 | Mode.Positional, 52 | Mode.Positional, 53 | Mode.Positional 54 | ), 55 | Token.Mult 56 | ) 57 | 58 | parseCommand("2") shouldBe expected 59 | parseCommand("02") shouldBe expected 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /2020/README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2020 2 | 3 | My solutions for Advent Of Code in 2020. They'll be written in Scala or Rust. 4 | 5 | Note: By default, this time I'm solving these as fast as I can, so they'll likely be implemented 6 | in almost-functional Scala (side effects like reading the file will be permitted without suspension) in a Metals worksheet. 7 | Please don't judge my sick FP skillz by these. 8 | -------------------------------------------------------------------------------- /2020/scala/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .metals/ 3 | *.class 4 | .idea 5 | .bloop 6 | target/ 7 | **/secret.conf 8 | **/.DS_Store 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /2020/scala/build.sbt: -------------------------------------------------------------------------------- 1 | val compilerPlugins = List( 2 | compilerPlugin("org.typelevel" % "kind-projector" % "0.11.1" cross CrossVersion.full), 3 | compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") 4 | ) 5 | 6 | val commonSettings = Seq( 7 | scalaVersion := "2.13.4", 8 | scalacOptions -= "-Xfatal-warnings", 9 | scalacOptions += "-Ymacro-annotations", 10 | name := "scala", 11 | libraryDependencies ++= compilerPlugins 12 | ) 13 | 14 | val cats = project 15 | .in(file("cats")) 16 | .settings(commonSettings) 17 | .settings( 18 | libraryDependencies ++= Seq( 19 | "co.fs2" %% "fs2-io" % "3.0.0-M3", 20 | "org.typelevel" %% "cats-mtl" % "1.0.0", 21 | "org.typelevel" %% "cats-effect-std" % "3.0.0-M3", 22 | "org.typelevel" %% "cats-effect" % "3.0.0-M3", 23 | "org.typelevel" %% "cats-tagless-macros" % "0.12", 24 | "io.chrisdavenport" %% "semigroups" % "0.2.0", 25 | "io.chrisdavenport" %% "monoids" % "0.2.0", 26 | "io.estatico" %% "newtype" % "0.4.3", 27 | "com.github.julien-truffaut" %% "monocle-macro" % "2.0.0", 28 | "org.typelevel" %% "cats-parse" % "0.2.0", 29 | "org.tpolecat" %% "atto-core" % "0.7.0", 30 | "org.scalatest" %% "scalatest" % "3.2.3" % Test 31 | ) 32 | ) 33 | 34 | val zio = project 35 | .settings(commonSettings) 36 | .settings( 37 | libraryDependencies ++= Seq( 38 | "dev.zio" %% "zio" % "1.0.3", 39 | "dev.zio" %% "zio-streams" % "1.0.3", 40 | "dev.zio" %% "zio-macros" % "1.0.3" 41 | ) 42 | ) 43 | 44 | val root = 45 | project 46 | .in(file(".")) 47 | .settings(commonSettings) 48 | .settings(skip in publish := true) 49 | .aggregate(cats, zio) 50 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/Util.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc 2 | 3 | import cats.effect.Concurrent 4 | import java.nio.file.Paths 5 | import fs2.io.file.Files 6 | import cats.effect.kernel.Ref 7 | import cats.Monad 8 | import cats.mtl.Stateful 9 | import cats.syntax.all._ 10 | import cats.effect.unsafe.IORuntime 11 | import cats.effect.IO 12 | 13 | object Util { 14 | 15 | def readFile[F[_]: Files: Concurrent](name: String): F[List[String]] = 16 | streamFile[F](name).compile.toList 17 | 18 | def streamFile[F[_]: Files: Concurrent](name: String) = 19 | Files[F] 20 | .readAll(Paths.get(name), 4096) 21 | .through(fs2.text.utf8Decode[F]) 22 | .through(fs2.text.lines[F]) 23 | .dropLastIf(_.trim.isEmpty) 24 | 25 | def readFileUnsafe(name: String): List[String] = 26 | readFile[IO](name).unsafeRunSync()(IORuntime.global) 27 | 28 | def state[F[_]: Ref.Make: Monad, A](start: A): F[Stateful[F, A]] = 29 | Ref[F].of(start).map { ref => 30 | new Stateful[F, A] { 31 | def monad: Monad[F] = implicitly 32 | def get: F[A] = ref.get 33 | def set(s: A): F[Unit] = ref.set(s) 34 | override def modify(f: A => A): F[Unit] = ref.update(f) 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day0/Day0.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day0 2 | 3 | import cats.effect.IOApp 4 | import cats.effect.IO 5 | import com.kubukoz.aoc.Util 6 | 7 | object Day0 extends IOApp.Simple { 8 | 9 | val run: IO[Unit] = 10 | Util.readFile[IO]("files/day0.txt").flatMap { file => 11 | IO.println(file) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day1/Day1.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day1 2 | 3 | import cats.effect.IOApp 4 | import cats.effect.IO 5 | import com.kubukoz.aoc.Util 6 | import cats.implicits._ 7 | 8 | object Day1 extends IOApp.Simple { 9 | 10 | def run: IO[Unit] = 11 | Util 12 | .readFile[IO]("files/day1.txt") 13 | .map(_.map(_.trim).filter(_.nonEmpty).map(_.toInt)) 14 | .map { nums => 15 | List(2, 3) 16 | .map { 17 | nums 18 | .combinations(_) 19 | .find(_.sum == 2020) 20 | .map(_.product) 21 | } 22 | } 23 | .flatMap(_.traverse_(IO.println(_))) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day16/day16.worksheet.sc: -------------------------------------------------------------------------------- 1 | import cats.data.State 2 | import com.kubukoz.aoc.Util 3 | import cats.implicits._ 4 | case class Field(label: String, values: Set[Int]) 5 | 6 | val Array(fieldsString, myTicketString, nearbyTicketsString) = Util 7 | .readFileUnsafe("./files/day16.txt") 8 | .mkString("\n") 9 | .split("your ticket:|nearby tickets:") 10 | .map(_.trim) 11 | 12 | val fields = { 13 | val range = """(\d+)\-(\d+)""" 14 | val pattern = s"""(.+): $range or $range""".r 15 | 16 | fieldsString.split("\n").map { case pattern(label, from1, to1, from2, to2) => 17 | Field(label, ((from1.toInt to to1.toInt) ++ (from2.toInt to to2.toInt)).toSet) 18 | } 19 | } 20 | 21 | val myTicket = myTicketString.split(",").map(_.toInt).toVector 22 | 23 | val nearbyTickets = 24 | nearbyTicketsString 25 | .split("\n") 26 | .map(_.split(",").map(_.toInt)) 27 | .map(_.toList) 28 | .toList 29 | 30 | def errors(ticket: List[Int]) = ticket 31 | .view 32 | .filter { field => 33 | !fields.exists(_.values(field)) 34 | } 35 | 36 | //part 1 result 37 | val errorRate = nearbyTickets.foldMap(errors(_).sum) 38 | 39 | val validTickets = nearbyTickets.filter(errors(_).isEmpty).map(_.toVector) 40 | 41 | //for every field, we have the list of potential indices... 42 | val candidates = fields.map { field => 43 | field -> fields.indices.filter { index => 44 | (myTicket :: validTickets).forall(ticket => field.values(ticket(index))) 45 | } 46 | } 47 | 48 | //names of rules and the indices 49 | val actualRules: Map[String, Int] = 50 | candidates 51 | .sortBy(_._2.size) 52 | .toList 53 | .map(_.leftMap(_.label)) 54 | .nested 55 | .traverse { potentialValues => 56 | State { (seen: Set[Int]) => 57 | val notSeen = potentialValues.toSet -- seen 58 | require(notSeen.size == 1) //thank heck! that was one way to make that assumption 59 | 60 | val next = notSeen.head 61 | (seen + next, next) 62 | } 63 | } 64 | .runA(Set.empty) 65 | .value 66 | .value 67 | .toMap 68 | 69 | //this is the actual part 2 result lol 70 | actualRules 71 | .collect { case (k, v) if k.startsWith("departure") => v } 72 | .map(myTicket(_).toLong) 73 | .product 74 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day2/fast.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | 4 | val pat = """(\d+)\-(\d+) (\w)\: (\w+)""".r 5 | 6 | case class Password(from: Int, to: Int, letter: Char, value: String) { 7 | 8 | def isValid: Boolean = 9 | (from to to) contains value.count(_ == letter) 10 | 11 | def isValid2: Boolean = { 12 | val indexed = value.zipWithIndex.map(_.fmap(_ + 1)).map(_.swap).toMap 13 | 14 | List(from, to).count(indexed(_) == letter) == 1 15 | } 16 | 17 | } 18 | 19 | val passwords = Util.readFileUnsafe("./files/day2.txt").map { 20 | case pat(fromDay, toDay, letter, password) => Password(fromDay.toInt, toDay.toInt, letter.head, password) 21 | } 22 | 23 | passwords.count(_.isValid) 24 | passwords.count(_.isValid2) 25 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day3/day3.fast.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | 4 | val baseAdvance = 3 -> 1 5 | val allAdvances = List(1 -> 1, baseAdvance, 5 -> 1, 7 -> 1, 1 -> 2) 6 | 7 | case class Position(x: Int, y: Int) { 8 | def advance(diff: (Int, Int)) = Position(x + diff._1, y + diff._2) 9 | } 10 | 11 | case class Line(baseFields: List[Boolean]) { 12 | def fieldAt(x: Int) = baseFields(x % baseFields.length) 13 | } 14 | 15 | val lineMap = Util.readFileUnsafe("./files/day3.txt").map(_.toList.map(_ == '#')).map(Line(_)).zipWithIndex.map(_.swap).toMap 16 | 17 | def toField(position: Position): Option[Boolean] = lineMap.get(position.y).map(_.fieldAt(position.x)) 18 | 19 | def go(diff: (Int, Int)) = fs2.Stream.iterate(Position(0, 0))(_.advance(diff)).map(toField).unNoneTerminate.toList.count(identity).toLong 20 | 21 | go(baseAdvance) 22 | 23 | allAdvances.map(go).product 24 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day4/day4.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | 4 | def parsePassport(raw: String): Map[String, String] = raw 5 | .split("""(\s+)""") 6 | .map(_.split(":").toList match { 7 | case a :: b :: Nil => (a, b) 8 | case idontcare => throw new Exception(idontcare.toString) 9 | }) 10 | .toMap 11 | 12 | val requiredFields = Map[String, String => Boolean]( 13 | "byr" -> { _.toIntOption.exists((1920 to 2002).contains) }, 14 | "iyr" -> { _.toIntOption.exists((2010 to 2020).contains) }, 15 | "eyr" -> { _.toIntOption.exists((2020 to 2030).contains) }, 16 | "hgt" -> { 17 | case s"${height}cm" => height.toIntOption.exists((150 to 193).contains) 18 | case s"${height}in" => height.toIntOption.exists((59 to 76).contains) 19 | case _ => false 20 | }, 21 | "hcl" -> { s => 22 | s.startsWith("#") && s.length == 7 && s.tail.forall((('a' to 'f') ++ ('0' to '9')).toSet) 23 | }, 24 | "ecl" -> "amb blu brn gry grn hzl oth".split(" ").toSet, 25 | "pid" -> { s => s.length == 9 && s.forall(_.isDigit) } 26 | ) 27 | 28 | def isValid1(passport: Map[String, String]) = 29 | requiredFields.keySet.forall(passport.contains) 30 | 31 | def isValid2(passport: Map[String, String]) = 32 | requiredFields.forall { case (key, condition) => passport.get(key).exists(condition) } 33 | 34 | val input = Util.readFileUnsafe("./files/day4.txt").mkString("\n").split("\n\n").map(parsePassport).count(isValid2) 35 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day5/day5.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | 4 | def bin(s: String): Int = Integer.valueOf(s, 2) 5 | 6 | def parse(raw: String) = 7 | raw 8 | .map(Map('B' -> '1', 'R' -> '1').getOrElse(_, '0')) 9 | .splitAt(7) 10 | .bimap(bin, bin) match { case (row, column) => row * 8 + column } 11 | 12 | val allIds = Util.readFileUnsafe("./files/day5.txt").map(parse) 13 | 14 | val part1 = allIds.max 15 | val part2 = allIds.find(n => allIds.contains(n + 2) && !allIds.contains(n + 1)).map(_ + 1) 16 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day6/day6.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | 4 | val input = Util.readFileUnsafe( 5 | "./files/day6.txt" 6 | ) 7 | 8 | val groups = input.mkString("\n").split("\n\n").map(_.split("\n").toList).toList 9 | 10 | val questions = 'a' to 'z' 11 | 12 | def matches(group: List[String]): Int = questions.count(q => group.exists(_.contains(q))) 13 | def matches2(group: List[String]): Int = questions.count(q => group.forall(_.contains(q))) 14 | 15 | groups.foldMap(matches) 16 | groups.foldMap(matches2) 17 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day7/day7.worksheet.sc: -------------------------------------------------------------------------------- 1 | import cats.data.NonEmptyList 2 | import com.kubukoz.aoc._ 3 | import cats.implicits._ 4 | 5 | case class Tree[A](value: A, children: List[Tree[A]]) { 6 | 7 | def pathToMatch(matcher: Tree[A] => Boolean): List[NonEmptyList[Tree[A]]] = 8 | if (matcher(this)) List(NonEmptyList.one(this)) 9 | else 10 | children 11 | .toNel 12 | .toList 13 | .flatMap(_.toList.flatMap(_.pathToMatch(matcher)).map(this :: _)) 14 | 15 | def find(pred: A => Boolean): Option[Tree[A]] = 16 | if (pred(value)) this.some 17 | else children.view.flatMap(_.find(pred)).headOption 18 | 19 | def fold[B](f: (A, List[B]) => B): B = 20 | f(value, children.map(_.fold(f))) 21 | 22 | } 23 | 24 | case class ChildReference(capacity: Int, kind: String) 25 | 26 | def stripS(bags: String) = bags match { 27 | case s"$kind bags" => kind 28 | case s"$kind bag" => kind 29 | } 30 | 31 | def parseValue(value: String): List[ChildReference] = { 32 | val pat = """(\d+) (.+)""".r 33 | 34 | value match { 35 | case "no other bags" => Nil 36 | case str => 37 | str 38 | .split(",") 39 | .map(_.trim) 40 | .map { case pat(n, bags) => 41 | ChildReference(n.toInt, stripS(bags)) 42 | } 43 | .toList 44 | } 45 | } 46 | 47 | val input: Map[String, List[ChildReference]] = { 48 | val pat = """(.+) contain (.+)\.""".r 49 | 50 | Util 51 | .readFileUnsafe( 52 | "./files/day7.txt" 53 | ) 54 | .map { case pat(parent, children) => 55 | stripS(parent) -> parseValue(children) 56 | } 57 | .toMap 58 | } 59 | 60 | val trees: List[Tree[ChildReference]] = { 61 | //all input keys that don't appear in values 62 | val topLevel = input.keySet -- input.values.flatten.map(_.kind) 63 | 64 | def buildNode(parent: ChildReference): Tree[ChildReference] = { 65 | val directChildren = input.getOrElse(parent.kind, Nil) 66 | 67 | Tree(parent, directChildren.map(buildNode)) 68 | } 69 | 70 | topLevel.toList.map(ChildReference(0, _)).map(buildNode) 71 | } 72 | 73 | val part1 = { 74 | val possiblePathsToMe = trees.flatMap(_.pathToMatch(_.value.kind == "shiny gold")) 75 | 76 | possiblePathsToMe 77 | .flatMap(_.init) 78 | .map(_.value.kind) 79 | .distinct 80 | .size 81 | } 82 | 83 | val part2 = { 84 | val me = trees.flatMap(_.find(_.kind === "shiny gold")).head 85 | 86 | me.children.foldMap { 87 | _.fold[Int] { (child, itsChildren) => 88 | child.capacity * (itsChildren.sum + 1) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /2020/scala/cats/src/main/scala/com/kubukoz/aoc/day9/day9.worksheet.sc: -------------------------------------------------------------------------------- 1 | import com.kubukoz.aoc._ 2 | import cats.implicits._ 3 | import cats.data._ 4 | 5 | val input = Util.readFileUnsafe("./files/day9.txt").map(_.toLong: BigDecimal) 6 | 7 | def valid[A: Numeric](line: List[A]) = { 8 | val last = line.last 9 | line.init.combinations(2).exists(_.sum == last) 10 | } 11 | 12 | val preambleSize = 25 13 | 14 | val part1 = input.sliding(preambleSize + 1).filterNot(valid).map(_.last).toList.head 15 | 16 | def part2 = 17 | (2 to input.size) 18 | .flatMap(input.sliding(_)) 19 | .find(_.sum == part1) 20 | .map(r => r.min + r.max) 21 | -------------------------------------------------------------------------------- /2020/scala/files/day0.txt: -------------------------------------------------------------------------------- 1 | hello 2 | world 3 | -------------------------------------------------------------------------------- /2020/scala/files/day1.txt: -------------------------------------------------------------------------------- 1 | 1227 2 | 1065 3 | 329 4 | 1063 5 | 1889 6 | 1700 7 | 1805 8 | 1373 9 | 389 10 | 1263 11 | 1276 12 | 1136 13 | 1652 14 | 1981 15 | 1406 16 | 1249 17 | 1197 18 | 1379 19 | 1050 20 | 1791 21 | 1703 22 | 2001 23 | 1842 24 | 1707 25 | 1486 26 | 1204 27 | 1821 28 | 1807 29 | 1712 30 | 1871 31 | 1599 32 | 1390 33 | 1219 34 | 1612 35 | 1980 36 | 1857 37 | 1511 38 | 1702 39 | 1455 40 | 1303 41 | 1052 42 | 1754 43 | 1545 44 | 1488 45 | 1848 46 | 1236 47 | 1549 48 | 1887 49 | 1970 50 | 1123 51 | 1686 52 | 1404 53 | 1688 54 | 1106 55 | 1296 56 | 401 57 | 1829 58 | 1693 59 | 1389 60 | 1957 61 | 914 62 | 1176 63 | 1348 64 | 1275 65 | 1624 66 | 1401 67 | 1045 68 | 1396 69 | 1352 70 | 1569 71 | 1060 72 | 1235 73 | 1679 74 | 1503 75 | 1340 76 | 1872 77 | 1410 78 | 1077 79 | 958 80 | 1681 81 | 1189 82 | 1466 83 | 1087 84 | 1852 85 | 1293 86 | 1139 87 | 1300 88 | 1323 89 | 661 90 | 1388 91 | 1983 92 | 1325 93 | 1112 94 | 1774 95 | 1858 96 | 1785 97 | 1616 98 | 1255 99 | 1198 100 | 1354 101 | 1124 102 | 1834 103 | 1417 104 | 1918 105 | 1496 106 | 33 107 | 1150 108 | 1861 109 | 1172 110 | 2006 111 | 1199 112 | 1558 113 | 1919 114 | 1620 115 | 1613 116 | 1710 117 | 1477 118 | 1592 119 | 1709 120 | 1909 121 | 1670 122 | 1922 123 | 1840 124 | 1768 125 | 1982 126 | 1193 127 | 1736 128 | 1877 129 | 1770 130 | 1191 131 | 1433 132 | 1072 133 | 1148 134 | 1225 135 | 1147 136 | 1171 137 | 1424 138 | 1913 139 | 1228 140 | 1339 141 | 1814 142 | 1504 143 | 1251 144 | 1240 145 | 1272 146 | 1500 147 | 1927 148 | 1428 149 | 1641 150 | 1453 151 | 1729 152 | 1976 153 | 1808 154 | 1180 155 | 1024 156 | 1108 157 | 1085 158 | 1669 159 | 1636 160 | 1005 161 | 1520 162 | 1929 163 | 1626 164 | 1551 165 | 1234 166 | 1988 167 | 1256 168 | 1524 169 | 1571 170 | 1506 171 | 1977 172 | 1749 173 | 1408 174 | 1540 175 | 1934 176 | 1810 177 | 1328 178 | 1910 179 | 1478 180 | 1600 181 | 1699 182 | 1413 183 | 1446 184 | 1798 185 | 1013 186 | 1998 187 | 1661 188 | 1058 189 | 1051 190 | 1220 191 | 1447 192 | 1675 193 | 1912 194 | 1668 195 | 1932 196 | 1962 197 | 1055 198 | 1757 199 | 1116 200 | 1090 201 | -------------------------------------------------------------------------------- /2020/scala/files/day10.txt: -------------------------------------------------------------------------------- 1 | 133 2 | 157 3 | 39 4 | 74 5 | 108 6 | 136 7 | 92 8 | 55 9 | 86 10 | 46 11 | 111 12 | 58 13 | 80 14 | 115 15 | 84 16 | 67 17 | 98 18 | 30 19 | 40 20 | 61 21 | 71 22 | 114 23 | 17 24 | 9 25 | 123 26 | 142 27 | 49 28 | 158 29 | 107 30 | 139 31 | 104 32 | 132 33 | 155 34 | 96 35 | 91 36 | 15 37 | 11 38 | 23 39 | 54 40 | 6 41 | 63 42 | 126 43 | 3 44 | 10 45 | 116 46 | 87 47 | 68 48 | 72 49 | 109 50 | 62 51 | 134 52 | 103 53 | 1 54 | 16 55 | 101 56 | 117 57 | 35 58 | 120 59 | 151 60 | 102 61 | 85 62 | 145 63 | 135 64 | 79 65 | 2 66 | 147 67 | 33 68 | 41 69 | 93 70 | 52 71 | 48 72 | 64 73 | 81 74 | 29 75 | 20 76 | 110 77 | 129 78 | 43 79 | 148 80 | 36 81 | 53 82 | 26 83 | 42 84 | 156 85 | 154 86 | 77 87 | 88 88 | 73 89 | 27 90 | 34 91 | 12 92 | 146 93 | 78 94 | 47 95 | 28 96 | 97 97 | -------------------------------------------------------------------------------- /2020/scala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.4 2 | -------------------------------------------------------------------------------- /2020/scala/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.15") 2 | -------------------------------------------------------------------------------- /2020/scala/zio/src/main/scala/com/kubukoz/aoc/ZUtil.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc 2 | 3 | import zio.stream.Stream 4 | import java.nio.file.Paths 5 | import zio.stream.ZTransducer 6 | import zio.blocking.Blocking 7 | import zio.IO 8 | import zio.ZLayer 9 | import zio.macros.accessible 10 | 11 | @accessible 12 | object ZUtil { 13 | 14 | trait Service { 15 | def readFile(name: String): IO[Throwable, List[String]] 16 | } 17 | 18 | val layer: ZLayer[Blocking, Nothing, ZUtil] = ZLayer.fromFunction[Blocking, Service] { blocking => 19 | new Service { 20 | def readFile(name: String): zio.IO[Throwable, List[String]] = 21 | Stream 22 | .fromFile(Paths.get(name)) 23 | .transduce(ZTransducer.utf8Decode) 24 | .runCollect 25 | .map(_.toList) 26 | .provide(blocking) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /2020/scala/zio/src/main/scala/com/kubukoz/aoc/ZUtilApp.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc 2 | 3 | import zio.ExitCode 4 | import zio.ZIO 5 | 6 | abstract class ZUtilApp extends zio.App { 7 | def run(args: List[String]): zio.URIO[zio.ZEnv, ExitCode] = runProg.provideCustomLayer(ZUtil.layer).exitCode 8 | 9 | def runProg: ZIO[ZUtil with zio.ZEnv, Any, Any] 10 | } 11 | -------------------------------------------------------------------------------- /2020/scala/zio/src/main/scala/com/kubukoz/aoc/day0/Day0ZIO.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz.aoc.day0 2 | 3 | import com.kubukoz.aoc.ZUtil 4 | import com.kubukoz.aoc.ZUtilApp 5 | import zio.ZIO 6 | import zio.console._ 7 | 8 | object Day0ZIO extends ZUtilApp { 9 | 10 | def runProg: ZIO[ZUtil with zio.ZEnv, Any, Any] = 11 | ZUtil 12 | .readFile("files/day0.txt") 13 | .flatMap(ZIO.foreach(_)(putStrLn(_))) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /2020/scala/zio/src/main/scala/com/kubukoz/aoc/package.scala: -------------------------------------------------------------------------------- 1 | package com.kubukoz 2 | 3 | import zio.Has 4 | 5 | package object aoc { 6 | type ZUtil = Has[ZUtil.Service] 7 | } 8 | -------------------------------------------------------------------------------- /2021/.gitattributes: -------------------------------------------------------------------------------- 1 | **/resources/day*.txt filter=git-crypt diff=git-crypt 2 | -------------------------------------------------------------------------------- /2021/.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .unisonHistory 3 | **/target 4 | result 5 | .DS_Store 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /2021/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | runner.dialect = scala213 2 | version = 3.2.0 3 | maxColumn = 100 4 | align.preset = some 5 | 6 | newlines.beforeMultiline = unfold 7 | newlines.topLevelStatements = [before, after] 8 | newlines.topLevelStatementsMinBreaks = 2 9 | newlines.implicitParamListModifierForce = [before] 10 | continuationIndent.defnSite = 2 11 | continuationIndent.extendSite = 2 12 | optIn.breakChainOnFirstMethodDot = true 13 | includeCurlyBraceInSelectChains = true 14 | includeNoParensInSelectChains = true 15 | 16 | trailingCommas = "multiple" 17 | 18 | rewrite.rules = [ 19 | RedundantBraces, 20 | RedundantParens, 21 | ExpandImportSelectors, 22 | PreferCurlyFors 23 | ] 24 | 25 | runner.optimizer.forceConfigStyleMinArgCount = 3 26 | danglingParentheses.defnSite = true 27 | danglingParentheses.callSite = true 28 | danglingParentheses.exclude = [ 29 | "`trait`" 30 | ] 31 | verticalMultiline.newlineAfterOpenParen = true 32 | verticalMultiline.atDefnSite = true 33 | -------------------------------------------------------------------------------- /2021/build.sbt: -------------------------------------------------------------------------------- 1 | val root = project 2 | .in(file(".")) 3 | .settings( 4 | libraryDependencies ++= Seq( 5 | "dev.optics" %% "monocle-macro" % "3.1.0", 6 | "org.typelevel" %% "cats-core" % "2.7.0", 7 | "org.typelevel" %% "cats-mtl" % "1.2.1", 8 | "co.fs2" %% "fs2-core" % "3.2.4", 9 | "org.typelevel" %% "cats-testkit-scalatest" % "2.1.5" % Test, 10 | compilerPlugin(("org.polyvariant" % "better-tostring" % "0.3.11").cross(CrossVersion.full)), 11 | compilerPlugin(("org.typelevel" % "kind-projector" % "0.13.2").cross(CrossVersion.full)), 12 | ), 13 | scalaVersion := "2.13.7", 14 | scalacOptions ++= Seq( 15 | "-Ywarn-unused:imports", 16 | "-Xsource:3.0", 17 | ), 18 | ) 19 | -------------------------------------------------------------------------------- /2021/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /2021/src/main/resources/day10-example.txt: -------------------------------------------------------------------------------- 1 | [({(<(())[]>[[{[]{<()<>> 2 | [(()[<>])]({[<{<<[]>>( 3 | {([(<{}[<>[]}>{[]{[(<()> 4 | (((({<>}<{<{<>}{[]{[]{} 5 | [[<[([]))<([[{}[[()]]] 6 | [{[{({}]{}}([{[{{{}}([] 7 | {<[[]]>}<{[{[{[]{()[[[] 8 | [<(<(<(<{}))><([]([]() 9 | <{([([[(<>()){}]>(<<{{ 10 | <{([{{}}[<[[[<>{}]]]>[]] 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day11-example.txt: -------------------------------------------------------------------------------- 1 | 5483143223 2 | 2745854711 3 | 5264556173 4 | 6141336146 5 | 6357385478 6 | 4167524645 7 | 2176841721 8 | 6882881134 9 | 4846848554 10 | 5283751526 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day11.txt: -------------------------------------------------------------------------------- 1 | 8624818384 2 | 3725473343 3 | 6618341827 4 | 4573826616 5 | 8357322142 6 | 6846358317 7 | 7286886112 8 | 8138685117 9 | 6161124267 10 | 3848415383 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day12-example.txt: -------------------------------------------------------------------------------- 1 | start-A 2 | start-b 3 | A-c 4 | A-b 5 | b-d 6 | A-end 7 | b-end 8 | -------------------------------------------------------------------------------- /2021/src/main/resources/day12.txt: -------------------------------------------------------------------------------- 1 | LP-cb 2 | PK-yk 3 | bf-end 4 | PK-my 5 | end-cb 6 | BN-yk 7 | cd-yk 8 | cb-lj 9 | yk-bf 10 | bf-lj 11 | BN-bf 12 | PK-cb 13 | end-BN 14 | my-start 15 | LP-yk 16 | PK-bf 17 | my-BN 18 | start-PK 19 | yk-EP 20 | lj-BN 21 | lj-start 22 | my-lj 23 | bf-LP 24 | -------------------------------------------------------------------------------- /2021/src/main/resources/day13-example.txt: -------------------------------------------------------------------------------- 1 | 6,10 2 | 0,14 3 | 9,10 4 | 0,3 5 | 10,4 6 | 4,11 7 | 6,0 8 | 6,12 9 | 4,1 10 | 0,13 11 | 10,12 12 | 3,4 13 | 3,0 14 | 8,4 15 | 1,10 16 | 2,14 17 | 8,10 18 | 9,0 19 | 20 | fold along y=7 21 | fold along x=5 22 | -------------------------------------------------------------------------------- /2021/src/main/resources/day14-example.txt: -------------------------------------------------------------------------------- 1 | NNCB 2 | 3 | CH -> B 4 | HH -> N 5 | CB -> H 6 | NH -> C 7 | HB -> C 8 | HC -> B 9 | HN -> C 10 | NN -> C 11 | BH -> H 12 | NC -> B 13 | NB -> B 14 | BN -> B 15 | BB -> N 16 | BC -> B 17 | CC -> N 18 | CN -> C 19 | -------------------------------------------------------------------------------- /2021/src/main/resources/day14.txt: -------------------------------------------------------------------------------- 1 | CPSSSFCFOFVFNVPKBFVN 2 | 3 | NV -> V 4 | CF -> O 5 | BB -> F 6 | SB -> H 7 | KF -> O 8 | SP -> H 9 | CS -> V 10 | VF -> F 11 | PC -> H 12 | PH -> H 13 | SF -> F 14 | CP -> B 15 | BC -> H 16 | PB -> V 17 | KO -> B 18 | CV -> S 19 | ON -> B 20 | PV -> F 21 | OO -> B 22 | VV -> B 23 | NO -> B 24 | SH -> N 25 | FC -> C 26 | VO -> B 27 | NN -> C 28 | HH -> S 29 | CK -> C 30 | PF -> N 31 | SN -> K 32 | OK -> F 33 | FH -> S 34 | BP -> K 35 | HO -> K 36 | FB -> P 37 | HC -> N 38 | FP -> P 39 | NC -> H 40 | PK -> O 41 | BV -> P 42 | HK -> S 43 | PP -> N 44 | VC -> K 45 | CH -> C 46 | KS -> V 47 | KB -> V 48 | FN -> P 49 | BS -> O 50 | PS -> N 51 | NS -> B 52 | PN -> N 53 | NP -> H 54 | CB -> S 55 | SV -> O 56 | OC -> H 57 | BO -> C 58 | HN -> N 59 | HP -> N 60 | OF -> H 61 | FS -> S 62 | KV -> P 63 | HV -> S 64 | VS -> P 65 | BH -> N 66 | CC -> V 67 | VN -> H 68 | NF -> B 69 | NK -> N 70 | CN -> F 71 | FV -> P 72 | NH -> S 73 | OV -> H 74 | KN -> F 75 | SO -> H 76 | OP -> N 77 | KC -> P 78 | HB -> B 79 | BN -> V 80 | VP -> N 81 | HS -> S 82 | VK -> C 83 | VH -> H 84 | OS -> C 85 | FO -> B 86 | NB -> P 87 | KP -> V 88 | SS -> O 89 | BK -> F 90 | SK -> N 91 | HF -> O 92 | PO -> F 93 | OH -> B 94 | KK -> O 95 | FK -> S 96 | VB -> V 97 | OB -> C 98 | KH -> H 99 | SC -> F 100 | FF -> H 101 | CO -> V 102 | BF -> H 103 | -------------------------------------------------------------------------------- /2021/src/main/resources/day15-example.txt: -------------------------------------------------------------------------------- 1 | 1163751742 2 | 1381373672 3 | 2136511328 4 | 3694931569 5 | 7463417111 6 | 1319128137 7 | 1359912421 8 | 3125421639 9 | 1293138521 10 | 2311944581 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day16.txt: -------------------------------------------------------------------------------- 1 | 420D4900B8F31EFE7BD9DA455401AB80021504A2745E1007A21C1C862801F54AD0765BE833D8B9F4CE8564B9BE6C5CC011E00D5C001098F11A232080391521E4799FC5BB3EE1A8C010A00AE256F4963B33391DEE57DA748F5DCC011D00461A4FDC823C900659387DA00A49F5226A54EC378615002A47B364921C201236803349B856119B34C76BD8FB50B6C266EACE400424883880513B62687F38A13BCBEF127782A600B7002A923D4F959A0C94F740A969D0B4C016D00540010B8B70E226080331961C411950F3004F001579BA884DD45A59B40005D8362011C7198C4D0A4B8F73F3348AE40183CC7C86C017997F9BC6A35C220001BD367D08080287914B984D9A46932699675006A702E4E3BCF9EA5EE32600ACBEADC1CD00466446644A6FBC82F9002B734331D261F08020192459B24937D9664200B427963801A094A41CE529075200D5F4013988529EF82CEFED3699F469C8717E6675466007FE67BE815C9E84E2F300257224B256139A9E73637700B6334C63719E71D689B5F91F7BFF9F6EE33D5D72BE210013BCC01882111E31980391423FC4920042E39C7282E4028480021111E1BC6310066374638B200085C2C8DB05540119D229323700924BE0F3F1B527D89E4DB14AD253BFC30C01391F815002A539BA9C4BADB80152692A012CDCF20F35FDF635A9CCC71F261A080356B00565674FBE4ACE9F7C95EC19080371A009025B59BE05E5B59BE04E69322310020724FD3832401D14B4A34D1FE80233578CD224B9181F4C729E97508C017E005F2569D1D92D894BFE76FAC4C5FDDBA990097B2FBF704B40111006A1FC43898200E419859079C00C7003900B8D1002100A49700340090A40216CC00F1002900688201775400A3002C8040B50035802CC60087CC00E1002A4F35815900903285B401AA880391E61144C0004363445583A200CC2C939D3D1A41C66EC40 2 | -------------------------------------------------------------------------------- /2021/src/main/resources/day17-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day17-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day17.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day17.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day18-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day18-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day18.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day18.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day19-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day19-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day19.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day19.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day20-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day20-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day20.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day20.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day21-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day21-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day21.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day21.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day22-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day22-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day22.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day22.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day23-example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day23-example.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day23.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubukoz/advent-of-code/a563a2db9d485e2cd4c117fa5d4afc1dc334739b/2021/src/main/resources/day23.txt -------------------------------------------------------------------------------- /2021/src/main/resources/day4-example.txt: -------------------------------------------------------------------------------- 1 | 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 2 | 3 | 22 13 17 11 0 4 | 8 2 23 4 24 5 | 21 9 14 16 7 6 | 6 10 3 18 5 7 | 1 12 20 15 19 8 | 9 | 3 15 0 2 22 10 | 9 18 13 17 5 11 | 19 8 7 25 23 12 | 20 11 10 24 4 13 | 14 21 16 12 6 14 | 15 | 14 21 17 24 4 16 | 10 16 15 9 19 17 | 18 8 23 26 20 18 | 22 11 13 6 5 19 | 2 0 12 3 7 20 | -------------------------------------------------------------------------------- /2021/src/main/resources/day5-example.txt: -------------------------------------------------------------------------------- 1 | 0,9 -> 5,9 2 | 8,0 -> 0,8 3 | 9,4 -> 3,4 4 | 2,2 -> 2,1 5 | 7,0 -> 7,4 6 | 6,4 -> 2,0 7 | 0,9 -> 2,9 8 | 3,4 -> 1,4 9 | 0,0 -> 8,8 10 | 5,5 -> 8,2 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day6-example.txt: -------------------------------------------------------------------------------- 1 | 3,4,3,1,2 2 | -------------------------------------------------------------------------------- /2021/src/main/resources/day6.txt: -------------------------------------------------------------------------------- 1 | 3,3,5,1,1,3,4,2,3,4,3,1,1,3,3,1,5,4,4,1,4,1,1,1,3,3,2,3,3,4,2,5,1,4,1,2,2,4,2,5,1,2,2,1,1,1,1,4,5,4,3,1,4,4,4,5,1,1,4,3,4,2,1,1,1,1,5,2,1,4,2,4,2,5,5,5,3,3,5,4,5,1,1,5,5,5,2,1,3,1,1,2,2,2,2,1,1,2,1,5,1,2,1,2,5,5,2,1,1,4,2,1,4,2,1,1,1,4,2,5,1,5,1,1,3,1,4,3,1,3,2,1,3,1,4,1,2,1,5,1,2,1,4,4,1,3,1,1,1,1,1,5,2,1,5,5,5,3,3,1,2,4,3,2,2,2,2,2,4,3,4,4,4,1,2,2,3,1,1,4,1,1,1,2,1,4,2,1,2,1,1,2,1,5,1,1,3,1,4,3,2,1,1,1,5,4,1,2,5,2,2,1,1,1,1,2,3,3,2,5,1,2,1,2,3,4,3,2,1,1,2,4,3,3,1,1,2,5,1,3,3,4,2,3,1,2,1,4,3,2,2,1,1,2,1,4,2,4,1,4,1,4,4,1,4,4,5,4,1,1,1,3,1,1,1,4,3,5,1,1,1,3,4,1,1,4,3,1,4,1,1,5,1,2,2,5,5,2,1,5 2 | 3 | -------------------------------------------------------------------------------- /2021/src/main/resources/day7-example.txt: -------------------------------------------------------------------------------- 1 | 16,1,2,0,4,2,7,1,2,14 2 | -------------------------------------------------------------------------------- /2021/src/main/resources/day8-example.txt: -------------------------------------------------------------------------------- 1 | be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe 2 | edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc 3 | fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg 4 | fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb 5 | aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea 6 | fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb 7 | dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe 8 | bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef 9 | egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb 10 | gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce 11 | -------------------------------------------------------------------------------- /2021/src/main/resources/day9-example.txt: -------------------------------------------------------------------------------- 1 | 2199943210 2 | 3987894921 3 | 9856789892 4 | 8767896789 5 | 9899965678 6 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day1.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | object Day1 extends App { 4 | 5 | val input = lib.readAllLines("day1.txt") 6 | val values = input.filterNot(_.trim.isEmpty).map(_.toInt) 7 | 8 | def result(data: List[Int]) = data.zip(data.tail).count { case (before, after) => after > before } 9 | 10 | println("Part 1: " + result(values)) 11 | println("Part 2: " + result(values.sliding(3).map(_.sum).toList)) 12 | } 13 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day11.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import scala.annotation.tailrec 4 | 5 | import cats.implicits._ 6 | import aoc.lib._ 7 | 8 | object Day11 extends App { 9 | type Data = Map[(Int, Int), Int] 10 | type Flashed = Set[(Int, Int)] 11 | 12 | def step(data: Data): (Data, Flashed) = { 13 | def siblingPositions(x: Int, y: Int): Set[(Int, Int)] = 14 | ((-1 to 1).toList, (-1 to 1).toList).mapN((dx, dy) => (x + dx, y + dy)).toSet - ((x, y)) 15 | 16 | @tailrec 17 | def go(data: Data, alreadyFlashed: Flashed): (Data, Flashed) = { 18 | val flashingNow = (data -- alreadyFlashed).filter { case (_, v) => v > 9 }.keySet 19 | 20 | if (flashingNow.isEmpty) 21 | (data, alreadyFlashed) 22 | else { 23 | val siblingUpdates = flashingNow.toList.foldMap { case (x, y) => 24 | siblingPositions(x, y) 25 | .filter(data.contains) 26 | .map(pos => (pos, 1)) 27 | .toMap 28 | } 29 | 30 | go(data |+| siblingUpdates, alreadyFlashed ++ flashingNow) 31 | } 32 | } 33 | 34 | val (newData, newFlashed) = go(data.view.mapValues(_ + 1).toMap, Set.empty) 35 | 36 | (newData ++ newFlashed.map(pos => (pos, 0)), newFlashed) 37 | } 38 | 39 | def solve(data: Data) = 40 | LazyList.iterate((data, Set.empty: Flashed)) { case (d, c) => step(d) }.tail 41 | 42 | def part1(data: Data): Int = solve(data).take(100).foldMap(_._2.size) 43 | 44 | def part2(data: Data) = solve(data).zipWithIndex.collectFirst { 45 | case ((d, flashed), i) if flashed.size == data.size => i + 1 46 | } 47 | 48 | val parse: List[String] => Data = 49 | _.map(_.toList) 50 | .zipWithIndex 51 | .flatMap { case (line, y) => 52 | line.zipWithIndex.map { case (ch, x) => ((x, y) -> ch.toString.toInt) } 53 | } 54 | .toMap 55 | 56 | val example = parse(readAllLines("day11-example.txt")) 57 | assertEquals(part1(example), 1656, "Part 1 (example)") 58 | assertEquals(part2(example), 195.some, "Part 2 (example)") 59 | 60 | val fromFile = parse(readAllLines("day11.txt")) 61 | assertEquals(part1(fromFile), 1667, "Part 1") 62 | assertEquals(part2(fromFile), 488.some, "Part 2") 63 | } 64 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day12.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import cats.data.NonEmptyList 4 | 5 | import aoc.lib._ 6 | import cats.implicits._ 7 | import scala.util.chaining._ 8 | 9 | object Day12 extends App { 10 | 11 | case class Edge(from: String, to: String) 12 | 13 | val isEnd: String => Boolean = _ == "end" 14 | val isSmall: String => Boolean = _.matches("[a-z]+") 15 | 16 | def countPaths(edges: Set[Edge], allowSmallDuplicate: Boolean, allowStartEnd: Boolean): Int = { 17 | 18 | val getEdgesFrom: String => Set[String] = { 19 | val edgesByFrom = edges.groupBy(_.from).map(_.map(_.map(_.to))) 20 | val edgesByTo = edges.groupBy(_.to).map(_.map(_.map(_.from))) 21 | 22 | (edgesByFrom |+| edgesByTo).withDefaultValue(Set.empty) 23 | } 24 | def dedupe(history: NonEmptyList[String]): Set[String] => Set[String] = { 25 | val smallHistory = history.filter(isSmall) 26 | def hasSmallDuplicate = smallHistory.groupBy(identity).exists(_._2.size > 1) 27 | 28 | if (!allowSmallDuplicate || hasSmallDuplicate) 29 | _ -- smallHistory.filter(isSmall) 30 | else 31 | identity 32 | } 33 | 34 | val skipStart: Set[String] => Set[String] = 35 | if (allowStartEnd) 36 | identity 37 | else 38 | _ - "start" 39 | 40 | def go(history: NonEmptyList[String]): NonEmptyList[NonEmptyList[String]] = 41 | if (!allowStartEnd && history.head == "end") 42 | NonEmptyList.one(history) 43 | else { 44 | val possibleNext = getEdgesFrom 45 | .andThen(dedupe(history)) 46 | .andThen(skipStart) 47 | .apply(history.head) 48 | 49 | NonEmptyList( 50 | history, 51 | possibleNext.toList.flatMap { next => 52 | go(next :: history).toList 53 | }, 54 | ) 55 | } 56 | 57 | go(NonEmptyList.of("start")) 58 | .filter(path => isEnd(path.head)) 59 | .size 60 | } 61 | 62 | def part1(edges: Set[Edge]) = countPaths(edges, false, true) 63 | def part2(edges: Set[Edge]) = countPaths(edges, true, false) 64 | 65 | val parse: List[String] => Set[Edge] = 66 | _.map(_.split("-") match { 67 | case Array(a, b) => Edge(a, b) 68 | }).toSet 69 | 70 | val example = readAllLines("day12-example.txt").pipe(parse) 71 | 72 | assertEquals(part1(example), 10, "Part 1 (example)") 73 | assertEquals(part2(example), 36, "Part 2 (example)") 74 | 75 | val fromFile = readAllLines("day12.txt").pipe(parse) 76 | 77 | assertEquals(part1(fromFile), 3298, "Part 1") 78 | assertEquals(part2(fromFile), 93572, "Part 2") 79 | 80 | } 81 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day16/Bit.scala: -------------------------------------------------------------------------------- 1 | package aoc.day16 2 | 3 | import Bit._ 4 | 5 | sealed trait Bit extends Product with Serializable { 6 | def isOne = this == _1 7 | 8 | def toChar = 9 | this match { 10 | case `_1` => '1' 11 | case `_0` => '0' 12 | } 13 | 14 | } 15 | 16 | object Bit { 17 | case object _0 extends Bit 18 | case object _1 extends Bit 19 | 20 | implicit def fromOne(one: 1): Bit = _1 21 | implicit def fromZero(zero: 0): Bit = _0 22 | } 23 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day16/OpType.scala: -------------------------------------------------------------------------------- 1 | package aoc.day16 2 | 3 | sealed trait OpType extends Product with Serializable { 4 | import OpType._ 5 | 6 | private def cond(f: (Long, Long) => Boolean): (Long, Long) => Long = 7 | (a, b) => 8 | if (f(a, b)) 9 | 1L 10 | else 11 | 0L 12 | 13 | def eval: (Long, Long) => Long = 14 | this match { 15 | case Sum => _ + _ 16 | case Product => _ * _ 17 | case Minimum => _ min _ 18 | case Maximum => _ max _ 19 | case GreaterThan => cond(_ > _) 20 | case LessThan => cond(_ < _) 21 | case EqualTo => cond(_ == _) 22 | } 23 | 24 | } 25 | 26 | object OpType { 27 | case object Sum extends OpType 28 | case object Product extends OpType 29 | case object Minimum extends OpType 30 | case object Maximum extends OpType 31 | case object GreaterThan extends OpType 32 | case object LessThan extends OpType 33 | case object EqualTo extends OpType 34 | 35 | val values = Map( 36 | 0 -> Sum, 37 | 1 -> Product, 38 | 2 -> Minimum, 39 | 3 -> Maximum, 40 | // 4 -> Sike 41 | 5 -> GreaterThan, 42 | 6 -> LessThan, 43 | 7 -> EqualTo, 44 | ) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day16/Packet.scala: -------------------------------------------------------------------------------- 1 | package aoc.day16 2 | 3 | import Packet._ 4 | import cats.implicits._ 5 | 6 | sealed trait Packet extends Product with Serializable { 7 | 8 | def fold[A]( 9 | literal: (Packet.Version, Long) => A, 10 | operator: (Packet.Version, OpType, List[A]) => A, 11 | ): A = { 12 | def recurse(packet: Packet) = packet.fold(literal, operator) 13 | 14 | this match { 15 | case Literal(version, value) => literal(version, value) 16 | case Operator(version, op, subs) => operator(version, op, subs.map(recurse)) 17 | } 18 | } 19 | 20 | def sumVersions = fold[Long]( 21 | literal = (version, _) => version.value, 22 | operator = (version, _, children) => version.value |+| children.combineAll, 23 | ) 24 | 25 | def eval = fold[Long]( 26 | literal = (_, value) => value, 27 | operator = (_, op, children) => children.reduceLeft(op.eval), 28 | ) 29 | 30 | } 31 | 32 | object Packet { 33 | final case class Operator(version: Version, tpe: OpType, subs: List[Packet]) extends Packet 34 | final case class Literal(version: Version, value: Long) extends Packet 35 | 36 | final case class Version(value: Long) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day16/ParserApi.scala: -------------------------------------------------------------------------------- 1 | package aoc.day16 2 | 3 | import cats.Monad 4 | import cats.MonadError 5 | import cats.implicits._ 6 | import cats.mtl.Stateful 7 | 8 | trait ParserApi[F[_]] { 9 | def index: F[Int] 10 | def bit: F[Bit] 11 | } 12 | 13 | object ParserApi { 14 | def apply[F[_]](implicit F: ParserApi[F]): ParserApi[F] = F 15 | 16 | object ops { 17 | 18 | implicit class ParsersMonadicOps[F[_]: MonadError[*[_], EpsilonError]]( 19 | private val self: ParserApi[F] 20 | ) { 21 | 22 | def const(bits: List[Bit]): F[Unit] = nBits(bits.size).flatMap { 23 | case actual if actual == bits => ().pure[F] 24 | case actual => raiseMessage(s"Expected $bits, got $actual") 25 | } 26 | 27 | def nBits(n: Int): F[List[Bit]] = self.bit.replicateA(n) 28 | 29 | def raiseMessage[A]( 30 | msg: String 31 | ): F[A] = self.index.flatMap(i => EpsilonError(msg, i).raiseError[F, A]) 32 | 33 | } 34 | 35 | implicit class ParserOps[F[_]: ParserApi, A](private val self: F[A]) { 36 | 37 | def takeThrough( 38 | p: A => Boolean 39 | )( 40 | implicit F: Monad[F] 41 | ): F[List[A]] = List.empty[A].tailRecM { memory => 42 | self.map { a => 43 | Either.cond(!p(a), right = (a :: memory).reverse, left = a :: memory) 44 | } 45 | } 46 | 47 | } 48 | 49 | } 50 | 51 | implicit def statefulParserApi[F[_]: MonadError[*[_], EpsilonError]]( 52 | implicit S: Stateful[F, ParserState[Bit]] 53 | ): ParserApi[F] = 54 | new ParserApi[F] { 55 | val index: F[Int] = S.inspect(_.index) 56 | 57 | val bit: F[Bit] = S 58 | .inspect(_.proceed) 59 | .rethrow 60 | .flatMap { case (r, s) => S.set(s).as(r) } 61 | .map(identity(_)) 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day18/Direction.scala: -------------------------------------------------------------------------------- 1 | package aoc.day18 2 | 3 | sealed trait Direction 4 | 5 | case object Left extends Direction 6 | case object Right extends Direction 7 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day18/Parser.scala: -------------------------------------------------------------------------------- 1 | package aoc.day18 2 | 3 | import cats.Defer 4 | import cats.Eval 5 | import cats.MonadError 6 | import cats.data.EitherT 7 | import cats.data.StateT 8 | import cats.implicits._ 9 | import cats.mtl.Stateful 10 | 11 | trait Parser[F[_]] { 12 | def char: F[Char] 13 | def fail[A](msg: String): F[A] 14 | } 15 | 16 | object Parser { 17 | def apply[F[_]](implicit F: Parser[F]): Parser[F] = F 18 | 19 | implicit def parserStateful[F[_]: MonadError[*[_], Throwable]]( 20 | implicit S: Stateful[F, (String, Int)] 21 | ): Parser[F] = 22 | new Parser[F] { 23 | def char: F[Char] = S.inspect { case (s, i) => s(i) } <* S.modify(_.map(_ + 1)) 24 | 25 | def fail[A](msg: String): F[A] = S 26 | .inspect(_._2) 27 | .map(i => new Throwable(s"$msg at index $i")) 28 | .flatMap(_.raiseError[F, A]) 29 | 30 | } 31 | 32 | def parseFull[F[_]: Parser: Defer]( 33 | implicit F: MonadError[F, Throwable] 34 | ): F[Snailfish] = Defer[F].fix { self => 35 | val api = Parser[F] 36 | import api._ 37 | 38 | def const(ch: Char): F[Unit] = char.flatMap { 39 | case `ch` => ().pure[F] 40 | case actual => fail(s"expected $ch, got $actual") 41 | } 42 | 43 | def parseInt(ch: Char): F[Int] = 44 | ch.toString.toIntOption match { 45 | case Some(v) => v.pure[F] 46 | case None => fail[Int]("expected digit") 47 | } 48 | 49 | val leaf: F[Snailfish] = 50 | ( 51 | char.flatMap(parseInt) 52 | ).map(Leaf.apply) 53 | 54 | val pair: F[Snailfish] = 55 | ( 56 | const('[') *> self <* const(','), 57 | self <* const(']'), 58 | ).mapN(Pair.apply) 59 | 60 | leaf.orElse(pair) 61 | } 62 | 63 | def parsePair( 64 | s: String 65 | ): Snailfish = 66 | parseFull[StateT[EitherT[Eval, Throwable, *], (String, Int), *]] 67 | .runA((s, 0)) 68 | .value 69 | .value 70 | .toTry 71 | .get 72 | 73 | } 74 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day18/day18.scala: -------------------------------------------------------------------------------- 1 | package aoc.day18 2 | 3 | import aoc.lib._ 4 | import cats.implicits._ 5 | 6 | object Day18 extends App { 7 | 8 | object Snailfish { 9 | 10 | def replaceNextIn(it: WithPath[RawPair[Int]], direction: Direction): Snailfish => Snailfish = 11 | BinaryTree 12 | .next[Int](it.map(_.asTree), direction) 13 | .modify(_.map(_ + it.content.select(direction))) 14 | 15 | def rawExplode( 16 | it: WithPath[RawPair[Int]] 17 | ): Snailfish => Snailfish = BinaryTree.at(it.path).replace(Leaf(0)) 18 | 19 | def tryExplode(root: Snailfish): Option[Snailfish] = root 20 | .collectFirst { self => 21 | self.traverse(_.asRawPair.filter(_ => self.path.size >= 4)) 22 | } 23 | .map { it => 24 | replaceNextIn(it, Left) 25 | .andThen(replaceNextIn(it, Right)) 26 | .andThen(rawExplode(it)) 27 | .apply(root) 28 | } 29 | 30 | def trySplit(root: Snailfish): Option[Snailfish] = root 31 | .collectFirst { self => 32 | self.traverse(_.asLeaf).filter(_.content.n >= 10) 33 | } 34 | .map { it => 35 | val n = it.content.n 36 | val low = n / 2 37 | val high = (n / 2.0f).round 38 | 39 | root.replaceAt(it.path, Pair(Leaf(low), Leaf(high))) 40 | } 41 | 42 | implicit class SnailfishOps(root: Snailfish) { 43 | 44 | def reduce: Snailfish = tryExplode(root) 45 | .orElse(trySplit(root)) 46 | .map(_.reduce) 47 | .getOrElse(root) 48 | 49 | } 50 | 51 | } 52 | 53 | import Snailfish._ 54 | 55 | def part1(data: List[Snailfish]) = data.reduceLeft(Pair(_, _).reduce).magnitude 56 | 57 | def part2(data: List[Snailfish]) = 58 | (data, data) 59 | .tupled 60 | .to(LazyList) 61 | .filterNot { case (a, b) => a == b } 62 | .map { case (a, b) => Pair(a, b).reduce.magnitude } 63 | .max 64 | 65 | import Parser.parsePair 66 | locally { 67 | val example = readAllLines("day18-example.txt") 68 | 69 | assertEquals(part1(example.map(parsePair)), 4140, "Part 1 (example)") 70 | assertEquals(part2(example.map(parsePair)), 3993, "Part 2 (example)") 71 | } 72 | 73 | locally { 74 | val fromFile = readAllLines("day18.txt") 75 | 76 | assertEquals(part1(fromFile.map(parsePair)), 2541, "Part 1") 77 | assertEquals(part2(fromFile.map(parsePair)), 4647, "Part 2") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day18/package.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import cats.data.Chain 4 | import cats.data.Writer 5 | 6 | package object day18 { 7 | 8 | type WithPath[A] = Writer[Chain[Direction], A] 9 | val WithPath = Writer 10 | 11 | implicit class WithPathOps[A](wp: WithPath[A]) { 12 | def content: A = wp.value 13 | def path: Chain[Direction] = wp.written 14 | } 15 | 16 | type Snailfish = BinaryTree[Int] 17 | 18 | } 19 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day19/ScannerDescriptor.scala: -------------------------------------------------------------------------------- 1 | package aoc.day19 2 | 3 | import cats.implicits._ 4 | 5 | case class ScannerDescriptor( 6 | id: String, 7 | positions: List[Position], 8 | movement: Position, 9 | ) { 10 | 11 | def relativeTo( 12 | pos: Position 13 | ): ScannerDescriptor = copy(positions = positions.map(_ |-| pos), movement = movement |+| pos) 14 | 15 | // minor hacky optimization: we're guaranteed to hit at least one common point even if we drop arbitrary 11 elements. 16 | // dropping the first 11 seems to be efficient on lists. 17 | def relative: LazyList[ScannerDescriptor] = positions.drop(11).to(LazyList).map(relativeTo) 18 | 19 | def permute: List[ScannerDescriptor] = Permutation.allCompiled.map { perm => 20 | copy(positions = positions.map(perm)) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day2.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import cats.data.State 4 | 5 | import cats.implicits._ 6 | 7 | object Day2 extends App { 8 | 9 | sealed trait Command 10 | 11 | object Command { 12 | final case class Up(n: Int) extends Command 13 | final case class Down(n: Int) extends Command 14 | final case class Forward(n: Int) extends Command 15 | } 16 | 17 | final case class AppState(x: Int, y: Int, aim: Int) { 18 | def result = x * y 19 | } 20 | 21 | type S[A] = State[AppState, A] 22 | 23 | import Command._ 24 | 25 | val parse: String => Command = { 26 | case s"up $n" => Up(n.toInt) 27 | case s"down $n" => Down(n.toInt) 28 | case s"forward $n" => Forward(n.toInt) 29 | } 30 | 31 | val modPart1: Command => S[Unit] = { 32 | case Up(n) => State.modify(s => s.copy(y = s.y - n)) 33 | case Down(n) => State.modify(s => s.copy(y = s.y + n)) 34 | case Forward(n) => State.modify(s => s.copy(x = s.x + n)) 35 | } 36 | 37 | val modPart2: Command => S[Unit] = { 38 | case Down(n) => State.modify(s => s.copy(aim = s.aim + n)) 39 | case Up(n) => State.modify(s => s.copy(aim = s.aim - n)) 40 | case Forward(n) => State.modify(s => s.copy(x = s.x + n, y = s.y + (s.aim * n))) 41 | } 42 | 43 | val data = lib.readAllLines("day2.txt") 44 | 45 | def solve(mod: Command => S[Unit]): Int = 46 | data 47 | .map(parse) 48 | .traverse(mod) 49 | .runS(AppState(0, 0, 0)) 50 | .value 51 | .result 52 | 53 | println(s"Part 1: ${solve(modPart1)}") 54 | println(s"Part 1: ${solve(modPart2)}") 55 | 56 | } 57 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day5.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import cats.implicits._ 4 | 5 | import lib._ 6 | 7 | object Day5 extends App { 8 | case class Point(x: Int, y: Int) 9 | 10 | // not the best name but whatever 11 | def rangeSymmetric(x: Int, y: Int): Range = { 12 | val List(min, max) = List(x, y).sorted 13 | min to max 14 | } 15 | 16 | case class Line(from: Point, to: Point) { 17 | 18 | def allPoints: List[Point] = 19 | if (sameX) { 20 | rangeSymmetric(from.y, to.y).map(y => from.copy(y = y)).toList 21 | } else if (sameY) { 22 | rangeSymmetric(from.x, to.x).map(x => from.copy(x = x)).toList 23 | } else { 24 | val List(start, end) = List(from, to).sortBy(_.x) 25 | 26 | val ySign = 27 | if (start.y < end.y) 28 | 1 29 | else 30 | -1 31 | 32 | ( 33 | (start.x to end.x).toList, 34 | (start.y to end.y by ySign).toList, 35 | ).parMapN(Point.apply) 36 | } 37 | 38 | def sameX = from.x == to.x 39 | def sameY = from.y == to.y 40 | } 41 | 42 | val parse: List[String] => List[Line] = _.map { case s"$ax,$ay -> $bx,$by" => 43 | Line(Point(ax.toInt, ay.toInt), Point(bx.toInt, by.toInt)) 44 | } 45 | 46 | val result: List[Line] => Int = _.zipWithIndex 47 | .flatMap { case (line, index) => line.allPoints.tupleRight(index) } 48 | .groupBy(_._1) 49 | .map(_.map(_.size)) 50 | .count(_._2 >= 2) 51 | 52 | val noDiagonals: List[Line] => List[Line] = _.filter(line => line.sameX || line.sameY) 53 | 54 | val part1 = result.compose(noDiagonals).compose(parse) 55 | val part2 = result.compose(parse) 56 | 57 | val exampleData = readAllLines("day5-example.txt") 58 | 59 | assertEquals(part1(exampleData), 5, "Part 1 example") 60 | assertEquals(part2(exampleData), 12, "Part 2 example") 61 | 62 | val data = readAllLines("day5.txt") 63 | 64 | assertEquals(part1(data), 5084, "Part 1") 65 | assertEquals(part2(data), 17882, "Part 2") 66 | } 67 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/day7.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import lib._ 4 | 5 | object Day7 extends App { 6 | 7 | val fromFile = readAll("day7.txt") 8 | val example = readAll("day7-example.txt") 9 | 10 | def solve(input: String, cost: Int => Int) = { 11 | val data = input.split(",").map(_.toInt).toList 12 | 13 | (data.min to data.max).map { target => 14 | data.map(pos => cost((pos - target).abs)).sum 15 | }.min 16 | } 17 | 18 | val part1: String => Int = solve(_, identity) 19 | val part2: String => Int = solve(_, 1.to(_).sum) 20 | 21 | assertEquals(part1(example), 37, "Part 1 example") 22 | assertEquals(part2(example), 168, "Part 2 example") 23 | 24 | assertEquals(part1(fromFile), 356179, "Part 1 real") 25 | assertEquals(part2(fromFile), 99788435, "Part 2 real") 26 | 27 | } 28 | -------------------------------------------------------------------------------- /2021/src/main/scala/aoc/lib.scala: -------------------------------------------------------------------------------- 1 | package aoc 2 | 3 | import cats.implicits._ 4 | 5 | import scala.concurrent.duration._ 6 | import scala.io.Source 7 | import scala.util.Using 8 | 9 | object lib { 10 | 11 | def readAllLines(fileName: String): List[String] = 12 | Using(Source.fromResource(fileName))(_.getLines.toList).get 13 | 14 | def readAll(fileName: String): String = readAllLines(fileName).mkString("\n") 15 | 16 | def assertEquals[A]( 17 | actual: => A, 18 | expected: A, 19 | description: String, 20 | showResult: Boolean = true, 21 | ): Unit = { 22 | val (result, td) = timed(actual).map(_.toMillis) 23 | 24 | if (result != expected) 25 | Console 26 | .err 27 | .println( 28 | s"Assertion failed: expected $expected, got $result" + Some(description) 29 | .filterNot(_.isEmpty()) 30 | .mkString(" (", "", ")") + s" (${td}ms)" 31 | ) 32 | else { 33 | val resultString = 34 | if (showResult) 35 | s": $expected" 36 | else 37 | "" 38 | println( 39 | s"${Console.GREEN}Assertion passed ($description)$resultString (${td}ms)${Console.RESET}" 40 | ) 41 | } 42 | } 43 | 44 | def timed[A]( 45 | f: => A 46 | ): (A, FiniteDuration) = { 47 | val start = System.nanoTime() 48 | val result = f 49 | val end = System.nanoTime() 50 | val td = (end - start).nanos 51 | 52 | (result, td) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /2021/src/test/scala/aoc/day19/Day19Tests.scala: -------------------------------------------------------------------------------- 1 | package aoc.day19 2 | 3 | import aoc.day19.Permutation 4 | import cats.kernel.Eq 5 | import cats.kernel.laws.discipline.MonoidTests 6 | import cats.tests.CatsSuite 7 | import org.scalacheck.Arbitrary 8 | import org.scalacheck.Gen 9 | import org.scalatest.matchers.should.Matchers 10 | import cats.kernel.laws.discipline.CommutativeGroupTests 11 | 12 | class Day19Tests extends CatsSuite with Matchers { 13 | 14 | implicit val arbAxisKind: Arbitrary[AxisKind] = Arbitrary( 15 | Gen.oneOf(AxisKind.X, AxisKind.Y, AxisKind.Z) 16 | ) 17 | 18 | implicit val arbAxis: Arbitrary[Axis] = Arbitrary(Gen.resultOf(Axis.apply)) 19 | 20 | implicit val arbPermutation: Arbitrary[Permutation] = Arbitrary { 21 | Gen.resultOf(Permutation.apply) 22 | } 23 | 24 | implicit val eqPermutation: Eq[Permutation] = Eq.fromUniversalEquals 25 | 26 | implicit val arbPosition: Arbitrary[Position] = Arbitrary(Gen.resultOf(Position.apply)) 27 | implicit val eqPosition: Eq[Position] = Eq.fromUniversalEquals 28 | 29 | checkAll("Monoid[Permutation]", MonoidTests[Permutation].monoid) 30 | 31 | checkAll("CommutativeGroup[Position]", CommutativeGroupTests[Position].commutativeGroup) 32 | test("Permutation.id is identity") { 33 | forAll { pos: Position => 34 | Permutation.id.compile(pos) shouldBe pos 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /2022/.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /2022/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /2022/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | aoc2022-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | input/ 29 | 30 | **/.vscode/settings.json 31 | -------------------------------------------------------------------------------- /2022/README.md: -------------------------------------------------------------------------------- 1 | # Aoc2022 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `aoc2022` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:aoc2022, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at . 21 | 22 | -------------------------------------------------------------------------------- /2022/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1667395993, 6 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1669927173, 21 | "narHash": "sha256-Z7rSKzC5OuWv5Q7RRNQPZb0jVJRJk0BJB6/fGZzaAIU=", 22 | "owner": "nixos", 23 | "repo": "nixpkgs", 24 | "rev": "9063accddd2e68dcc71032459a58399e977374c9", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "nixos", 29 | "repo": "nixpkgs", 30 | "type": "github" 31 | } 32 | }, 33 | "root": { 34 | "inputs": { 35 | "flake-utils": "flake-utils", 36 | "nixpkgs": "nixpkgs" 37 | } 38 | } 39 | }, 40 | "root": "root", 41 | "version": 7 42 | } 43 | -------------------------------------------------------------------------------- /2022/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:nixos/nixpkgs"; 3 | inputs.flake-utils.url = "github:numtide/flake-utils"; 4 | 5 | outputs = { flake-utils, self, nixpkgs, ... }: flake-utils.lib.simpleFlake { 6 | name = "aoc-2022"; 7 | inherit self nixpkgs; 8 | shell = { pkgs }: pkgs.mkShell { 9 | packages = [ 10 | pkgs.elixir 11 | (pkgs.scala-cli.override { jre = pkgs.openjdk17; }) 12 | ]; 13 | }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /2022/lib/day1.ex: -------------------------------------------------------------------------------- 1 | defmodule Day1 do 2 | defp elfCalories() do 3 | input = 4 | File.read!("input/day1.txt") 5 | |> String.trim() 6 | 7 | results = 8 | input 9 | |> String.split("\n\n") 10 | |> Enum.map(fn elf -> 11 | String.split(elf) 12 | |> Enum.map(fn cal -> 13 | cal 14 | |> Integer.parse() 15 | |> elem(0) 16 | end) 17 | end) 18 | 19 | results |> Enum.map(&Enum.sum/1) 20 | end 21 | 22 | def part1() do 23 | elfCalories() 24 | |> Enum.max() 25 | end 26 | 27 | def part2() do 28 | elfCalories() 29 | |> Enum.sort() 30 | |> Enum.reverse() 31 | |> Enum.take(3) 32 | |> Enum.sum() 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /2022/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Aoc2022.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :aoc2022, 7 | version: "0.1.0", 8 | elixir: "~> 1.13", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | # {:dep_from_hexpm, "~> 0.3.0"}, 25 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /2022/scala/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.5.3" 2 | runner.dialect = scala3 -------------------------------------------------------------------------------- /2022/scala/macros.scala: -------------------------------------------------------------------------------- 1 | import scala.quoted._ 2 | import java.nio.file.Files 3 | import java.nio.file.Paths 4 | 5 | inline transparent def readFileNow(path: String): String = ${ 6 | readFileNowImpl('path) 7 | } 8 | 9 | def readFileNowImpl(pathExpr: Expr[String])(using Quotes): Expr[String] = 10 | import quotes.reflect.* 11 | 12 | Expr(Files.readString(Paths.get(pathExpr.valueOrAbort)).strip()) 13 | 14 | inline def showType[A]: Unit = ${ showTypeImpl[A] } 15 | 16 | def showTypeImpl[A: Type](using Quotes): Expr[Unit] = { 17 | import quotes.reflect._ 18 | quotes.reflect.report.info( 19 | Type.show[A] 20 | ) 21 | '{ () } 22 | } 23 | -------------------------------------------------------------------------------- /2022/test/day1_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Aoc2022Test do 2 | use ExUnit.Case 3 | doctest Day1 4 | 5 | test "part 1" do 6 | assert Day1.part1() == 75622 7 | end 8 | 9 | test "part 2" do 10 | assert Day1.part2() == 213_159 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /2022/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /2024/README.md: -------------------------------------------------------------------------------- 1 | # 2024 2 | 3 | Go here: https://share.unison-lang.org/@kubukoz/aoc-2024/ 4 | --------------------------------------------------------------------------------