Advent of Code 2025 - Day 7

Part 1 took much more time than part 2. I started out by reusing my grid parsing function from day 4 and start coding before I had fully understood the splitting rules and how to count splits. It ended up in a mess.

After having finished part 2, I rewrote part 1 using the new parsing routine that I wrote for part 2.

defmodule Day07 do
  def part1(input) do
    {start, splitters} = parse_splitters(input)
    beams = [start]
    split_beams(beams, splitters, 0)
  end

  defp split_beams(_beams, [], num_splits), do: num_splits
  defp split_beams(beams, [splits | splitters], num_splits) do
    {beams, num_splits} = split_beams(beams, splits, [], num_splits)
    split_beams(beams, splitters, num_splits)
  end

  defp split_beams([], _splitters, new, num_splits) do
    {Enum.uniq(new), num_splits}
  end
  defp split_beams([column | beams], splits, new, num_splits) do
    case Enum.member?(splits, column) do
      true ->
        new = [column - 1, column + 1 | new]
        split_beams(beams, splits, new, num_splits + 1)
      false ->
        new = [column | new]
        split_beams(beams, splits, new, num_splits)
    end
  end

  def part2(input) do
    {start, splitters} = parse_splitters(input)
    {timelines, _} = count_timelines(start, splitters, %{})
    timelines
  end

  defp count_timelines(column, splitters, worlds) do
    key = {column, length(splitters)}
    case worlds do
      %{^key => timelines} ->
        {timelines, worlds}
      %{} ->
        {timelines, worlds} = count_timelines_split(column, splitters, worlds)
        {timelines, Map.put(worlds, key, timelines)}
    end
  end

  defp count_timelines_split(_column, [], worlds) do
    {1, worlds}
  end
  defp count_timelines_split(column, [splits | splitters], worlds) do
    case Enum.member?(splits, column) do
      true ->
        {timelines1, worlds} = count_timelines(column - 1, splitters, worlds)
        {timelines2, worlds} = count_timelines(column + 1, splitters, worlds)
        timelines = timelines1 + timelines2
        {timelines, worlds}
      false ->
        count_timelines(column, splitters, worlds)
    end
  end

  defp parse_splitters(input) do
    splitters = input
    |> Enum.map(fn line ->
      String.to_charlist(line)
      |> Enum.with_index
      |> Enum.flat_map(fn {char, col} ->
        case char do
          ?. -> []
          ?S -> [{:start, col}]
          ?^ -> [col]
        end
      end)
    end)
    [[{:start, start}] | splitters] = splitters
    {start, splitters}
  end
end

EDIT: Further simplified part 1 by removing vestiges of my messy first solution.

2 Likes
#!/usr/bin/env elixir

# Advent of Code 2025. Day 7

defmodule M do
  def go_downward_1(_map, row, _cols, max_row, n_split) when row > max_row, do: n_split
  def go_downward_1(map, row, cols, max_row, n_split) do
    {cols, n_split} = Enum.reduce(cols, {cols, n_split}, fn col, {cols, n_split} = acc ->
      case Map.get(map, {row+1, col}) do
        nil  -> acc
        true -> {cols |> MapSet.delete(col) |> MapSet.put(col-1) |> MapSet.put(col+1), n_split+1}
      end
    end)
    go_downward_1(map, row+1, cols, max_row, n_split)
  end

  def go_downward_2(_map, row, cols, max_row) when row > max_row, do: cols
  def go_downward_2(map, row, cols, max_row) do
    cols = Enum.reduce(cols, cols, fn {col, n}, cols ->
      case Map.get(map, {row+1, col}) do
        nil  -> cols
        true -> cols |> Map.delete(col) |> Map.update(col-1, n, & &1+n) |> Map.update(col+1, n, & &1+n)
      end
    end)
    go_downward_2(map, row+1, cols, max_row)
  end
end

