deadbeef

deadbeef

Similar approach to others. Think I just organized parsing/solving a bit different.
I end up committing regex for text splitting, but commented non-regex options (e.g. patterned matched the moves originally)

https://github.com/ed-flanagan/advent-of-code-solutions-elixir/blob/main/lib/advent/y2022/d05.ex

0
Post #11
deadbeef

deadbeef

I wondered the same thing about simplifying peek/drop/push.

I ended up doing something like this... 
crates = # current state: %{column_idx => [crate]}
[count, from, to] = # parsed: "move <count> from <from> to <to>"
{to_move, crates} = Map.get_and_update(crates, from, &Enum.split(&1, count))
# Reverse is for part 1 only, remove for part 2
Map.update(crates, to, [], &(Enum.reverse(to_move) ++ &1))
0
Post #12
hauleth

hauleth

Lenses in general aren’t really popular outside of the “hard core FP”.

My solution:

https://github.com/hauleth/advent-of-code/blob/master/2022/day05.livemd

1
Post #13
faried

faried

I didn’t enjoy the parsing process. Here’s my code for it:

  def read_input(fname) do
    [crates, moves] =
      File.read!("priv/#{fname}")
      |> String.split("\n\n", trim: true)

    {to_map(crates), to_cmdlist(moves)}
  end

  # input -> list of rows
  # each row:
  # - split into list of single-character strings
  # - drop the [ and ] characters
  # - filter out "empty" crates
  # - add index to turn it into {crate, column} tuples
  # use flatmap to turn it into a map
  # - the key is the column (1, 2, ...)
  # - value is a list of crates in that column 
  defp to_map(crates) do
    String.split(crates, "\n", trim: true)
    |> Enum.flat_map(fn row ->
      String.graphemes(row)
      |> Enum.drop_every(2)
      |> Enum.with_index()
      |> Enum.filter(fn {crate, _} -> crate != " " end)
    end)
    |> Enum.reverse()
    |> Enum.reduce(%{}, fn {crate, col}, cratesmap ->
      Map.update(cratesmap, div(col, 2) + 1, [crate], fn existing -> [crate | existing] end)
    end)
  end

  defp to_cmdlist(moves) do
    moves
    |> String.split("\n", trim: true)
    |> Enum.map(fn movestr ->
      [_, count, _, from, _, to] = String.split(movestr, " ", trim: true)
      {String.to_integer(count), String.to_integer(from), String.to_integer(to)}
    end)
  end

Moving the crates was more work than necessary:

  def part1({crates, commands}) do
    Enum.reduce(commands, crates, fn {count, from, to}, crates ->
      {take, remain} = Enum.split(crates[from], count)

      Map.put(crates, from, remain)
      |> Map.put(to, Enum.reverse(take) ++ crates[to])
    end)
    |> top_crates()
  end

  def part2({crates, commands}) do
    Enum.reduce(commands, crates, fn {count, from, to}, crates ->
      {take, remain} = Enum.split(crates[from], count)

      Map.put(crates, from, remain)
      |> Map.put(to, take ++ crates[to])
    end)
    |> top_crates()
  end

  defp top_crates(crates) do
    Map.keys(crates)
    |> Enum.sort()
    |> Enum.map(fn col -> hd(crates[col]) end)
    |> Enum.join("")
  end
1
Post #14
Aetherus

Aetherus

I spent much more time parsing the input than moving the crates :sweat_smile:

