aoc/core/src/main/scala/tf/bug/aoc/y2020/Day2.scala

50 lines
1.5 KiB
Scala

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[_]: Async]: 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[_]: Async]: 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)
}
}