# Setup
{map, {start_row, start_col}, max_row} = File.read!("../day07.txt")
  |> String.split("\n")
  |> Enum.with_index(1)
  |> Enum.reduce({%{}, nil, 0}, fn {line, row}, {map, start, max_row} ->
    line
    |> String.codepoints()
    |> Enum.with_index(1)
    |> Enum.reduce({map, start, max_row}, fn
      {"S", col}, {map, nil, max_row} -> {map, {row,col}, (if row>max_row, do: row, else: max_row)}
      {"^", col}, {map, start, max_row} -> {Map.put(map, {row,col}, true), start, (if row>max_row, do: row, else: max_row)}
      {".", _col}, {map, start, max_row} -> {map, start, (if row>max_row, do: row, else: max_row)}
    end)
  end)

# Part 1
n_split = M.go_downward_1(map, start_row, MapSet.new([start_col]), max_row, 0)
IO.puts "Day 7. Part 1: #{n_split}"

# Part 2
cols = M.go_downward_2(map, start_row, %{start_col => 1}, max_row)
IO.puts "Day 7. Part 2: #{Enum.reduce(cols, 0, fn {_col,n}, total -> total+n end)}"

Simplified after doing Part2 because Part2 solves also Part1.

defmodule AdventOfCode.Solution.Year2025.Day07 do
  def part1(input), do: input |> fire_beam() |> take_counter()
  def part2(input), do: input |> fire_beam() |> take_timelines() |> Map.values() |> Enum.sum()
  def take_counter(a), do: elem(a, 1)
  def take_timelines(a), do: elem(a, 0)

  def fire_beam(input) do
    [[start] | rows] = parse(input)
    beam(Map.new([{start, 1}]), 0, rows)
  end

  def beam(in_flight, hits_counter, []), do: {in_flight, hits_counter}

  def beam(in_flight, hits_counter, [next_row_splitters | rows]) do
    # in_flight %{column => number of timelines leading to this point}
    # hits_counter : number of hits on a splitter
    {new_in_flight, new_hits} =
      Enum.reduce(in_flight, {%{}, 0}, fn {col, n_timelines}, {next_positions, hits} ->
        if col in next_row_splitters,
          do: {
            next_positions
            |> Map.update(col - 1, n_timelines, &(&1 + n_timelines))
            |> Map.update(col + 1, n_timelines, &(&1 + n_timelines)),
            hits + 1
          },
          else: {Map.update(next_positions, col, n_timelines, &(&1 + n_timelines)), hits}
      end)

    beam(new_in_flight, hits_counter + new_hits, rows)
  end

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.reduce([], fn line, lines ->
      splitter_cols =
        to_charlist(line)
        |> Enum.with_index()
        |> Enum.reduce([], fn
          {?., _}, acc -> acc
          {?^, col}, l -> [col | l]
          {?S, col}, l -> [col | l]
        end)

      if splitter_cols == [], do: lines, else: [splitter_cols | lines]
    end)
    |> Enum.reverse()
  end
end

1 Like

Nice problem - you can solve both part simultaneously if you keep track of everything.