defmodule Day05 do
  def part1(input_path) do
    {stacks, instructions} = parse_input(input_path)

    instructions
    |> Enum.reduce(stacks, fn instruction, stacks ->
      move1(stacks, instruction.from, instruction.to, instruction.amount)
    end)
    |> Enum.sort()
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&hd/1)
  end

  def part2(input_path) do
    {stacks, instructions} = parse_input(input_path)

    instructions
    |> Enum.reduce(stacks, fn instruction, stacks ->
      move2(stacks, instruction.from, instruction.to, instruction.amount)
    end)
    |> Enum.sort()
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&hd/1)
  end

  defp parse_input(input_path) do
    [stacks_part, instructions_part] =
      input_path
      |> File.read!()
      |> String.split("\n\n", parts: 2, trim: true)

    {build_stacks(stacks_part), build_instructions(instructions_part)}
  end

  defp build_stacks(stacks_part) do
    stacks_part
    |> String.split("\n", trim: true)
    |> Enum.reverse()
    |> tl()
    |> Enum.map(&to_layer(&1, []))
    |> Enum.reduce(%{}, &push_layer/2)
  end

  defp to_layer("", acc), do: Enum.reverse(acc)
  defp to_layer("    " <> rest, acc), do: to_layer(rest, [?_ | acc])
  defp to_layer("   " <> rest, acc), do: to_layer(rest, [?_ | acc])
  defp to_layer(<<?[, char, ?], ?\s, rest::binary>>, acc), do: to_layer(rest, [char | acc])
  defp to_layer(<<?[, char, ?], rest::binary>>, acc), do: to_layer(rest, [char | acc])

  defp push_layer(layer, stacks) do
    layer
    |> Enum.with_index(1)
    |> Enum.reduce(stacks, fn
      {?_, _}, stacks -> stacks
      {char, i}, stacks -> Map.update(stacks, i, [char], &[char | &1])
    end)
  end

  defp build_instructions(instructions_part) do
    ~r/\d+/m
    |> Regex.scan(instructions_part)
    |> List.flatten()
    |> Enum.map(&String.to_integer/1)
    |> Enum.chunk_every(3)
    |> Enum.map(fn [amount, from, to] ->
      %{amount: amount, from: from, to: to}
    end)
  end

  defp move1(stacks, from, to, amount) do
    {from_stack, to_stack} = for _ <- 1..amount, reduce: {stacks[from], stacks[to]} do
      {[h | t], to_stack} -> {t, [h | to_stack]}
    end

    %{stacks | from => from_stack, to => to_stack}
  end

  defp move2(stacks, from, to, amount) do
    {to_move, from_stack} = Enum.split(stacks[from], amount)
    to_stack = to_move ++ stacks[to]

    %{stacks | from => from_stack, to => to_stack}
  end
end
0
Post #15
LostKobrakai

LostKobrakai

I certainly overengieneered this, as I created a struct for holding the state and having an API to call, transforming from and to text (for debugging). But that also meant part 2 was actually just taking away from the existing solution :smiley:

Solution 
defmodule Day5 do
  defmodule State do
    defstruct stacks: %{}

    @col_size 3

    def from_text(text) do
      lines = String.split(text, "\n")
      {labels_line, stack_lines} = List.pop_at(lines, -1)
      labels = parse_columns(" ", labels_line)
      stack_columns = Enum.map(stack_lines, &parse_columns([" ", "[", "]"], &1))

      stacks =
        stack_columns
        |> Enum.reverse()
        |> Enum.reduce(Enum.map(labels, &{&1, []}), fn line, stack ->
          Enum.zip(stack, line)
          |> Enum.map(fn
            {col, ""} -> col
            {{label, stack}, cargo} -> {label, [cargo | stack]}
          end)
        end)

      %__MODULE__{stacks: Map.new(stacks)}
    end

    defp parse_columns(trim, <<text::binary-size(@col_size)>>) do
      [trim(text, trim)]
    end

    defp parse_columns(trim, <<text::binary-size(@col_size), " ", rest::binary>>) do
      [trim(text, trim) | parse_columns(trim, rest)]
    end

    defp trim(string, to_trim) do
      to_trim
      |> List.wrap()
      |> Enum.reduce(string, &String.trim(&2, &1))
    end

    def move(%__MODULE__{stacks: stacks}, from, to, num) do
      {to_move, stacks} =
        Map.get_and_update!(stacks, from, fn list ->
          Enum.split(list, num)
        end)

      to_put = Enum.reverse(to_move)

      stacks = Map.update!(stacks, to, fn current -> to_put ++ current end)
      %__MODULE__{stacks: stacks}
    end

    def move_alternate(%__MODULE__{stacks: stacks}, from, to, num) do
      {to_move, stacks} =
        Map.get_and_update!(stacks, from, fn list ->
          Enum.split(list, num)
        end)

      stacks = Map.update!(stacks, to, fn current -> to_move ++ current end)
      %__MODULE__{stacks: stacks}
    end

    def id(%__MODULE__{stacks: stacks}) do
      stacks
      |> Map.values()
      |> Enum.map_join("", &List.first/1)
    end

    defimpl Inspect do
      def inspect(%_{stacks: stacks}, _) do
        stack_lists = Map.values(stacks)
        height = stack_lists |> Enum.map(&length/1) |> Enum.max()
        indexes = -height..-1//1

        lines =
          Enum.map_join(indexes, "\n", fn index ->
            Enum.map_join(stack_lists, " ", fn stack ->
              case Enum.at(stack, index) do
                nil -> "   "
                cargo -> "[#{cargo}]"
              end
            end)
          end)

        labels = stacks |> Map.keys() |> Enum.map_join(" ", fn label -> " #{label} " end)

        text = lines <> "\n" <> labels

        """
        Day5.State.from_text(\"\"\"
        #{text}
        \"\"\")
        """
      end
    end
  end

  def parse(text) do
    [starting_stacks, procedure] = String.split(text, "\n\n", parts: 2)
    state = State.from_text(starting_stacks)

    operations =
      ~r/^move (\d+) from (.) to (.)$/m
      |> Regex.scan(procedure)
      |> Enum.map(fn [_, num, from, to] ->
        %{num: String.to_integer(num), from: from, to: to}
      end)

    {state, operations}
  end

  def run_operations(text) do
    {state, operations} = parse(text)

    operations
    |> Enum.reduce(state, fn operation, state ->
      State.move(state, operation.from, operation.to, operation.num)
    end)
    |> State.id()
  end

  def run_operations_alternate(text) do
    {state, operations} = parse(text)

    operations
    |> Enum.reduce(state, fn operation, state ->
      State.move_alternate(state, operation.from, operation.to, operation.num)
    end)
    |> State.id()
  end
