↤ go back, or return home

jack's advent of code 2022 ramblings

day 5

a spicy day!

not a hard problem to understand, but a wonky thing to parse into something useful before actually solving the problem

seems like some people skipped bothering to parse the input as-is and just manually typed up the stacks into their programs

this probably would be the way to go for speed, but as mudkip said today:

[12:01 PM] mudkip: I treat input as sacred and immutable

i too, treat input as sacred and immutable, but power to you if you did otherwise


click to view my solution

given the sample input:

    [D]
[N] [C]
[Z] [M] [P]
 1   2   3

move 1 from 2 to 1
move 3 from 1 to 3
move 2 from 2 to 1
move 1 from 1 to 2

assuming input is the above as one big string,

[initial_state_string, instructions_string] =
  input
  |> Kino.Input.read()
  |> String.split("\n\n", trim: true)

pull out the \n\n split trick, but this time to break the two parts of the problem into their own chunks

[
  "    [D]    \n[N] [C]    \n[Z] [M] [P]\n 1   2   3 ",
  "move 1 from 2 to 1\nmove 3 from 1 to 3\nmove 2 from 2 to 1\nmove 1 from 1 to 2"
]

its time for some fun parsing

parsing initial state

rows =
  initial_state_string
  |> String.split("\n")
  |> Enum.reverse()
  |> tl()
  |> Enum.reverse()

so first, i will focus on parsing the initial state into something useful

split on newline, the usual, and a quick reverse -> tl -> reverse to pluck the stack labels away

[
  "    [D]    ",
  "[N] [C]    ",
  "[Z] [M] [P]"
]

we are left with this, which is still quite raw, but we can work with this

rows =
  rows
  |> Enum.map(fn line ->
    line
    |> String.graphemes()
    |> tl
    |> Enum.take_every(4)
  end)

so, we’re only concerned with every 4 characters, the ] [ that seperate them are not important to us, so we do just that!

firstly, using String.graphemes on the line splits it from a string to a list of its characters

since the Enum.take_every function will always take the first element of the given enumerable, we take the tail of the linked list (using tl) and then take every 4th value from the list of characters

[
  [" ", "D", " "],
  ["N", "C", " "],
  ["Z", "M", "P"]
]

we are left with this, a lot nicer than the input, but its the wrong way, visually here the columns are aligned, but the underlying rows are not!

initial_state =
  rows
  |> Enum.reduce(List.duplicate([], length(rows) + 1), fn row, acc ->
    Enum.zip(row, acc)
    |> Enum.map(fn {row, acc} -> [row | acc] end)
    |> Enum.map(&Enum.filter(&1, fn item -> item != " " end))
  end)
  |> Enum.map(&Enum.reverse(&1))

doing a bit of reduce magic, start with a list of empty lists of length of the amount of rows we have

then, reduce each row into the lists of lists by inserting each nth item of the row into the nth list of lists, and filter out any values that are simply space characters

[
  ["N", "Z"],
  ["D", "C", "M"],
  ["P"]
]

this is better, visually this isn’t as nice as the above, but now each column is in its own seperate list, which will make operating on it later a lot nicer

parsing instructions

instructions =
  instructions_string
  |> String.split("\n")
  |> Enum.map(fn instruction ->
    instruction
    |> String.split(" ")
    |> tl
    |> Enum.take_every(2)
    |> Enum.map(&String.to_integer(&1))
  end)

parsing the instructions is much nicer than the initial state :)

similar usage of Enum.take_every happens here, except acting on a list of words instead of characters

[
  [1, 2, 1],
  [3, 1, 3],
  [2, 2, 1],
  [1, 1, 2]
]

here, the 0th item is the amount to move, the second is the from location, and the third is the two location

utils

defmodule CargoCrane do
  def activate(model, instructions, initial_state) do
    Enum.reduce(instructions, initial_state, fn [move, from, to], state ->
      to_move =
        state
        |> Enum.at(from - 1)
        |> Enum.take(move)

      state
      |> List.update_at(to - 1, fn stack ->
        case model do
          :CrateMover9000 -> (to_move |> Enum.reverse()) ++ stack
          :CrateMover9001 -> to_move ++ stack
          _ -> raise "I don't know that model of crane!"
        end
      end)
      |> List.update_at(from - 1, fn stack ->
        stack |> Enum.drop(move)
      end)
    end)
  end

  def inspect_tops(state) do
    state |> Enum.map(&hd(&1)) |> Enum.join()
  end
end

the meat

activate/3

def activate(model, instructions, initial_state) do

here, we accept 3 arguments, model, instructions, and initial_state

model should either :CrateMover9000 or :CrateMover9001, depending on the mode of operation expected by the user

instructions is simply our list of instructions

and initial_state is our list of stacks that we will apply the instructions to

