From 031ef41462c207a4d24bf853c3235a6d3eb900bb Mon Sep 17 00:00:00 2001 From: Alexander Gehrke Date: Fri, 17 Dec 2021 20:10:22 +0100 Subject: [PATCH] Day 16: Parser Fun --- build.sbt | 2 + input/2021/day16-sample1.txt | 1 + input/2021/day16-sample2.txt | 1 + input/2021/day16-sample3.txt | 1 + input/2021/day16-sample4.txt | 1 + input/2021/day16-sample5.txt | 1 + input/2021/day16-sample6.txt | 1 + input/2021/day16-sample7.txt | 1 + input/2021/day16.txt | 1 + .../scala/de.qwertyuiop.aoc/2021/day16.scala | 106 +++++++++++++++++- .../de.qwertyuiop.aoc/lib/extensions.scala | 3 + 11 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 input/2021/day16-sample1.txt create mode 100644 input/2021/day16-sample2.txt create mode 100644 input/2021/day16-sample3.txt create mode 100644 input/2021/day16-sample4.txt create mode 100644 input/2021/day16-sample5.txt create mode 100644 input/2021/day16-sample6.txt create mode 100644 input/2021/day16-sample7.txt create mode 100644 input/2021/day16.txt diff --git a/build.sbt b/build.sbt index b5d109f..ce023d5 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,8 @@ lazy val root = project "org.typelevel" %% "cats-core" % "2.6.1", "org.typelevel" %% "cats-effect" % "3.1.1", "org.typelevel" %% "kittens" % "3.0.0-M1", + "org.tpolecat" %% "atto-core" % "0.9.5", + ) ) diff --git a/input/2021/day16-sample1.txt b/input/2021/day16-sample1.txt new file mode 100644 index 0000000..3f0eda1 --- /dev/null +++ b/input/2021/day16-sample1.txt @@ -0,0 +1 @@ +D2FE28 diff --git a/input/2021/day16-sample2.txt b/input/2021/day16-sample2.txt new file mode 100644 index 0000000..a7f8f25 --- /dev/null +++ b/input/2021/day16-sample2.txt @@ -0,0 +1 @@ +38006F45291200 diff --git a/input/2021/day16-sample3.txt b/input/2021/day16-sample3.txt new file mode 100644 index 0000000..bcc798c --- /dev/null +++ b/input/2021/day16-sample3.txt @@ -0,0 +1 @@ +EE00D40C823060 diff --git a/input/2021/day16-sample4.txt b/input/2021/day16-sample4.txt new file mode 100644 index 0000000..0d2cbff --- /dev/null +++ b/input/2021/day16-sample4.txt @@ -0,0 +1 @@ +8A004A801A8002F478 diff --git a/input/2021/day16-sample5.txt b/input/2021/day16-sample5.txt new file mode 100644 index 0000000..ed3b78a --- /dev/null +++ b/input/2021/day16-sample5.txt @@ -0,0 +1 @@ +620080001611562C8802118E34 diff --git a/input/2021/day16-sample6.txt b/input/2021/day16-sample6.txt new file mode 100644 index 0000000..827e51b --- /dev/null +++ b/input/2021/day16-sample6.txt @@ -0,0 +1 @@ +C0015000016115A2E0802F182340 diff --git a/input/2021/day16-sample7.txt b/input/2021/day16-sample7.txt new file mode 100644 index 0000000..0a1278e --- /dev/null +++ b/input/2021/day16-sample7.txt @@ -0,0 +1 @@ +A0016C880162017C3686B18A3D4780 diff --git a/input/2021/day16.txt b/input/2021/day16.txt new file mode 100644 index 0000000..f9fc365 --- /dev/null +++ b/input/2021/day16.txt @@ -0,0 +1 @@ +020D790050D26C13EC1348326D336ACE00EC299E6A8B929ED59C880502E00A526B969F62BF35CB4FB15B93A6311F67F813300470037500043E2D4218FA000864538E905A39CAF77386E35AB01802FC01BA00021118617C1F00043A3F4748A53CF66008D00481272D73308334EDB0ED304E200D4E94CF612A49B40036C98A7CF24A53CA94C6370FBDCC9018029600ACD529CA9A4F62ACD2B5F928802F0D2665CA7D6CC013919E78A3800D3CF7794A8FC938280473057394AFF15099BA23CDD37A08400E2A5F7297F916C9F97F82D2DFA734BC600D4E3BC89CCBABCBE2B77D200412599244D4C0138C780120CC67E9D351C5AB4E1D4C981802980080CDB84E034C5767C60124F3BC984CD1E479201232C016100662D45089A00087C1084F12A724752BEFEA9C51500566759BF9BE6C5080217910CD00525B6350E8C00E9272200DCE4EF4C1DD003952F7059BCF675443005680103976997699795E830C02E4CBCE72EFC6A6218C88C9DF2F3351FCEF2D83CADB779F59A052801F2BAACDAE7F52A8190073937FE1D700439234DBB4F7374DC0CC804CF1006A0D47B8A4200F445865170401F8251662D100909401AB8803313217C680004320D43F871308D140C010E0069E7EDD1796AFC8255800052E20043E0F42A8B6400864258E51088010B85910A0F4ECE1DFE069C0229AE63D0B8DC6F82529403203305C00E1002C80AF5602908400A20240100852401E98400830021400D30029004B6100294008400B9D0023240061C000D19CACCD9005F694AEF6493D3F9948DEB3B4CC273FFD5E9AD85CFDFF6978B80050392AC3D98D796449BE304FE7F0C13CD716656BD0A6002A67E61A400F6E8029300B300B11480463D004C401889B1CA31800211162204679621200FCAC01791CF6B1AFCF2473DAC6BF3A9F1700016A3D90064D359B35D003430727A7DC464E6401594A57C93A0084CC56A662B8C00AA424989F2A9112 diff --git a/src/main/scala/de.qwertyuiop.aoc/2021/day16.scala b/src/main/scala/de.qwertyuiop.aoc/2021/day16.scala index 053d9ba..889a484 100644 --- a/src/main/scala/de.qwertyuiop.aoc/2021/day16.scala +++ b/src/main/scala/de.qwertyuiop.aoc/2021/day16.scala @@ -1,6 +1,108 @@ package de.qwertyuiop.aoc.`2021` -import de.qwertyuiop.aoc.lib.* +import de.qwertyuiop.aoc.lib.{*, given} import cats.*, cats.implicits.given +import atto.*, Atto.* -def day16(using InputSource): Unit = ??? +def day16(using InputSource): Unit = + val pkg = input().head + println(BITSParser(pkg) | BITSParser.versionSum) + println(BITSParser(pkg) | BITSParser.evaluate) + +def day16test: Unit = + import BITSParser.* + val tests = List( + ("C200B40A82", 3), + ("04005AC33890", 54), + ("880086C3E88112", 7), + ("CE00C43D881120", 9), + ("D8005AC2A8F0", 1), + ("F600BC2D8F", 0), + ("9C005AC2F8F0", 0), + ("9C0141080250320F1802104A08",1 ), + ) + tests.foreach((hex, expected) => {val pkg = BITSParser(hex); println((expected == evaluate(pkg), hex, expected, evaluate(pkg), show(pkg)))}) + +object BITSParser: + + def apply(hex: String): Payload = + val bin = hex.toVector.reverse.map(c => Integer.parseInt(c.toString,16).toBinaryString.padLeft(4,'0')).reverse.mkString + pkg.parseOnly(bin).option.getOrElse(sys.error("Couldn't parse input")) + + final case class Header(version: Long, typ: Long) + + sealed trait Payload: + def header: Header + final case class Operator(header: Header, operands: List[Payload]) extends Payload + final case class Literal(header: Header, value: Long) extends Payload + + + val bit: Parser[Boolean] = (char('0') || char('1')).map(_.isRight) + def bits(digits: Int) = take(digits).map(BigInt(_, 2).toLong) + + val header = (bits(3), bits(3)).mapN(Header.apply) + + val literal: Parser[Long] = (many(char('1') ~> take(4)), (char('0') ~> take(4))) + .mapN((init, last) => BigInt(init.mkString + last, 2).toLong) + + def literal(header: Header): Parser[Literal] = literal.map(Literal(header, _)) + + val subpackageSized: Parser[List[Payload]] = + for + len <- bits(15) + payload <- take(len.toInt) + yield many(pkg).parseOnly(payload).option.getOrElse(List()) + + val subpackageCounted: Parser[List[Payload]] = + for + len <- bits(11) + payload <- manyN(len.toInt, pkg) + yield payload + + def operator(header: Header) = + for + usePackageCount <- bit + payload <- if usePackageCount then subpackageCounted else subpackageSized + yield Operator(header, payload) + + val pkg = + for + h <- header + result <- if h.typ == 4 then literal(h) + else operator(h) + yield result + + def versionSum(payload: Payload): Long = payload match + case Operator(Header(vers,_), operands) => vers + operands.map(versionSum).sum + case Literal(Header(vers,_),_) => vers + + extension (b: Boolean) + def toLong: Long = if b then 1L else 0L + + def evaluate(payload: Payload): Long = payload match + case Operator(Header(_, op), subPayload) => + val subValues = subPayload.map(evaluate) + op match + case 0 => subValues.sum + case 1 => subValues.product + case 2 => subValues.min + case 3 => subValues.max + case 5 => (subValues(0) > subValues(1)).toLong + case 6 => (subValues(0) < subValues(1)).toLong + case 7 => (subValues(0) == subValues(1)).toLong + case u => sys.error(s"Invalid operator encountered: $u") + case Literal(_, v) => v + + def show(payload: Payload): String = payload match + case Operator(Header(_, op), subPayload) => + val subValues = subPayload.map(show) + op match + case 0 => s"sum($subValues)" + case 1 => s"product($subValues)" + case 2 => s"min($subValues)" + case 3 => s"max($subValues)" + case 5 => s"${subValues(0)} > ${subValues(1)}" + case 6 => s"${subValues(0)} < ${subValues(1)}" + case 7 => s"${subValues(0)} == ${subValues(1)}" + case u => sys.error(s"Invalid operator encountered: $u") + case Literal(_, v) => v.toString diff --git a/src/main/scala/de.qwertyuiop.aoc/lib/extensions.scala b/src/main/scala/de.qwertyuiop.aoc/lib/extensions.scala index debdb77..dd78b6d 100644 --- a/src/main/scala/de.qwertyuiop.aoc/lib/extensions.scala +++ b/src/main/scala/de.qwertyuiop.aoc/lib/extensions.scala @@ -25,6 +25,9 @@ extension (s: String) def splitNN(regex: String): List[String] = s.split(regex).nn.map(_.nn).toList + def padLeft(size: Int, elem: Char): String = + elem.toString * (size - s.length) + s + extension [K,V,W](map: Map[K,V]) def mapValuesS(f: V => W): Map[K, W] = map.view.mapValues(f).toMap