# 14 posts tagged with "Advent of Code"

View All Tags

## Advent of Code - Solving the Third Problem

It's that time of the year again, and the 2022 Advent of Code is up and running! If you're unfamiliar with it, the Advent of Code (AoC) is a series of programming puzzles that runs from December 1 - 25; one question a day, with two parts each. You can claim points on a leader board by being the fastest to solve a problem, but I generally just do them for fun.

I don't think I've ever finished all 25 days before, due to inevitably falling too far behind over the course of the month, but I have a fondness for it, because it pushed me to use a very valuable skill, that I try to use every day: solve the next question before it's asked. Or, you know, try to.

When I first started participating, as a casual player, I would usually try to solve the problem with one-liners, and add operating logic while mapping through steps that was as close to a series of one-liners as I could make it. Then, for the second part, I'd find myself doing mostly the same, from scratch, to solve the twist on the original question that the second part asked for. It was a real slog. Eventually, I stopped trying to solve the first question for what was asked, and started to solve it for what I thought the second question was going to be.

That's what I consider the third, and most challenging problem; solving the first one in such a way that the code can be (mostly) re-used in anticipation of the second part with minimal re-write, but also not over-engineering a bloated solution that would be a pain to maintain. Also, it's the most fun problem to solve! It's always exciting to get it right, and worse case you've invested yourself deeper in the problem-space you're trying to solve.

Here's an example of from Day 4. The premise of the question is that some elves are assigned rooms to clean, but it's noticed that, given a pair of elves, some of their assigned rooms overlap. The (literal) question to part one:

In how many assignment pairs does one range fully contain the other?

The straight-forward thing to do, as we iterate over pairs of elves, is to check does one completely overlap, or not?

``def redundant: Boolean =    (workers._1.assignedTo.toSet, workers._2.assignedTo.toSet) match {        case (a, b) if a.subsetOf(b) | b.subsetOf(a) => true        case _                                       => false    }``

And that's the right solution! It will get you the answer. Ship it! But, perhaps ask yourself, "What's a next logical question I might be asked once I know if there has been overlapping rooms assigned?". My solution for this part of the question answers if there is a redundancy, and if so, which elf is redundant.

``def redundant: Option[Int] =    (workers._1.assignedTo.toSet, workers._2.assignedTo.toSet) match {        case (a, b) if a.subsetOf(b) => Some(1)        case (a, b) if b.subsetOf(a) => Some(2)        case _                       => None    }``

That solution serves the same logic as the first - I can check if the `Option` is defined or not for a true/false solution, but now I have the added power of peaking into the result if I need to know which one to re-assign. Spoiler alert, the follow up question didn't ask about it this time.

I've found these little pieces of logic can start to add up in a larger code base encompassing some domain, and when you're asked how long it would take to roll out some new feature, instead of saying "2 weeks", you can say, "We'll, it's funny you should ask that..."

If you're interested in my full solution for day 4, you can find it here.

## Advent of Code - Day 3