defmodule Y2025.Day07 do
  def parse(s) do
    {[first], rest} = s |> String.split("\n") |> Enum.split(1)

    start = Enum.find_index(first |> String.graphemes(), &(&1 == "S"))

    n = String.length(first)

    rest =
      rest
      |> Enum.map(fn line ->
        line
        |> String.graphemes()
        |> Enum.with_index()
        |> Enum.filter(&(elem(&1, 0) == "^"))
        |> Enum.map(&elem(&1, 1))
        |> MapSet.new()
      end)
      |> Enum.reject(&Enum.empty?/1)

    {start, n, rest}
  end

  def run(s) do
    {start, n, splitters} = parse(s)

    splitters
    |> Enum.reduce({[{start, 1}], 0}, fn splitter, {current_beams, count} ->
      {new_beams, inc} = current_beams |> split(splitter, n)
      {new_beams, inc + count}
    end)
  end

  def split(beams, splitter, n) do
    {beams, count} =
      beams
      |> Enum.reduce({[], 0}, fn {beam, beam_count}, {current_beams, n_splits} ->
        {new_beams, inc} =
          if MapSet.member?(splitter, beam) do
            {
              if(beam < 0, do: [], else: [{beam - 1, beam_count}]) ++
                if(beam >= n, do: [], else: [{beam + 1, beam_count}]),
              1
            }
          else
            {[{beam, beam_count}], 0}
          end

        {Enum.concat(new_beams, current_beams), n_splits + inc}
      end)

    {beams |> compress, count}
  end

  def compress(list) do
    list
    |> Enum.sort()
    |> Enum.chunk_by(&elem(&1, 0))
    |> Enum.flat_map(fn l ->
      beam = l |> List.first() |> elem(0)
      sum = l |> Enum.map(&elem(&1, 1)) |> Enum.sum()
      [{beam, sum}]
    end)
  end

  def part1(s) do
    run(s) |> elem(1)
  end

  def part2(s) do
    run(s) |> elem(0) |> Enum.map(&elem(&1, 1)) |> Enum.sum()
  end
end
1 Like

Parse

[start | rest] = String.split(puzzle_input)

start_col = byte_size(start) - byte_size(String.trim_leading(start, "."))

splitters =
  Enum.map(rest, fn row ->
    row
    |> String.to_charlist()
    |> Enum.with_index()
    |> Enum.filter(&(elem(&1, 0) == ?^))
    |> MapSet.new(&elem(&1, 1))
  end)

Part 1

Enum.reduce(splitters, {MapSet.new([start_col]), 0}, fn splits, {beams, count} ->
  import MapSet, only: [intersection: 2, difference: 2, union: 2]
  
  hits = intersection(beams, splits)
  new_beams = for hit <- hits, dx <- [-1, 1], into: MapSet.new(), do: hit + dx
  beams = beams |> difference(hits) |> union(new_beams)

  {beams, MapSet.size(hits) + count}
end)

Part 2

Enum.reduce(splitters, %{start_col => 1}, fn splits, beams ->
  Enum.reduce(splits, beams, fn s, acc ->
    case Map.pop(acc, s) do
      {nil, map} ->
        map

      {count, map} ->
        Map.merge(
          map,
          %{
            (s + 1) => count,
            (s - 1) => count
          },
          fn _k, a, b -> a + b end
        )
    end
  end)
end)
|> Enum.sum_by(&elem(&1, 1))

EDIT

I decided to draw the resulting image

4 Likes

Part 1 took me more time too :slight_smile:

defmodule AdventOfCode.Solutions.Y25.Day07 do
  alias AoC.Input

  def parse(input, _part) do
    lines =
      input
      |> Input.read!()
      |> String.split("\n", trim: true)
      |> Enum.map(&String.to_charlist/1)

    [first_row | rows] = lines
    start = Enum.find_index(first_row, fn x -> x == ?S end)

    layers =
      Enum.map(rows, fn row ->
        for {?^, x} <- Enum.with_index(row),
            reduce: %{},
            do: (acc -> Map.put(acc, x, true))
      end)

    {start, layers}
  end

  def part_one({start, layers}) do
    {_last_positions, count} =
      Enum.reduce(layers, {[start], 0}, fn layer, {poses, count} ->
        {splitters, keep_positions} = Enum.split_with(poses, &Map.has_key?(layer, &1))
        count = count + length(splitters)
        split_positions = Enum.flat_map(splitters, &[&1 - 1, &1 + 1])
        new_positions = Enum.uniq(keep_positions ++ split_positions)
        {new_positions, count}
      end)

    count
  end

  def part_two({start_pos, layers}) do
    new_poscounts =
      Enum.reduce(layers, _init_poscounts = %{start_pos => 1}, fn layer, poscounts ->
        Enum.reduce(poscounts, %{}, fn {pos, n}, new_poscounts ->
          if Map.has_key?(layer, pos) do
            new_poscounts
            |> Map.update(pos - 1, n, &(&1 + n))
            |> Map.update(pos + 1, n, &(&1 + n))
          else
            Map.update(new_poscounts, pos, n, &(&1 + n))
          end
        end)
      end)

    Enum.sum_by(new_poscounts, &elem(&1, 1))
  end
