Aetherus

Aetherus

Advent of Code 2023 - Day 8

The second part of today’s puzzle is very misleading.

FYI, each of the ghosts has only one possible position that ends with a "Z" on its path.

Most Liked

shritesh

shritesh

I tried letting it run for 5 minutes in part 2 hoping it would work out. It didn’t lol.

Turns out elixir already has gcd in the stdlib: Integer — Elixir v1.15.7. Then the LCM simply becomes div(x * y, Integer.gcd(x, y))

rugyoga

rugyoga

import AOC
import Math

aoc 2023, 8 do
  def p1(input) do
    {instructions, map} = parse(input)
    traverse("AAA", map, 0, instructions, instructions, &(&1 == "ZZZ"))
  end

  def extract(s) do
    <<key::binary-size(3), " = (", left::binary-size(3), ", ", right::binary-size(3), ")">> = s
    {key, {left, right}}
  end

  def traverse(key, map, n, [], path, finished?), do: traverse(key, map, n, path, path, finished?)
  def traverse(key, map, n, [move | moves], path, finished?) do
    if finished?.(key) do
      n
    else
      {left, right} = map[key]
      case move do
        "L" -> traverse(left, map, n+1, moves, path, finished?)
        _ -> traverse(right, map, n+1, moves, path, finished?)
      end
    end
  end

  def parse(input) do
    [instructions_str, map_str] = input |> String.split("\n\n")
    {instructions_str |> String.split("", trim: true),
     map_str |> String.split("\n") |> Enum.map(&extract/1) |> Map.new}
  end

  def p2(input) do
    {moves, map} = parse(input)
    map
    |> Map.keys()
    |> Enum.filter(&String.ends_with?(&1, "A"))
    |> Enum.map(fn start -> traverse(start, map, 0, moves, moves, &String.ends_with?(&1, "Z")) end)
    |> Enum.reduce(1, &lcm/2)
  end
end
bjorng

bjorng

Erlang Core Team

I suspected that was the case, but burned by previous AoC puzzles by making assumptions not explicitly stated in the problem descriptions, I now try to avoid simplifying my code based on assumptions. For this puzzle, I tested all possible end positions for each start positions.

My solution:

seeplusplus

seeplusplus

FYI, each of the ghosts has only one possible position that ends with a "Z" on its path.

Edit: I was thinking about this and this post might not be true because entry node could leave the terminal node in different ways (by being on different steps and exiting either left or right). I thought about removing this post, but maybe someone will find interesting. Skip past if you don’t like sort-of-wrong explanations of things.

A bit of graph theory, if folks are interested: the instructions induce a set of disjoint cyclic graphs (in non-graph theory speak the entry “room”, its exit, and all of the rooms in between form a ring). So, each entry node (read, node that ends in A) belongs to some cycle that also contains a terminating node (read, node that ends in a Z). Otherwise the problem wouldn’t be solvable.

What may not be obvious, is if any cycle contains more than one terminating node (if any room could reach multiple exits) it must also contain an additional entry node. That is, if any entry node could reach more than one terminating node then there exists at least one other entry node that can also reach both.

If such a cycle existed (one that contains two terminating nodes), the only way this problem would be solvable is if both entry nodes reach their corresponding terminus in the same number of steps.

Visually, this a graph like this would never be solvable:

stevensonmt

stevensonmt

Reading the discussion makes me think I just got lucky making an assumption about the paths cycling. I think the assumption must hold that all paths will cycle IF every starting node actually can reach a finishing node AND every finishing node can be reached by a starting node. As long as that is true the LCM approach should be valid, I think. Can anyone think of a counter example where the cycle of the instructions and the length of at least one path from start to finish are such you could endlessly cycle with out ever repeating?

Here’s my code:

  defmodule Parser do
    import NimbleParsec

    instructions = times(choice([ascii_char([?L]), ascii_char([?R])]), min: 1) |> wrap()

    node = times(ascii_char([?A..?Z]), 3) |> wrap()

    parent = node |> ignore(string(" = "))

    children =
      ignore(string("("))
      |> concat(node)
      |> ignore(string(", "))
      |> concat(node)
      |> ignore(string(")\n"))

    tree =
      ignore(string("\n\n"))
      |> times(parent |> concat(children) |> wrap(), min: 1)

    defparsec(:parser, instructions |> concat(tree))
  end

# it was probably overkill to use NimbleParsec on this input, but I'm trying to get more comfortable with it. Starting to feel a little more intuitive.

