woojiahao
Advent of Code 2023 - Day 13
Fun day today! Quite straightforward but the key observations are:
Spoilers
- Finding the column-wise reflection line is the same as transposing the pattern and finding the row-wise reflection line so we just need to develop 1 search algorithm
- Smudges correspond to the differences between rows, so if the total differences is exactly 1, then a smudge can exist
- We only need to compare between the minimum between the (top, bottom) from the reflection line, this saves a teeny bit of time
Most Liked
bjorng
The approach I took for solving part 1 was not useful for part 1, so I had to rewrite my solver so that it was able to handle part 2.
My solution:
midouest
Went with Nx again for today’s problem.
Part 1
defmodule Part1 do
def parse(input) do
for pattern <- String.split(input, "\n\n") do
for line <- String.split(pattern, "\n", trim: true) do
String.graphemes(line)
|> Enum.map(fn c -> if c == "#", do: 1, else: 0 end)
end
|> Nx.tensor(type: :u8, names: [:y, :x])
end
end
def summarize(patterns) do
for pattern <- patterns do
{axis, {index, _}} =
reflect(pattern)
left = index + 1
if axis == :x, do: left, else: 100 * left
end
|> Enum.sum()
end
def reflect(pattern) do
[:x, :y]
|> Enum.map(fn axis -> {axis, reflect_along(pattern, axis)} end)
|> Enum.max_by(fn {_, {_, size}} -> size end)
end
def reflect_along(pattern, axis) do
0..Nx.axis_size(pattern, axis)
|> Enum.reduce({-1, -1}, fn index, {_, prev_size} = prev ->
{_, next_size} = next = reflect_at(pattern, axis, index)
if next_size > prev_size, do: next, else: prev
end)
end
def reflect_at(pattern, axis, index), do: reflect_at(pattern, axis, index, index + 1)
def reflect_at(pattern, axis, left, right) do
if left < 0 or right >= Nx.axis_size(pattern, axis) do
reflection(left + 1, right - 1)
else
left_tensor = pattern[Keyword.new([{axis, left}])]
right_tensor = pattern[Keyword.new([{axis, right}])]
if left_tensor != right_tensor do
{-1, -1}
else
reflect_at(pattern, axis, left - 1, right + 1)
end
end
end
def reflection(left, right) when left > right, do: {-1, -1}
def reflection(left, right) do
size = div(right - left, 2)
{left + size, size}
end
end
input
|> Part1.parse()
|> Part1.summarize()
Part 2
defmodule Part2 do
def summarize(patterns) do
for pattern <- patterns do
{axis, {index, _}} =
reflect(pattern)
left = index + 1
if axis == :x, do: left, else: 100 * left
end
|> Enum.sum()
end
def reflect(pattern) do
[:x, :y]
|> Enum.map(fn axis -> {axis, reflect_along(pattern, axis)} end)
|> Enum.max_by(fn {_, {_, size}} -> size end)
end
def reflect_along(pattern, axis) do
0..Nx.axis_size(pattern, axis)
|> Enum.reduce({-1, -1}, fn index, prev ->
{start, size, smudge} = reflect_at(pattern, axis, index)
if smudge, do: {start, size}, else: prev
end)
end
def reflect_at(pattern, axis, index), do: reflect_at(pattern, axis, index, index + 1, false)
def reflect_at(pattern, axis, left, right, smudge) do
if left < 0 or right >= Nx.axis_size(pattern, axis) do
reflection(left + 1, right - 1, smudge)
else
left_tensor = pattern[Keyword.new([{axis, left}])]
right_tensor = pattern[Keyword.new([{axis, right}])]
if left_tensor != right_tensor do
if smudge or
Nx.equal(left_tensor, right_tensor)
|> Nx.logical_not()
|> Nx.sum() != Nx.tensor(1, type: :u64) do
{-1, -1, false}
else
reflect_at(pattern, axis, left - 1, right + 1, true)
end
else
reflect_at(pattern, axis, left - 1, right + 1, smudge)
end
end
end
def reflection(left, right, _) when left > right, do: {-1, -1, false}
def reflection(left, right, smudge) do
size = div(right - left, 2)
{left + size, size, smudge}
end
end
input
|> Part1.parse()
|> Part2.summarize()
Aetherus
I’m so not proud of my code. Just a pile of junk. Ctrl+C & Ctrl+V everywhere. Anyway, I post it here.
tywhisky
lud
Easier than yesterday ![]()
Today I learnt that you can match a 0-length binary part in a patter.
Each pattern is a list of string, so to change a smudge I need to update the string at list index Y and change a character in that string at index X. The following code works when X is zero.
defp stream_changes(pattern) do
yo = length(pattern) - 1
xo = (pattern |> hd() |> String.length()) - 1
Stream.resource(
fn -> {0, 0} end,
fn
{_, y} when y > yo -> raise "expleted"
{x, y} when x > xo -> {[], {0, y + 1}}
{x, y} -> {[fix_pattern(pattern, x, y)], {x + 1, y}}
end,
fn _ -> nil end
)
end
defp fix_pattern(pattern, x, y) do
List.update_at(pattern, y, fn <<prev::binary-size(x), c, rest::binary>> ->
new_c =
case c do
?. -> ?#
?# -> ?.
end
<<prev::binary-size(x), new_c, rest::binary>>
end)
end
I used Stream.resource instead of Stream.unfold because it lets you return an empty list, which is convenient to reset the stream state to X=0 and Y+1.