end

1 Like

I have no idea what the algorithm is called here but I think I’ve done something very similar in previous puzzles - instead of adding | to my grid map, add the number of beams that have reached this point. Then at the end, add up all of the numbers on the bottom row.

Wasted about half an hour debugging the part 2 example because one of my clauses was wrong - I forgot to increment when a beam overlaps another beam :woman_facepalming:

Name                     ips        average  deviation         median         99th %
day 07, part 1         99.21       10.08 ms     ±7.20%        9.72 ms       11.75 ms
day 07, part 2         90.82       11.01 ms     ±7.99%       10.61 ms       12.94 ms
1 Like

I think it’s called “Pascal’s Triangle”, dunno maybe I’m wrong. Anyway, I naively did Part 2 and of course it doesn’t complete in the age of the universe. So, decided to keep track of both timelines and tachyons in one pass. Went much better. Used MapSet and Map to speed lookups. Fun day!

Edit: It is Pascal’s Triangle except with the twist that the tree doesn’t split necessarily evenly on each level.

defmodule RAoc.Solutions.Y25.Day07 do
  alias AoC.Input

  def parse(input, _part) do
    Input.read!(input)
    |> String.split("\n", trim: true)
    |> then(fn [first_line | rest] ->
      {first_line
       |> String.graphemes()
       |> Enum.with_index()
       |> Enum.filter(fn {c, _n} -> c == "S" end)
       |> List.first()
       |> elem(1),
       rest
       |> Enum.map(fn line ->
         line
         |> String.graphemes()
         |> Enum.with_index()
         |> Enum.reject(&(elem(&1, 0) == "."))
         |> Enum.map(&elem(&1, 1))
         |> MapSet.new()
       end)}
    end)
  end

  def part_one(problem) do
    tach_counters(problem)
    |> elem(1)
  end

  def part_two(problem) do
    tach_counters(problem)
    |> elem(0)
    |> Enum.sum_by(fn {_, n} -> n end)
  end

  defp tach_counters({tachyon, rows}) do
    tachyons_w_timelines = %{tachyon => 1}

    Enum.reduce(rows, {tachyons_w_timelines, 0}, fn row, {tachyons_w_timelines, split_count} ->
      Enum.reduce(tachyons_w_timelines, {tachyons_w_timelines, split_count}, fn {n, last},
                                                                                {tachyons_w_timelines,
                                                                                 split_count} ->
        if MapSet.member?(row, n) do
          {tachyons_w_timelines
           |> Map.delete(n)
           |> Map.update(n + 1, last, &(&1 + last))
           |> Map.update(n - 1, last, &(&1 + last)), split_count + 1}
        else
          {tachyons_w_timelines, split_count}
        end
      end)
    end)
  end
end
2 Likes

Part 01 was pretty straightforward, but in Part 02 I had difficulty parsing the input into an adjacency list. On my first try, I ran a pure DFS, which of course didn’t finish. I had to optimize it using memoization which took some time

oh I’ve heard of that! Cheers :slight_smile:

Very elegant solution. I realized, after looking at it, that I shouldn’t even have to check for boundary conditions, as by design the beams cannot go outside.

1 Like

Part 1

