Advent Of Code 2022 - Day 13

Anyone else think the prompt for this challenge is contradictory?
The rules for comparing packets include

  • If both values are lists, compare the first value of each list, then the second value, and so on. If the left list runs out of items first, the inputs are in the right order. If the right list runs out of items first, the inputs are not in the right order. If the lists are the same length and no comparison makes a decision about the order, continue checking the next part of the input.
  • If exactly one value is an integer, convert the integer to a list which contains that integer as its only value, then retry the comparison. For example, if comparing [0,0,0] and 2, convert the right value to [2] (a list containing 2); the result is then found by instead comparing [0,0,0] and [2].

But if you compare lists according to the rules, [0,0,0] vs [2] should be incorrect order since the right side runs out of items first. But the sample data evaluation we’re told

Compare [2,3,4] vs 4
- Mixed types; convert right to [4] and retry comparison
- Compare [2,3,4] vs [4]
- Compare 2 vs 4
- Left side is smaller, so inputs are in the right order

So the rule for “list vs wrapped integer” is not the same as the rule for “list vs list”. Am I crazy?

I didn’t understand what they meant in Part 2 for longer than I’d like to. But part 1 was super fun to solve.

@stevensonmt
Yeah, it was a bit annoying to get right… but in your example
[0, 0, 0] vs [2] you would compare 0 vs 2 first and left is smaller

Turned out okay, I don’t dare to clean this up more now that it works :sweat_smile:
Code.eval_string saved me from some parsing fun.

1 Like

Nice, looks very clean :slight_smile:

1 Like

I was having a hard time understanding the rules, too. The thing is, when you traverse two lists, you stop as soon as the result is either right or wrong. Only if it stays undecided until one of the lists are out of items, then that rule decides.

Today it felt like an Elixir puzzle again…

Parsing:

parsed =
  input_field
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(&Code.eval_string/1)
  |> Enum.map(&elem(&1, 0))
  |> Enum.chunk_every(2)

Decoder:

defmodule Decoder do
  def compare([], []), do: :none
  def compare([_ | _], []), do: false
  def compare([], [_ | _]), do: true

  def compare([a | resta], [b | restb]) do
    case compare(a, b) do
      :none -> compare(resta, restb)
      other -> other
    end
  end

  def compare(a, b) when is_integer(a) and is_integer(b) do
    cond do
      a < b -> true
      a > b -> false
      true -> :none
    end
  end

  def compare(a, b), do: compare(List.wrap(a), List.wrap(b))
end

Part 1:

parsed
|> Enum.map(fn [a, b] -> Decoder.compare(a, b) end)
|> Enum.with_index(1)
|> Enum.filter(&(elem(&1, 0) == true))
|> Enum.map(&elem(&1, 1))
|> Enum.sum()

Part 2:

sorted =
  [[[[2]], [[6]]] | parsed]
  |> Enum.flat_map(&Function.identity/1)
  |> Enum.sort(&Decoder.compare/2)

divider_packet_1 = Enum.find_index(sorted, &(&1 == [[2]])) + 1
divider_packet_2 = Enum.find_index(sorted, &(&1 == [[6]])) + 1
divider_packet_1 * divider_packet_2
1 Like

I have the same kind of solution, but my compare/2 function returns :lt | :gt | :eq which means that I can call Enum.sort(packets, __MODULE__)

defmodule Aoe.Y22.Day13 do
  alias Aoe.Input

  def read_file!(file, _part) do
    Input.read!(file)
  end

  def parse_input!(input, _part) do
    input
    |> String.trim()
    |> String.split("\n")
    |> Enum.flat_map(&parse_line/1)
  end

  defp parse_line("") do
    []
  end

  defp parse_line(text) do
    [Jason.decode!(text)]
  end

  def part_one(packets) do
    packets
    |> Enum.chunk_every(2)
    |> Enum.map(&compare/1)
    |> Enum.with_index(1)
    |> Enum.filter(fn {order, _} -> order == :lt end)
    |> Enum.reduce(0, fn {_, index}, acc -> acc + index end)
  end

  def part_two(packets) do
    [[[2]], [[6]] | packets]
    |> Enum.sort(__MODULE__)
    |> Enum.with_index(1)
    |> Enum.filter(fn {p, _} -> p == [[6]] or p == [[2]] end)
    |> case(do: ([{_, a}, {_, b}] -> a * b))
  end

  def compare([left, right]) do
    compare(left, right)
  end

  def compare([a | as], [b | bs]) do
    case compare(a, b) do
      :eq -> compare(as, bs)
      other -> other
    end
  end

  def compare([], []) do
    :eq
  end

  def compare([], [_ | _]) do
    :lt
  end

  def compare([_ | _], []) do
    :gt
  end

  def compare(a, b) when is_integer(a) and is_integer(b) do
    cond do
      a < b -> :lt
      a > b -> :gt
      a == b -> :eq
    end
  end

  def compare(a, b) when is_list(a) and is_integer(b) do
    compare(a, [b])
  end

  def compare(a, b) when is_integer(a) and is_list(b) do
    compare([a], b)
  end
end

Every year I start doing AoC in Rust now, but after a week I have no time for it since I have to work, so I fallback on Elixir and then it feels like cheating :smiley:

3 Likes

That’s neat!

Similar to others – made a “sorter” function for Enum.sort/2. Although, doesn’t seem like anyone’s sorter functions are strictly identical which is cool!

Using &Enum.sort/2 in part 2 with absolutely no modifications to the comparator was something else

2 Likes

We did almost the same thing :grin:

1 Like

Was a nice break after the last few days :sweat_smile:

Although I am not proud of my comparison logic…zip wasn’t a good choice due to the need to pad the arrays, pattern matching on head/tail as others did is much better.

I used Code.string_to_quoted since lists and integers are both conveniently represented as themselves in AST, and it seems less likely to release nasal demons than Code.eval_string.

I really like how the definition of compare winds up pretty much matching the paragraphs in the problem statement; for instance, the last two clauses perfectly encapsulate the “integer vs list” rule.

1 Like