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 && 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) &&
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) &&
outward_rows_match?(cols, idx1, idx2)
end)
# |> IO.inspect()
IO.inspect([midrow, midcol], label: "midrow & col")
acc +
((midrow &&
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