defmodule Aoc do

  def find_all_index(l, a) do
    l
    |> Enum.with_index
    |> Enum.reduce([], fn {x, i}, acc -> if x == a, do: [i | acc], else: acc end)
  end

  def loop([], _, results, count), do: {Enum.reverse(results), count}

  def loop([head | tail], beams, results, count) do
    {current, hits} = head
      |> Enum.with_index(fn x, i ->
        cond do
          Enum.at(head, i + 1) == "^" and Enum.member?(beams, i + 1) -> {"|", 0}
          Enum.at(head, i - 1) == "^" and Enum.member?(beams, i - 1) -> {"|", 0}
          x == "^" and Enum.member?(beams, i) -> {"^", 1}
          x == "." and Enum.member?(beams, i) -> {"|", 0}
          true -> {x, 0}
        end
      end)
      |> Enum.unzip
    loop(tail, find_all_index(current, "|"), [current | results], count + Enum.sum(hits))
  end
end

[start | lines] =
  File.read!("ids.txt")
  |> String.split("\n")
  |> Enum.map(&String.graphemes/1)

start_index = Enum.find_index(start, &(&1 == "S"))

{results, count} = Aoc.loop(lines, [start_index], [], 0)
results
|> Enum.map(&Enum.join/1)
|> Enum.map(&IO.inspect/1)

IO.inspect(count)

No native find_all_index? (or all indexes)

My not pretty part2


defmodule Aoc do

  def loop([], current_beams), do: current_beams |> Enum.map(fn {_, w} -> w end) |> Enum.sum

  def loop([head | tail], beams) do
    current_beams = head
      |> Enum.with_index(fn x, i ->

        {next_index, next_weight} = Enum.find(beams, {-1, 0}, fn {index, _} -> index == (i + 1) end)
        {current_index, current_weight} = Enum.find(beams, {-1, 0}, fn {index, _} -> index == i end)
        {previous_index, previous_weight} = Enum.find(beams, {-1, 0}, fn {index, _} -> index == (i - 1) end)

        right? = Enum.at(head, i + 1) == "^" and next_index > -1
        left? = Enum.at(head, i - 1) == "^" and previous_index > -1
        top? = current_index > -1

        cond do
          right? and left? and top? -> {i, next_weight + current_weight + previous_weight}
          right? and top? -> {i, next_weight + current_weight}
          left? and top? -> {i, current_weight + previous_weight}
          right? and left? -> {i, next_weight + previous_weight}
          right? -> {i, next_weight}
          left? -> {i, previous_weight}
          x == "." and top? -> {i, current_weight}
          true -> nil
        end
      end)
      |> Enum.reject(&(&1 == nil))
    loop(tail, current_beams)
  end
end

[start | lines] =
  File.read!("ids.txt")
  |> String.split("\n")
  |> Enum.map(&String.graphemes/1)

start_index = Enum.find_index(start, &(&1 == "S"))

count = Aoc.loop(lines, [{start_index, 1}])

IO.inspect(count)

So…I went with binary parsing just to do it a bit different this time. Now I see part2 and I hate myself :wink:

ddefmodule Aoc2025.Solutions.Y25.Day07 do
  alias AoC.Input

  Application.put_env(:elixir, :inspect_opts, charlists: :as_lists)

  def parse(input, _part) do
    Input.read!(input)
    |> String.trim()
  end

  def part_one(problem) do
    problem |> parse_bin()
  end

  def parse_bin(bin, _cursor \\ 0, _y_axes \\ [], _prev_y_axes \\ [], _counter \\ 0)

  # Start, let set the first Y-axis for the beam
  def parse_bin(<<?S>> <> rest, cursor, y_axes, prev_y_axes, counter),
    do: parse_bin(rest, cursor + 1, [cursor | y_axes], prev_y_axes, counter)

  # . in beam path...this y is enlightened
  def parse_bin(<<?.>> <> rest, cursor, y_axes, [pl1 | rest_prev_y_axes], counter) when cursor == pl1,
    do: parse_bin(rest, cursor + 1, [cursor | y_axes], rest_prev_y_axes, counter)

  # ^ in beam path...the beam splits
  def parse_bin(<<?^>> <> rest, cursor, y_axes, [pl1 | rest_prev_y_axes], counter) when cursor == pl1,
    do: parse_bin(rest, cursor + 1, [cursor - 1, cursor + 1 | y_axes], rest_prev_y_axes, counter + 1)

  # newline, let's pass the uniq collected beam axes to the next line and reset the collection
  def parse_bin(<<?\n>> <> rest, _cursor, y_axes, _prev_y_axes, counter),
    do: parse_bin(rest, 0, [], y_axes |> Enum.sort() |> Enum.uniq(), counter)

  # catchall, just some regular space
  def parse_bin(<<_>> <> rest, cursor, y_axes, prev_y_axes, counter),
    do: parse_bin(rest, cursor + 1, y_axes, prev_y_axes, counter)

  # end
  def parse_bin(<<>>, _cursor, _y_axes, _prev_y_axes, counter),
    do: counter