defmodule Day8 do
  @moduledoc """
  Day8 AoC Solutions
  """

  alias AocToolbox.Input

  def input(:test),
    do: """
    RL

    AAA = (BBB, CCC)
    BBB = (DDD, EEE)
    CCC = (ZZZ, GGG)
    DDD = (DDD, DDD)
    EEE = (EEE, EEE)
    GGG = (GGG, GGG)
    ZZZ = (ZZZ, ZZZ)
    """

  def input(:test2),
    do: """
    LLR

    AAA = (BBB, BBB)
    BBB = (AAA, ZZZ)
    ZZZ = (ZZZ, ZZZ)
    """

  def input(:test3),
    do: """
    LR

    11A = (11B, XXX)
    11B = (XXX, 11Z)
    11Z = (11B, XXX)
    22A = (22B, XXX)
    22B = (22C, 22C)
    22C = (22Z, 22Z)
    22Z = (22B, 22B)
    XXX = (XXX, XXX)
    """

  def input(:real), do: Input.load(__DIR__ <> "/input.txt")

  def solve(1, mode) do
    __MODULE__.Part1.solve(input(mode))
  end

  def solve(2, mode) do
    __MODULE__.Part2.solve(input(mode))
  end

  defmodule Part1 do
    def solve(input) do
      input
      |> parse()
      |> build_tree()
      |> traverse_instructions()
    end

    def parse(input) do
      input
      |> Input.Parser.parser()
      |> elem(1)
    end

    defp build_tree([instructions | tree]) do
      graph =
        tree
        |> Enum.reduce(%{}, fn [p, l, r], acc ->
          Map.put(acc, p, %{~c"L" => l, ~c"R" => r})
        end)

      {instructions, graph}
    end

    defp traverse_instructions({instructions, tree}) do
      instructions
      |> Stream.cycle()
      |> Enum.reduce_while({~c"AAA", 0}, fn direction, {key, steps} ->
        if key == ~c"ZZZ" do
          {:halt, steps}
        else
          {:cont, {tree[key][[direction]], steps + 1}}
        end
      end)
    end
  end

  defmodule Part2 do
    def solve(input) do
      input
      |> parse()
      |> build_tree()
      |> traverse_all_starting_nodes()
      |> Enum.reduce(&AocToolbox.Math.lcm(&1, &2))
    end

    defp parse(input) do
      Part1.parse(input)
    end

    defp build_tree([instructions | tree]) do
      {instructions,
       tree
       |> Enum.reduce({%{}, MapSet.new()}, fn
         [[_, _, ?A] = p, l, r], {graph, starters} ->
           {Map.put(graph, p, %{~c"L" => l, ~c"R" => r}), MapSet.put(starters, p)}

         [p, l, r], {graph, starters} ->
           {Map.put(graph, p, %{~c"L" => l, ~c"R" => r}), starters}
       end)}
    end

    defp traverse_all_starting_nodes({instructions, {tree, starters}}) do
      starters
      |> Task.async_stream(fn starter ->
        instructions
        |> Stream.cycle()
        |> Enum.reduce_while({starter, 0}, fn direction, {key, steps} ->
          if match?([_, _, ?Z], key) do
            {:halt, steps}
          else
            {:cont, {tree[key][[direction]], steps + 1}}
          end
        end)
      end)
      |> Enum.map(&elem(&1, 1))
    end
  end
end

Where Next?

Popular in Challenges Top

bjorng
This topic is about Day 10 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums ): https://adv...
New
stevensonmt
Trying to get more facility with dynamic programming concepts on Leetcode and having an issue I can’t find a way around. It’s a chutes an...
New
dominicletz
This topic is about Day 8 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
Aetherus
The second part of today’s puzzle is very misleading. FYI, each of the ghosts has only one possible position that ends with a "Z" on its...
New
bjorng
This topic is about Day 2 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adven...
New
cblavier
Hey there :wave: No magic or algorithmic finesse today, I just finished the challenge and I my code is quite slow (1sec for part1, 3se...
New
bjorng
Here is my solution for day 1 of Advent of Code: defmodule Day01 do def part1(input) do all = parse(input) {first, second} = E...
New
Aetherus
Today’s problem is really tense. I don’t think I can do it without libgraph.
New
bjorng
Note: This topic is to talk about Day 4 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
cblavier
Hi, there :wave: Today, I felt it was way more challenging! I went through part2 thanks to Agent based memoization (without memoization ...
New

Other popular topics Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement