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) }