end

My part 1 here. I made this mistake to just extract the positions of the splitters. This bite me in part 2.

defmodule P1 do
  def parse(problem) do
    lines = problem |> String.split("\n", trim: true)
    row_count = Enum.count(lines)
    [start | splitter] = lines

    start_pos =
      start
      |> String.split("", trim: true)
      |> Enum.find_index(&(&1 == "S"))

    beam_splitter =
      splitter
      |> Enum.map(fn line -> Regex.scan(~r/\^/, line, return: :index) end)
      |> Enum.map(fn l -> Enum.map(l, fn [{pos, _}] -> pos end) end)
      |> Enum.with_index()
      |> Enum.reject(fn {list, _} -> list == [] end)
      |> Enum.map(fn {list, y} -> Enum.map(list, fn x -> {x, y} end) end)
      |> Enum.flat_map(fn item -> item end)
      |> Enum.map(fn pos -> {pos, true} end)
      |> Map.new()

    {start_pos, beam_splitter, row_count}
  end

  def split_beams(_row, [], _beam_splitter, new_beams, count) do
    {Enum.dedup(new_beams), count}
  end

  def split_beams(row, [beam_pos | rest_beams], beam_splitter, new_beams, count) do
    # IO.inspect({row, beam_pos, rest_beams, beam_splitter, new_beams, count})
    if Map.has_key?(beam_splitter, {beam_pos, row}) do
      split_beams(
        row,
        rest_beams,
        beam_splitter,
        [beam_pos - 1 | [beam_pos + 1 | new_beams]],
        count + 1
      )
    else
      split_beams(
        row,
        rest_beams,
        beam_splitter,
        [beam_pos | new_beams],
        count
      )
    end
  end

  def run(input) do
    {start_pos, beam_splitter, rows} = input |> parse()

    {_, count} =
      0..(rows - 1)
      |> Enum.reduce({[start_pos], 0}, fn row, {beams, count} ->
        split_beams(row, beams, beam_splitter, [], count)
      end)

    count
  end
end

My complicated part 2 here. Had to do the parsing again. I should have use used just another map for the results, not one map with the splitters and the result.

