Intro

Day 5 and I’m about to reach the the 20% mark! So far things have been going pretty well, nothing too complex let’s see what Today’s challenge will be!

The Puzzle

The elves have a several stacks of crates but need to re-order them so that the crates with the most important items are on the top for easy access. Thankfully we’ve been given the list of rearrangements the crane is going to do and our task to calculate which crates will be where after completing all the movements. One final catch is that the crane can only move one crate at a time so when moving multiple crates between piles the order of those crates is reversed!

Input

The input for today is a little trickier than the previous days! It consists of two different sections, the first is a representation of the crates and then we have an empty line followed by the list of movements the crane is going to make.


[J] [T]     [H]         [P] [L]
[F] [S] [T] [B]         [M] [D]
[C] [L] [J] [Z] [S]     [L] [B]
[N] [Q] [G] [J] [J]     [F] [F] [R]
[D] [V] [B] [L] [B] [Q] [D] [M] [T]
[B] [Z] [Z] [T] [V] [S] [V] [S] [D]
[W] [P] [P] [D] [G] [P] [B] [P] [V]
 1   2   3   4   5   6   7   8   9

move 4 from 9 to 6
move 7 from 2 to 5
move 3 from 5 to 2
move 2 from 2 to 1
move 2 from 8 to 4
move 1 from 6 to 9
  ... many lines of input!

Okay parsing that is going to be a little more involved than just slurp and splitting on newlines… But that is still a great place to start! Then I need to split the input into two, the crates and the instructions.

(ns day5.core
  (:require [clojure.string :as str]))