``import zio.*import zio.stream.*object Day3 extends ZIOAppDefault {  val source: String => ZStream[Any, Throwable, String] =    fileName =>      ZStream        .fromFileName(fileName)        .via(ZPipeline.utfDecode >>> ZPipeline.splitLines)        .filter(_.nonEmpty)  // Indexes start at 0. Avoid an extra map by prepending an element, and taking the tail.  val priorities: Map[Char, Int] =    (('a' +: ('a' to 'z')) ++ ('A' to 'Z')).zipWithIndex.tail.toMap  val data = "day-3-1.data"  override def run: ZIO[Any, Any, Any] = for {    _ <- source(data)      .map(str => str.splitAt(str.length / 2)) // Split in two      .map { case (a, b) => a.intersect(b).head } // Elfs promised there would be only one      .map(k => priorities.getOrElse(k, throw new RuntimeException("hau ruck")))      .run(ZSink.sum)      .debug("Answer pt.1")    _ <- source(data)      .grouped(3) // Get it together, Elves!!!      .map(_.reduceLeft((a, b) => a.intersect(b)).head) // Elfs promised there would be only one      .map(k => priorities.getOrElse(k, throw new RuntimeException("hau ruck")))      .run(ZSink.sum)      .debug("Answer pt.2")  } yield Exit.Success}``

## Advent of Code - Day 2

``import zio.*import zio.stream.*object Day2 extends ZIOAppDefault {  sealed trait RPS  object RPS {    case object Rock extends RPS    case object Paper extends RPS    case object Scissors extends RPS    def apply(s: String): RPS = s match {      case "A" | "X" => Rock      case "B" | "Y" => Paper      case "C" | "Z" => Scissors      case _         => throw new RuntimeException("That's cheating!")    }    extension(rps: RPS) {      def losesTo: RPS = rps match {        case Rock     => Paper        case Paper    => Scissors        case Scissors => Rock      }      def winsAgainst = rps match {        case Rock     => Scissors        case Paper    => Rock        case Scissors => Paper      }    }    def score(a: RPS, b: RPS): Int = {      val choice = b match {        case Rock     => 1        case Paper    => 2        case Scissors => 3      }      val outcome = (a, b) match {        case (x, y) if x == y            => 3 // Draw        case (x, Rock) if x == Scissors  => 6 // Winner        case (x, Paper) if x == Rock     => 6 // Winner        case (x, Scissors) if x == Paper => 6 // Chicken substitute dinner        case _                           => 0 // If ya ain't first, yer last      }      // A refactor of outcome, after extension methods were added for part 2      val betterOutcome = (a, b) match {        case (x, y) if x == y         => 3 // Draw        case (x, y) if x.losesTo == y => 6 // Winner        case _                        => 0 // If ya ain't first, yer last      }      choice + betterOutcome    }    def throwTheGame(a: RPS, b: RPS): Int = (a, b) match {      case (x, Rock)     => score(x, x.winsAgainst) // Need to lose      case (x, Paper)    => score(x, x) // Need to tie      case (x, Scissors) => score(x, x.losesTo) // Need to win    }  }  val source: String => ZStream[Any, Throwable, String] =    fileName => ZStream.fromFileName(fileName).via(ZPipeline.utfDecode)  val pt1Pipeline: ZPipeline[Any, Nothing, String, Int] =    ZPipeline.splitLines >>>      ZPipeline.map[String, Int] { str =>        val arr = str.split(" ").map(RPS.apply)        RPS.score(arr.head, arr.last)      }  val pt2Pipeline: ZPipeline[Any, Nothing, String, Int] =    ZPipeline.splitLines >>>      ZPipeline.map[String, Int] { str =>        val arr = str.split(" ").map(RPS.apply)        RPS.throwTheGame(arr.head, arr.last)      }  val scoreSink: ZSink[Any, Nothing, Int, Nothing, Int] = ZSink.sum[Int]  val data = "day-2-1.data"  override def run: ZIO[Any, Any, Any] = for {    _ <- source(data)      .via(pt1Pipeline)      .run(scoreSink)      .debug("Answer pt.1")    _ <- source(data)      .via(pt2Pipeline)      .run(scoreSink)      .debug("Answer pt.2")  } yield ExitCode.success}``
``import zio.*import zio.stream.*object Day1 extends ZIOAppDefault {  // Add a couple new lines so we don't have to collectLeftover  val source: String => ZStream[Any, Throwable, String] =    fileName =>      ZStream.fromFileName(fileName).via(ZPipeline.utfDecode) ++ ZStream("\n\n")  val pt1Pipeline: ZPipeline[Any, Nothing, String, Int] =    // Split by lines    ZPipeline.splitLines >>>      // group into Chunk[Chunk[_]] based on empty lines      ZPipeline        .mapAccum[String, Chunk[String], Chunk[String]] {          Chunk.empty        } { (state, elem) =>          if (elem.nonEmpty) (state :+ elem, Chunk.empty)          else (Chunk.empty, state)        }        //Get rid of empty Chunks        .filter(_.nonEmpty) >>>      // Add up the inner Chunk      ZPipeline.map[Chunk[String], Int](_.map(_.toInt).sum)  // Find the max  val pt1Sink: ZSink[Any, Nothing, Int, Nothing, Int] =    ZSink.collectAll[Int].map(_.max)  // Sum of biggest 3  val pt2Sink: ZSink[Any, Nothing, Int, Nothing, Int] =    ZSink.collectAll[Int].map(_.sortBy(-_).take(3).sum)    override def run: ZIO[Any, Any, Any] = for {    _ <- source("day-1-1.data")      .via(pt1Pipeline)      .run(pt1Sink)      .debug("Answer pt.1")    _ <- source("day-1-1.data")      .via(pt1Pipeline)      .run(pt2Sink)      .debug("Answer pt.2")  } yield ExitCode.success}``