defmodule P2 do
  # Putting the map and the result in the same map is not always a good idea.
  def parse(problem) do
    lines =
      problem
      |> String.split("\n", trim: true)

    y_size = Enum.count(lines)
    x_size = hd(lines) |> String.split("", trim: true) |> Enum.count()

    map =
      lines
      |> Enum.with_index()
      |> Enum.map(fn {line, y} ->
        String.split(line, "", trim: true)
        |> Enum.with_index(fn
          "^", x -> {{x, y}, {:splitter, 0}}
          ".", x -> {{x, y}, {:empty, 0}}
          "S", x -> {{x, y}, {:empty, 1}}
        end)
      end)
      |> Enum.flat_map(fn element -> element end)
      |> Map.new()

    {map, x_size, y_size}
  end

  def update(map, {:empty, _}, x, y) do
    above = elem(Map.get(map, {x, y - 1}, {:empty, 0}), 1)
    Map.update!(map, {x, y}, fn {type, sum} -> {type, sum + above} end)
  end

  def update(map, {:splitter, _}, x, y) do
    above = elem(Map.get(map, {x, y - 1}, {:empty, 0}), 1)

    map
    |> Map.update!({x - 1, y}, fn {type, sum} -> {type, sum + above} end)
    |> Map.put({x, y}, {:splitter, 0})
    |> Map.update!({x + 1, y}, fn {type, sum} -> {type, sum + above} end)
  end

  def update(map, _, _x, _y) do
    map
  end

  def run_beam(map, x, y, x_size, y_size) do
    new_map = update(map, Map.get(map, {x, y}), x, y)

    if x < x_size do
      run_beam(new_map, x + 1, y, x_size, y_size)
    else
      if y < y_size do
        run_beam(new_map, 0, y + 1, x_size, y_size)
      else
        new_map
      end
    end
  end

  def select_row(map, row) do
    map
    |> Map.keys()
    |> Enum.filter(fn {_, y} -> y == row end)
    |> Enum.reduce([], fn key, list -> [elem(Map.get(map, key), 1) | list] end)
  end

  def run(input) do
    {map, x_size, y_size} = input |> P2.parse()

    run_beam(map, 0, 1, x_size, y_size)
    |> select_row(y_size - 1)
    |> Enum.sum()
  end
end

This one felt like some kind of cellular automata, nice to solve with some pattern matching. For part 1 I started with 0 instead and added one to the left of every split.

defmodule Advent2025Test do
  use ExUnit.Case

  def day7_data() do
    "day7.txt"
    |> File.stream!()
    |> Enum.map(fn line ->
      String.trim(line) |> String.graphemes()
    end)
  end

  def num(x) when is_number(x), do: x
  def num(_), do: 0

  def tacrow([], []), do: []
  def tacrow(["S" | rest], ["." | rest2]), do: [1 | tacrow(rest, rest2)]

  def tacrow([y, x | rest], [".", "^" | rest2]) when is_number(x) do
    [x + num(y) | tacrow([x | rest], ["^" | rest2])]
  end

  def tacrow([x, y, z | rest], ["^", ".", "^" | rest2]) when is_number(x) and is_number(z) do
    ["^", x + z + num(y) | tacrow([z | rest], ["^" | rest2])]
  end

  def tacrow([x, y | rest], ["^", "." | rest2]) when is_number(x) do
    ["^", x + num(y) | tacrow(rest, rest2)]
  end

  def tacrow([x | rest], ["." | rest2]) when is_number(x), do: [x | tacrow(rest, rest2)]
  def tacrow([_ | rest], [a | rest2]), do: [a | tacrow(rest, rest2)]

  test "day7_p2" do
    [first | rest] = day7_data()
    res = dbg(Enum.reduce(rest, first, fn row, prev -> tacrow(prev, row) end))
    dbg(Enum.filter(res, &is_number/1) |> Enum.sum())
  end
end

defmodule Day07 do
  def part1(file), do: file |> traverse() |> elem(1)
  def part2(file), do: file |> traverse() |> elem(0) |> Enum.sum_by(&elem(&1, 1))

  def traverse(file) do
    grid = Util.file_to_char_map(file)
    {{max_row, _}, _} = Enum.max_by(grid, fn {{row, _}, _} -> row end)
    {start, _} = Enum.find(grid, &match?({_, ?S}, &1))

    Enum.reduce(1..(max_row - 1), {%{start => 1}, 0}, fn _, {beams, splits} ->
      new_beams =
        Enum.flat_map(beams, fn {{row, col}, count} ->
          case grid[{row + 1, col}] do
            ?. -> [{{row + 1, col}, count}]
            ?^ -> [{{row + 1, col - 1}, count}, {{row + 1, col + 1}, count}]
          end
        end)

      {Enum.reduce(new_beams, %{}, fn {k, v}, acc -> Map.update(acc, k, v, &(&1 + v)) end),
       splits + length(new_beams) - map_size(beams)}
    end)
  end
end

soooo, nice!

1 Like