end
1
Post #16
Arzar

Arzar

Getting harder. I’m using Advent of Code to get more familiar with Elixir and functional language and until now it was very pleasant thanks to Elixir amazing stdlib.

But I’m hitting the conceptual wall today. An operation thas is trivial in many languages (pop a few item from a container, put them in another) became incredibly difficult with Elixir and its immutable structure. I spent hours trying to figure out how to do that, only to end up with a bunch of hard to understand Enum.reduce.
Going to study the other answers in this thread for sure!

Day5 
the_crates = [
  ["P", "Z", "M", "T", "R", "C", "N"],
  ["Z", "B", "S", "T", "N", "D"],
  ["G", "T", "C", "F", "R", "Q", "H", "M"],
  ["Z", "R", "G"],
  ["H", "R", "N", "Z"],
  ["D", "L", "Z", "P", "W", "S", "H", "F"],
  ["M", "G", "C", "R", "Z", "D", "W"],
  ["Q", "Z", "W", "H", "L", "F", "J", "S"],
  ["N", "W", "P", "Q", "S"]
]

defmodule Day5 do
  def move_crates(crates, quantity, from, to) do
    from = from - 1
    to = to - 1
    {crates_to_move, remaining} = Enum.at(crates, from)  |> Enum.split(quantity)
    crates
    |> Enum.with_index
    |> Enum.reduce([], fn {stack, index}, acc ->
      case index do
        ^from -> acc ++ [remaining]
        # part 1
        ^to -> acc ++ [Enum.concat(Enum.reverse(crates_to_move), stack)]
        # part 2
        #^to -> acc ++ [Enum.concat(crates_to_move, stack)]
        _ -> acc ++ [stack]
       end
    end)
  end
end


File.stream!("input05")
|> Stream.map(fn line ->
     String.split(line, ["move","from"," to"], trim: true)
     |> Enum.map(&String.trim/1)
     |> Enum.map(&String.to_integer/1)
   end)
|> Enum.reduce(the_crates, fn [quantity, from, to], acc ->
     Day5.move_crates(acc, quantity, from, to)
    end)
|> Enum.map(&hd/1)
|> Enum.join("")
|> IO.puts
1
Post #17
hst337

hst337

One more linear binary scanning day

