diff --git a/build.sbt b/build.sbt index ba560f9..111bad3 100644 --- a/build.sbt +++ b/build.sbt @@ -8,5 +8,6 @@ lazy val core = (project in file("core")).settings( "org.typelevel" %% "cats-effect" % "3.0.0-M4", "co.fs2" %% "fs2-core" % "3.0.0-M6", "co.fs2" %% "fs2-io" % "3.0.0-M6", + "org.typelevel" %% "cats-parse" % "0.1-31-923a513", ), ) diff --git a/core/src/main/scala/tf/bug/aoc/y2020/Day1.scala b/core/src/main/scala/tf/bug/aoc/y2020/Day1.scala index c172310..84ca07b 100644 --- a/core/src/main/scala/tf/bug/aoc/y2020/Day1.scala +++ b/core/src/main/scala/tf/bug/aoc/y2020/Day1.scala @@ -10,10 +10,10 @@ import tf.bug.aoc.Day object Day1 extends Day: - def part1[F[_]: Sync]: Pipe[F, String, String] = + override def part1[F[_]: Sync]: Pipe[F, String, String] = parts[F](2) - def part2[F[_]: Sync]: Pipe[F, String, String] = + override def part2[F[_]: Sync]: Pipe[F, String, String] = parts[F](3) def parts[F[_]: Sync](nums: Int): Pipe[F, String, String] = diff --git a/core/src/main/scala/tf/bug/aoc/y2020/Day2.scala b/core/src/main/scala/tf/bug/aoc/y2020/Day2.scala new file mode 100644 index 0000000..a747041 --- /dev/null +++ b/core/src/main/scala/tf/bug/aoc/y2020/Day2.scala @@ -0,0 +1,47 @@ +package tf.bug.aoc.y2020 + +import cats.effect._ +import cats.parse.{Parser => P, Parser1, Numbers} +import cats.syntax.all._ +import fs2._ +import tf.bug.aoc.Day + +object Day2 extends Day: + + case class Policy(min: Int, max: Int, letter: Char) + case class Entry(policy: Policy, password: String) + + val parsePolicy: Parser1[Policy] = + (Numbers.nonNegativeIntString, P.char('-'), Numbers.nonNegativeIntString, P.char(' '), P.anyChar).mapN { + case (min, (), max, (), letter) => + Policy(min.toInt, max.toInt, letter) + } + + val parseEntry: Parser1[Entry] = + (parsePolicy, P.string1(": "), P.until1(P.end)).mapN { + case (policy, (), password) => + Entry(policy, password) + } + + override def part1[F[_]: Sync]: Pipe[F, String, String] = + parts[F] { ent => + val charCount = ent.password.count(_ == ent.policy.letter) + ent.policy.min <= charCount && charCount <= ent.policy.max + } + + override def part2[F[_]: Sync]: Pipe[F, String, String] = + parts[F] { ent => + val a = ent.policy.min <= ent.password.length && ent.password.charAt(ent.policy.min - 1) == ent.policy.letter + val b = ent.policy.max <= ent.password.length && ent.password.charAt(ent.policy.max - 1) == ent.policy.letter + (a || b) && !(a && b) + } + + def parts[F[_]: Sync](validation: Entry => Boolean): Pipe[F, String, String] = + (lines: Stream[F, String]) => { + lines.map(parseEntry.parseAll) + .collect { + case Right(entry) => entry + } + .foldMap(ent => if(validation(ent)) 1 else 0) + .map(_.show) + } diff --git a/core/src/main/scala/tf/bug/aoc/y2020/y2020.scala b/core/src/main/scala/tf/bug/aoc/y2020/y2020.scala index 97ee44e..d28850c 100644 --- a/core/src/main/scala/tf/bug/aoc/y2020/y2020.scala +++ b/core/src/main/scala/tf/bug/aoc/y2020/y2020.scala @@ -5,4 +5,5 @@ import tf.bug.aoc.{Day, Year} object y2020 extends Year: override def days: Map[Int, Day] = Map( 1 -> Day1, + 2 -> Day2, )