Enum.reduce(instructions, initial_state, fn [move, from, to], state ->

the first thing we do is start a Enum.reduce, and pass in the instructions as the thing we shall be reducing over, but the initial accumulator will be our initial state

the idea here, is that the accumulator should always be something that represents a list of stacks, and each time we reduce over the list of instructions, what we return from this inner function is the next state after applying a given instruction

also happening on this line is destucturing the list into three variables, move, from, and to

to_move =
  state
  |> Enum.at(from - 1)
  |> Enum.take(move)

within this inner function, take the items we need from the stack we are moving from, and store them in a to_move variable

this does not mutate anything, because in this language thats not on the table

state
|> List.update_at(to - 1, fn stack ->
  case model do
    :CrateMover9000 -> (to_move |> Enum.reverse()) ++ stack
    :CrateMover9001 -> to_move ++ stack
    _ -> raise "I don't know that model of crane!"
  end
end)
|> List.update_at(from - 1, fn stack ->
  stack |> Enum.drop(move)
end)

the next block is busy, but not bad

as mentioned previously mutations are no bueno, so List.update_at can be used to update an item within a list at a given index, and return a new list with the changes made

the first List.update_at is to put the items we just stored in to_move into the target location

here, we use a case statement on model to match if we are the 9000 or 9001 model, and either append the reversed version of the list to the target stack, or the list as-is

or just raise with “I don’t know that model of crane!” if that case fails

and for the second List.update_at, we drop the amount we moved from the stack we moved from

inspect_tops/1

def inspect_tops(state) do
  state |> Enum.map(&hd(&1)) |> Enum.join()
end

given a state, pluck the heads from each of the stacks, and join them together into a string

this is used to spit out the answer that can be fed directly into the advent of code text field

part 1

CargoCrane.activate(:CrateMover9000, instructions, initial_state)
|> CargoCrane.inspect_tops() # -> "CMZ"

so there was a lot of preamble before, but as you can see, our part 1 is a two liner

part 2

CargoCrane.activate(:CrateMover9001, instructions, initial_state)
|> CargoCrane.inspect_tops() # -> "MCD"

and our part 2 is a similar two liner with a different crate mover model!

wow

the full solution can be found [here]



i stayed up early, again, but this one was the longest to figure out solutions so far

5   00:47:43  9363      0   00:50:06  8313      0

from my [personal stats] page, you can see it took me 47 minutes, from problem release, to solve, to answer this question

but, then only about two minutes after that to solve part 2, so a tiny delta between solutions :D

i had work in the morning, and two alarms that should have woken me up for it, but alas due to flying too close to the sun, i slept through the alarms and awoke at 11am

no more 1:30am problem solving for me :), now to see if i can flip around to waking up super early to complete problems instead…


due to i think sloppy wording on my part, we have a guest star today helping out with the rambling!


for context, earlier in conversation with [sven], he mentioned

[1:44 PM] Secure Cluster Nerd 5000: Just waiting for the rambling now 🙂

to which i replied

[2:30 PM] ja (gmup): ramblings will be later during the work week sadly

what i meant here was that ramblings would be later during the day, during the work week, not that i would do them later in the work week

marty then jumped in and said

[4:10 PM] mort: i’ll do jacks writeup for him today and put up a PR

[4:10 PM] mort: xD

i didn’t immediately correct him and assumed the xD implied it was a joke

it was not a joke, [he did end up putting up a pr]

so this post is a combination of his preabmle / comments, and also my own ramblings

ramble 2x

(everything marty wrote is from the perspective of me not being here)

here is the marty preamble:


hey guys, [mert] here. I’m filling in for jack today cause he slept in and told his fans he needed an earned break. Until then you’re stuck with my shitty, less productive reviews… onwards!

my general comments for today - the spice level is increasing. We rarely see this in AoC but today was an instance of our input data having its form defined prior to, what would be more commonly referred to as, the action data.

there’s a couple ways I see you could handle this. The first would be a brute force copy paste into a manual refactoring and storage of the data you want. When it comes to speed, this might be considered quicker with less brain overhead, as the second you choose to parse the input and make sense of it, you’ve more of less added a 3rd programming part to the problem.


others

since we have a guest star today, there is double the review

🤝

[Mudkip/AdventOfCode] python src/data/aoc/2022/data

mert