defmodule AOC do

  import :erlang, only: [setelement: 3, element: 2]

  def run(input) do
    {towers, commands_str} = traverse_towers(input)
    IO.inspect(towers)
    towers = traverse_commands(commands_str, towers)
    to_string Enum.map(Tuple.to_list(towers), fn [head | _] -> head end)
  end

  defp index_for(?\s, index), do: index + 1
  defp index_for(?\n, index), do: 1

  def traverse_towers(string, index \\ 1, towers \\ {}) do
    case string do
      <<"\nmove ", tail :: binary>> ->
        {towers, tail}

      <<"   ", delimiter, tail :: binary>> ->
        towers =
          with towers when tuple_size(towers) < index <- towers do
            Tuple.append(towers, [])
          end
        traverse_towers(tail, index_for(delimiter, index), towers)

      <<?[, char, ?], delimiter, tail :: binary>> ->
        towers =
          case towers do
            towers when tuple_size(towers) < index ->
              Tuple.append(towers, [char])

            _ ->
              tower = element(index, towers)
              setelement(index, towers, [char | tower])
          end

        traverse_towers(tail, index_for(delimiter, index), towers)

      <<" ", index_char, " ", _, tail :: binary>> when index_char in ?0..?9 ->
        tower = element(index, towers)
        tower = :lists.reverse(tower)
        towers = setelement(index, towers, tower)
        traverse_towers(tail, index + 1, towers)
    end
  end

  defp traverse_commands(string, towers, number \\ 0, acc \\ [])
  defp traverse_commands("", towers, 0, [to, from, how_much]) do
    move(towers, how_much, from, to)
  end
  defp traverse_commands(string, towers, 0, [to, from, how_much] = acc) do
    towers = move(towers, how_much, from, to)
    traverse_commands(string, towers, 0, [])
  end
  defp traverse_commands(string, towers, number, acc) do
    case string do
      <<"\nmove ", tail :: binary>> ->
        traverse_commands(tail, towers, 0, [number | acc])

      <<" from ", tail :: binary>> ->
        traverse_commands(tail, towers, 0, [number | acc])

      <<" to ", tail :: binary>> ->
        traverse_commands(tail, towers, 0, [number | acc])

      "\n" ->
        traverse_commands("", towers, 0, [number | acc])

      <<char, tail :: binary>> ->
        # IO.inspect <<char>>, label: :char
        traverse_commands(tail, towers, number * 10 + char - ?0, acc)

      "" ->
        traverse_commands("", towers, 0, [number | acc])
    end
  end

  defp move(towers, how_much, from_index, to_index) do
    to = element(to_index, towers)
    from = element(from_index, towers)
    {moving, from} = :lists..split(how_much, from)
    to = moving ++ to

    towers = setelement(to_index, towers, to)
    setelement(from_index, towers, from)
  end

end

IO.inspect AOC.run IO.read :eof
0
Post #18
hst337

hst337

And it is also extremely unobvious and unsupported feature. Performance, structures, lenses as lists, introspection, Access.key etc.

0
Post #19
daskycodes

daskycodes 

defmodule Day5 do
  def input do
    [stacks, instructions] = File.read!("input5.txt") |> String.split("\n\n", trim: true)

    stacks = map_stacks(stacks)
    instructions = map_instructions(instructions)

    [stacks, instructions]
  end

  def part1 do
    [stacks, instructions] = input()

    Enum.reduce(instructions, stacks, fn [count, from, to] , acc ->
      to_move = Enum.at(acc, from - 1) |> Enum.take(count) |> Enum.reverse()

      List.update_at(acc, from - 1, fn x -> Enum.drop(x, count) end)
      |> List.update_at(to - 1, fn x -> to_move ++ x end)
    end)
    |> Enum.map(&List.first/1)
    |> Enum.join("")
  end

  def part2 do
    [stacks, instructions] = input()

    Enum.reduce(instructions, stacks, fn [count, from, to] , acc ->
      to_move = Enum.at(acc, from - 1) |> Enum.take(count)

      List.update_at(acc, from - 1, fn x -> Enum.drop(x, count) end)
      |> List.update_at(to - 1, fn x -> to_move ++ x end)
    end)
    |> Enum.map(&List.first/1)
    |> Enum.join("")
  end

  @doc """
    Maps the given rows of stacks to columns, and removes the numbers

    ## Example

      iex> Day5.map_stacks("   [D]    \\n[N] [C]    \\n[Z] [M] [P]\\n 1   2   3 ")
      [["N", "Z"], ["D", "C", "M"], ["P"]]
  """
  def map_stacks(stacks) do
    stacks
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      row
      |> String.split("", trim: true)
      |> Enum.chunk_every(4)
      |> Enum.map(fn item -> Enum.take(item, 3) |> Enum.at(1) end)
    end)
    |> Enum.reverse()
    |> Enum.drop(1)
    |> Enum.reverse()
    |> Enum.zip_with(& &1)
    |> Enum.map(&Enum.reject(&1, fn x -> x == " " end))
  end

  @doc """
    Maps the instructions to a list of numbers [count, from, to]

    ## Example

      iex> Day5.map_instructions("move 1 from 2 to 1\\nmove 3 from 1 to 3\\nmove 2 from 2 to 1\\nmove 1 from 1 to 2\\n")
      [[1, 2, 1], [3, 1, 3], [2, 2, 1], [1, 1, 2]]
  """
  def map_instructions(instructions) do
    instructions
    |> String.split("\n", trim: true)
    |> Enum.map(fn instruction ->
      instruction
      |> String.trim_leading("move ")
      |> String.replace("from ", "")
      |> String.replace("to ", "")
      |> String.split(" ")
      |> Enum.map(&String.to_integer/1)
    end)
  end
end
0
Post #20