Powered by AppSignal & Oban Pro

Day 5: Supply Stacks -- AoC 22

2022/05.livemd

Day 5: Supply Stacks -- AoC 22

stream = File.stream!("/Users/pw/src/weiland/adventofcode/2022/input/05.txt")

Part One

build = fn line, [stack, moves] ->
  line = line |> String.replace("\n", "")

  if String.starts_with?(line, "move") do
    [stack, [line | moves]]
  else
    [[line | stack], moves]
  end
end

[stack, moves] =
  stream
  |> Enum.reduce([[], []], build)

parse_stack = fn line ->
  Regex.scan(~r/\[([A-Z\d])\]|\s{9}/, line, capture: :all_but_first)
  # |> Enum.flat_map(fn el -> el end)
  |> Enum.map(fn [el] -> el end)
end

stack =
  stack
  # a bit stateful and hacky :see_no_evil:
  |> Enum.map(&String.replace(&1, "    ", " [0]"))
  # skip empty and the numbers (for now)
  |> Enum.drop(2)
  |> Enum.map(parse_stack)
  # transpose
  |> List.zip()
  |> Enum.map(&Tuple.to_list/1)
  |> Enum.map(&Enum.filter(&1, fn el -> el != "0" end))
parse_moves = fn line ->
  [res] = Regex.scan(~r/move ([\d]+) from ([\d]+) to ([\d]+)/, line, capture: :all_but_first)

  res
  |> Enum.map(&String.to_integer(&1))
end

moves =
  moves
  |> Enum.map(parse_moves)
  |> Enum.reverse()
expand_move = fn move ->
  [amount, from, to] = move

  cond do
    amount > 1 -> List.duplicate([1, from, to], amount)
    true -> [move]
  end
end

moves1 =
  moves
  |> Enum.flat_map(expand_move)
exec_move = fn stack, from, to ->
  [ilist] =
    stack
    |> Enum.drop(from - 1)
    |> Enum.take(1)

  [elem | tail] = ilist |> Enum.reverse()

  stack
  |> Enum.with_index(1)
  |> Enum.map(fn {list, index} ->
    cond do
      index == from -> Enum.reverse(tail)
      index == to -> Enum.reverse([elem | Enum.reverse(list)])
      true -> list
    end
  end)
end

moves1
|> Enum.reduce(stack, fn [_a, from, to], acc -> exec_move.(acc, from, to) end)
|> Enum.map(fn list -> List.last(list) end)
|> Enum.filter(fn item -> item != nil end)
|> List.to_string()

Part Two

exec_move2 = fn stack, amount, from, to ->
  [ilist] =
    stack
    |> Enum.drop(from - 1)
    |> Enum.take(1)

  ilist = ilist |> Enum.reverse()
  elems = Enum.take(ilist, amount)
  tail = Enum.drop(ilist, amount)

  stack
  |> Enum.with_index(1)
  |> Enum.map(fn {list, index} ->
    cond do
      index == from -> Enum.reverse(tail)
      index == to -> Enum.reverse(Enum.concat(elems, Enum.reverse(list)))
      true -> list
    end
  end)
end

moves
|> Enum.reduce(stack, fn [amount, from, to], acc -> exec_move2.(acc, amount, from, to) end)
|> Enum.map(fn list -> List.last(list) end)
|> Enum.filter(fn item -> item != nil end)
|> List.to_string()

Code from exec_move2 can also sove Part One.

moves1
|> Enum.reduce(stack, fn [_a, from, to], acc -> exec_move2.(acc, 1, from, to) end)
|> Enum.map(fn list -> List.last(list) end)
|> Enum.filter(fn item -> item != nil end)
|> List.to_string()