(def input
  (->> "./advent-of-code/day5/input.txt"
       slurp
       str/split-lines
       (split-with #(not (str/blank? %)))))

Okay that’ll return a tuple of [crates instructions]. Now I can focus on parsing each individually. I’m going to go with the crates first as… well… they are first in the input. :-)

The crates are in an interesting shape to parse. The piles are depicted vertically so I could grab each crate by index but that’d be kinda messy. Thankfully I can treat the ASCII art like a matrix, strings are just lists of characters and transform / rotate it and treat each pile as a list. Clojure makes this super simple too!

The map function can take an arbitrary number of collections to work on and it’ll pass each element in each collection as parameters to a function at the same time.

(map str ["a" "b" "c"] ["d" "e" "f"])
("ad" "be" "cf")

The list of crates are a two dimensional array if we treat the strings as lists of chars so I can use apply to pass the contents of a collection as the arguments to a function. Then I can group each row together with a function like list or str.

(apply map list
       [[1 2 3]
      [4 5 6]
      [7 8 9]])
((1 4 7) (2 5 8) (3 6 9))

Hey presto, that is a rotated matrix. If I do the same to the crates then strip out all the lines which don’t contain a alpha character I can get a list of strings that represent the piles of crates!

(defn parse-crates [crates]
  (->> crates
       (apply map str)
       (map str/trim)
       (filter #(re-matches #"\w+" %))
       vec))
(parse-crates (first input))
["JFCNDBW1" "TSLQVZP2" "TJGBZP3" "CHBZJLTD4" "SJBVG5" "QSP6" "NPMLFDVB7" "RLDBFMSP8" "RTDV9"]

Not too shabby! :-) One final thing to note. I’m pretty sure I’ll want to access those crates by index, it just sounds like that’ll be the case for this puzzle so I need to use vec to have a vector and not a list.

Okay now onto those instructions, this should be simpler, I basically just want to extract the numbers as a list. I’ve already done something similar before this advent. I need to drop the separating new line from the raw input using rest, then split each line on a space char. Then for each line I just need to filter out the segments of the string that aren’t numeric and parse what’s left as integers.

(defn extract-digits [strings]
  (->> strings
       (filter #(re-matches #"\d+" %))
       (map #(Integer/parseInt %))))

(defn parse-instructions [instructions]
  (->>
   (rest instructions)
   (map #(str/split % #" "))
   (map extract-digits)))
(take 5 (parse-instructions (last input)))
((4 9 6) (7 2 5) (3 5 2) (2 2 1) (2 8 4))

And that is a great starting point for me to start moving some crates around.

Part 1

I want to start small, instead of following all the instructions I’m going to try and apply just the first and then iterate from there (probably literally). I guess using recursion might help with the reverse order requirement but so might just using reverse. I’m getting ahead of myself. Right…

I can destructure the instruction into three its three parts [number from to]. Then I need to dec (decrement by 1) from and to because our instructions start at 1 but my list of crates are indexed from 0. I can then use those indexes to grab the two piles of cares I’m interested in. I can then split-at to separate the crates I’m going to move from the crates that are being left behind. Then I need to reverse the crates I’m moving. Finally I am going to assoc into crates the updated values of the two piles I’ve changed.

(defn move-crates [crates [number from to]]
  (let [from-pile (nth crates (dec from))
	to-pile (nth crates (dec to))
	[moving-crates remaining-crates] (split-at number from-pile)
	moved-crates (apply str (reverse moving-crates))]
    (assoc crates
	   (dec from) (apply str remaining-crates)
	   (dec to) (str moved-crates to-pile))))

Right… that looks like quite a lot but it isn’t too complex. Let’s give it a go:

Let’s try that out:

(def some-crates (parse-crates (first input)))
(def one-instruction [3 1 2])
#‘day5.core/some-crates
#‘day5.core/one-instruction
some-crates
one-instruction
(move-crates some-crates one-instruction)
[“JFCNDBW1” “TSLQVZP2” “TJGBZP3” “CHBZJLTD4” “SJBVG5” “QSP6” “NPMLFDVB7” “RLDBFMSP8” “RTDV9”]
[3 1 2]
[“NDBW1” “CFJTSLQVZP2” “TJGBZP3” “CHBZJLTD4” “SJBVG5” “QSP6” “NPMLFDVB7” “RLDBFMSP8” “RTDV9”]

Nice! JFC has become CFJ and been appended to the front of the next string / piles of crates! Now I just want a function that’ll keep doing that for the rest of the instructions.

(defn follow-instructions [crates instructions]
  (if (empty? instructions)
    crates
    (recur (move-crates crates (first instructions))
	 (rest instructions))))

Okay let’s try moving some crates!

(follow-instructions
 (parse-crates (first input))
 (parse-instructions (last input)))
["LMCCRGJH1" "BW2" "LVFZTBS3" "V4" "VJQBPSPPJZPBN5" "TRSQMTJFGZ6" "VTBLN7" "LDFBDDD8" "PDS9"]

Well… that looks…. Well it looks like a bunch of strings! Let’s finish the puzzle and see if it doing the right thing! The puzzle answer is the first crate in every pile as a string which is easy enough to extract:

(defn part-1 [crates instructions]
  (->>
   (follow-instructions crates instructions)
   (map first)
   (apply str)))
(part-1
 (parse-crates (first input))
 (parse-instructions (last input)))
"LBLVVTVLP"

Plugging that into the website….

That’s the right answer!

Score, bring on part 2!

Part 2

The part 2 twist! The crane can actually move multiple crates at once!!! I no longer need the moved crates to be in reverse order as they’ll all be lifted and put down together. Thankfully that is a very small change for my code. I literally just need to stop calling the reverse function.

Now I could have done a refactoring here but I had to start actual work soon so I did what any developer would do when running out of time and copy pasted a whole chunk of code:

(defn move-multiple-crates [crates [number from to]]
  (let [from-pile (nth crates (dec from))
	to-pile (nth crates (dec to))
	[moving-crates remaining-crates] (split-at number from-pile)
	moved-crates (apply str moving-crates)] ;; <- this is the change
    (assoc crates
	   (dec from) (apply str remaining-crates)
	   (dec to) (str moved-crates to-pile))))

(defn follow-instructions-multiple-crates [crates instructions]
  (if (empty? instructions)
    crates
    (recur (move-multiple-crates crates (first instructions))
	   (rest instructions))))

(defn part-2 [crates instructions]
  (->>
   (follow-instructions-multiple-crates
    crates
    instructions)
   (map first)
   (apply str)))
#‘day5.core/move-multiple-crates
#‘day5.core/follow-instructions-multiple-crates
#‘day5.core/part-2
(part-2
 (parse-crates (first input))
 (parse-instructions (last input)))
"TPFFBDRJD"

You have completed Day 5!

Great Day 5 Done and I wasn’t late to my morning stand up! Day 5 was certainly a step up from the day before. I really liked how easy Clojure makes transforming a matrix though it is a great trick to have up your sleeve!

If you’re interested, I’ve put the code for this puzzle and the other AoC challenges I’ve done on github.

Thanks for reading! :-)