Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 13

day13.livemd

Day 13

Mix.install([
  {:kino, "~> 0.7.0"}
])

IEx.Helpers.c("/Users/johnb/dev/2023adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/03/04/' > day04.livemd`
#

Installation and Data

input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_p1example)) ||
    Kino.Input.read(input_p1puzzleInput)
end

Part 1

defmodule Day13 do
  def grid_cells(grid) do
    0..grid.last_cell
  end

  def grid_rows(grid) do
    grid_cells(grid)
    |> Enum.chunk_every(grid.grid_width)
  end

  def grid_x(grid, cell), do: rem(cell, grid.grid_width)
  def grid_y(grid, cell), do: div(cell, grid.grid_width)

  def to_text_grid(grid) do
    grid_rows(grid)
    |> Enum.map(fn row ->
      Enum.map(row, fn x -> grid[x] end)
      |> Enum.join("")
    end)
    |> Enum.join("\n")
  end

  def invert(grid) do
    grid_cells(grid)
    |> Enum.reduce(
      %{
        grid
        | grid_width: grid.grid_height,
          grid_height: grid.grid_width,
          last_cell: grid.last_cell
      },
      fn cell, acc ->
        Map.put(acc, grid_x(grid, cell) * grid.grid_height + grid_y(grid, cell), grid[cell])
      end
    )
  end

  def off_by_one?(row1, row2) do
    mismatches =
      Enum.zip(String.codepoints(row1), String.codepoints(row2))
      |> Enum.reduce(0, fn {a, b}, acc -> acc + ((a == b && 0) || 1) end)

    mismatches == 1
  end

  def near_match?(row1, row2) do
    # This direct comparison satisfies part 1 of the puzzle
    # row1 == row2 ||

    # But part two allows for 1-char smudges
    off_by_one?(row1, row2)
  end

  def outward_rows_match?(rows, idx1, idx2) do
    # IO.puts("outward_rows_match? #{idx1 - 1}, #{idx2 + 1}")
    if idx1 > 0 &amp;&amp; idx2 < Enum.count(rows) - 1 do
      # outer rows exist - make sure they and the next also match
      # IO.inspect([Enum.at(rows, idx1 - 1), Enum.at(rows, idx2 + 1)], label: "#{idx1 - 1} vs #{idx2 + 1}")
      near_match?(Enum.at(rows, idx1 - 1), Enum.at(rows, idx2 + 1)) ||
        outward_rows_match?(rows, idx1 - 1, idx2 + 1)
    else
      # IO.puts("off board?")
      # off-board is assumed to match
      true
    end
  end

  def solve(text) do
    text
    |> AOC.as_doublespaced_paragraphs()
    |> Enum.reduce(0, fn paragraph, acc ->
      rows = AOC.as_single_lines(paragraph)

      rows_paired =
        rows
        |> Enum.with_index()
        |> Enum.chunk_every(2, 1, :discard)

      # |> IO.inspect()
      cols =
        paragraph
        |> AOC.as_grid()
        |> invert()
        |> to_text_grid()
        |> AOC.as_single_lines()

      cols_paired =
        cols
        |> Enum.with_index()
        |> Enum.chunk_every(2, 1, :discard)

      # |> IO.inspect()

      midrow =
        rows_paired
        # [{"#.##..##.", 0}, {"..#.##.#.", 1}]
        |> Enum.find(fn [{row1, idx1}, {row2, idx2}] ->
          near_match?(row1, row2) &amp;&amp;
            outward_rows_match?(rows, idx1, idx2)
        end)

      # |> IO.inspect()
      midcol =
        cols_paired
        |> Enum.find(fn [{row1, idx1}, {row2, idx2}] ->
          # row1 == row2 &&
          near_match?(row1, row2) &amp;&amp;
            outward_rows_match?(cols, idx1, idx2)
        end)

      # |> IO.inspect()

      IO.inspect([midrow, midcol], label: "midrow & col")

      acc +
        ((midrow &amp;&amp;
            elem(List.last(midrow), 1) * 100) ||
           elem(List.last(midcol), 1))
    end)
  end

  def solve2(text) do
    text
  end
end

p1data.()
|> Day13.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 405)")

# 34911

# p1data.()
# |> Day13.solve2()
# |> IO.inspect(label: "\n*** Part 2 solution (example: 400)")

# 34911 is too high
# 34994 is also too high
# 38631 is still too high
# 40855 <- not tried since I know its too high