Skip to main content

14 posts tagged with "Advent of Code"

View All Tags

· 4 min read

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.

· One min read

My solution for https://adventofcode.com/2022/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

}

· 3 min read

My solution for https://adventofcode.com/2022/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

}

· One min read

My solution for https://adventofcode.com/2022/day/1

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

}