def parseInputs():
    stacks, instructions = {}, []
    for line in open("5.in", "r").read().splitlines():
        if (line.strip().startswith('1')): continue
        if (line.startswith('move')):
            instructions.append(list((int(x) if x.isdigit() else x for x in line.split(' ')))[1::2])
            continue
        for i in range(0, len(line), 4):
            index = (i//4)+1
            crate = line[i+1:i+2].strip()
            if index not in stacks: stacks[index] = []
            if crate != '': stacks[index].insert(0, crate)
    return stacks, instructions>

ah, yes, my python wizard.

there’s a little bit of python magic here and there, but i like it.

including this function cause not only is it clean, but it’s a good demonstration of the mental overhead that can exist in these coding challenges sometimes. If you’re the golfy type, copy-pasting and letting your text editor do this work would be the speed solution to grab the data structures you want, quickly. vim-max anyone?

jack

yeah, what mert said

regarding python magic, i’ve made some [notes over in the new tips section] regarding my thoughts on list comprehension

here, its being used to keep things super compact, but i hope nobody is doing this sort of thing in production code :)

[briannamcdonald/advent-of-code-2022] python src/data/aoc/2022/data

mert

i LOVE the assumptions and just raw coding the stacks. The mental effort to arriving at a solution feels a lot lower. Not only that, it’s easy for me to understand whats happening.

jack

# parse stacks
stacks = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: []}
offset = 1
for i in range(0, 8):
    line = data[i]
    for j in range(0, 9):
        if line[offset] != ' ':
            stacks[j].append(line[offset])
        offset += 4
    offset = 1

i like this input parsing, the stacks dict could probably be created in a list comprehension, but hey, it works

[krbarter/Advent-Of-Code-2022] python src/data/aoc/2022/data

mert

Nice. Take the input, put it into the form you want cause f&$! parsing it, then do your logical operations.

clean

however, if this WAS an exercise in learning python, which I’m assuming it wasn’t, then I’d be intentional with doing the parsing programitically for learning sake.

jack

but marty, this is about solving problems, and kent be out here solving problems

final = []
for x in crates:
  final.append(x[-1])

this could be a list comprehension :)

final = [x[-1] for x in crates]

much better

sorry marty is doing so well, i can only add little bits of critique

and for someone who warns people about list comprehension usage, i sure do be telling people to use list comprehension

[nint8835/AdventOfCode2022] f# & python src/data/aoc/2022/data

jack

nice

let applyMovement (reverse: bool) (grid: char [] []) (instruction: Instruction) : char [] [] =
    grid
    |> Array.copy
    |> Array.updateAt (instruction.from_col - 1) grid[instruction.from_col - 1].[instruction.count ..]
    |> Array.updateAt
        (instruction.to_col - 1)
        (Array.concat [| grid[instruction.from_col - 1].[0 .. instruction.count - 1]
                         |> (if reverse then Array.rev else id)
                         grid[instruction.to_col - 1] |])

this feels quite like my solutions reducer usage! neat

i see usage of Array.copy here, i assume Array.updateAt potentially mutates the underlying array? therefore you make a copy beforehand?

interesting

mert

f# gives me life.

let instructions =
    inputData[ 1 ].Split "\n"
    |> Array.map (fun instruction -> instruction.Split " ")
    |> Array.map (fun instruction ->
        { count = int instruction[1]
          from_col = int instruction[3]
          to_col = int instruction[5] })

i like looking at this code. its instructional, clean, and to the point.

obligatory jack: “nice”

[hamzahap/AdventOfCode2022] sheets src/data/aoc/2022/data

jack

i thought today would stump hamzah / force him to return to actually programming language land

but alas, he is here, and its glorious

its neat to see a set of columns representing the intermediate states the stacks are in, which makes sense that the memory would be visible in a sheets-based solution

also marty couldn’t find the link to your sheet so he has nothing to say

hey marty if you are reading this you can put up a pr to add something here

[TheCrypticCanadian/advent-of-code-2022] python src/data/aoc/2022/data

mert

COPY PASTA GOD MODE

jack

COPY PASTA GOD MODE

also, not sure if this is my browser, but there seems to be a lot of newlines in these cells making them much bigger than their contents

but yeah

COPY PASTA GOD MODE

(i don’t know what that means but mert is chanting it so i will chant as well)

[STollenaar/AdventOfCode2022] golang src/data/aoc/2022/data

mert

is my brain melting or is it just the go

jack

its just the go, i think this is a decent solution

the utility functions here aren’t exactly that small, but they aren’t exactly busy either

it does result the in main function being pretty simple

the state-based line-by-line input parser isn’t bad

line = strings.ReplaceAll(line, "    ", " ") // WHY 4 SPACES, WHYYYYYY, DAMN CLEANUP

a nice little rage inclusion here from sven as well

nice

[mathieuboudreau/advent] python notebook src/data/aoc/2022/data

mert

YES. GOOD.

stack = {
    1: 'WRF',
    2: 'THMCDVWP',
    3: 'PMZNL',
    4: 'JCHR',
    5: 'CPGHQTB',
    6: 'GCWLFZ',
    7: 'WVLQZJGC',
    8: 'PNRFWTVC',
    9: 'JWHGRSV'
}

jack

good indeed

mathieuboudreau: I didn’t bother parsing the the stacks list in my code, if a user gave me that in this format I’d send it back to them hahaha. Much quicker to simplify it manually than to code it

i like the stance here on user kickback :D

[RyanBrushett/adventofcode2022] ruby src/data/aoc/2022/data

jack

lets see what marty has to say about ruby

mert

i just fell in love with ruby, you will find me in my cave.

jack

yeah

[canetoads.ca] javascript src/data/aoc/2022/data

mert

somewhat cursed, somewhat ok cause javascript does whatever IT PLEASES

jack

stacks[destination].push(...stacks[origin].splice(-n).reverse().reverse()); //hehee

hehee indeed nathan

[apreynolds1989/AdventOfCode2022] typescript src/data/aoc/2022/data

jack

const crateMover9001 = (procedures: number[][], stacks: (string | undefined)[][]) => {
   // Turn on Air Conditioning
   // Admire Leather seats
   // Place ANOTHER coffee in the EXTRA cup holder

where do i get me one of these create mover 9001s this sounds amazing

i could um akctually into another reducer conversation, but i’m just happy to see you are still along for the ride, even after saying you were struggling to parse the file :)

[davidtgillard/advent-of-code] f# src/data/aoc/2022/data

jack

// read the stacks
let stackLines = List.tail preMoveLines
let stacks =
  seq {
    for i in colIndices do
      let mutable col = []
      for line in stackLines do
        if not (Char.IsWhiteSpace(line[i])) then
          col <- line[i]::col
      yield col
  } |> Seq.toList

mutable usage???

just kidding, i like this

usage of seq with what looks to be a nested scope that yields a column being expanded to a list is nice

[EthanDenny/AdventOfCode2022] c++ src/data/aoc/2022/data

mert

could link your helper functions together for code reuse. c++ always makes me happy, the code feels clean and correct

obligatory jack: “nice”

jack

yeah this is nice

pretty not busy

[lilmert/aoc] rust src/data/aoc/2022/data

mert

wait, why did i just look at my own code and hate it? it’s ok. i’ve perused the internet of rust solutions for this one and I didn’t miss much other THAN the mutability crisis i found myself in when trying to grab things from the hashmap. Note to self: if this happens again, it’s almost always the case that a vector is preferred, and simplifies the solution a touch.

jack

i like your code marty <3

the create_stacks function is :clean:

.filter(|(_, c)| c.is_alphabetic())

filtering out the non-alphabetic chars to remove noise is epic

also, matching on which part you’re on to figure out rather to reverse or not, is :clean:

also thanks for taking part in my ramblings, this is fun

[bjbemister19/AdventOfCode] rust src/data/aoc/2022/data

mert

i spy assert!. avoid this, it’s probably an artifact of something else you want to do. [is there a better way?]

yes! the core of iterative tools in rust is the [Iterator] & [itertools], and things built for / around it. You can typically chain your operations around this iterator type and achieve what you want. Also, a reimagination of the problem maybe isn’t splitting at every nth index, but rather splitting at every occurence of an alphabetical character.

[Here’s an option]

jack

look at mert linking to his own rust solution :P

[M-ArafatZaman/AdventOfCode] python & c++ src/data/aoc/2022/data

mert

i don’t love the aesthetics of the code (naming, organization), but it got the job done.

jack

yeah this could be broken up into more utility functions, even just to section code / give names to what is doing what, and then having a main area in which everything is composed

[zcvaters/adventofcode2022] swift src/data/aoc/2022/data

jack

very nice input parsing

and pulling out the reducer on moves to reduce the crates from one state to the next!

its giving functional

[devthedevel/advent_of_code] typescript src/data/aoc/2022/data

jack

Devin: I liked the actual processing of this, but hated the input parsing.

yeah

Devin: Also this is one where i hate that JS/TS has no built in deep clone: i spent the majority of the time trying to find a way to do it without using a lib

easy,

const clonedThing = JSON.parse(JSON.stringify(thing));

look ma, no libaries!

jack, the next day

coming back to make a note: the above was a joke, sorta

technically yes this makes a clone of thing, but this is also dirty & cursed


meta

as covered in [yesterdays meta], my little jack utils page has helped speed up the creation of this document, but today was a bit different anyways with the accidental guest apperance, if this happens again (which i hope it does!), hopefully it’ll be arranged instead :)

i created a page yesterday and didn’t talk about it, but i might as well mention it now, there is a [tips] page, which i am using to collect different recommendations / jackisms / etc. in one place


if you want to have your repo added for me to make a note of / talk about here (or have you repo removed), reach out:

email -> me@jackharrhy.com

discord -> <i>jack arthur null</i>#7539