woojiahao

woojiahao

Advent of Code 2023 - Day 13

Fun day today! Quite straightforward but the key observations are:

Spoilers
  1. 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
  2. Smudges correspond to the differences between rows, so if the total differences is exactly 1, then a smudge can exist
  3. We only need to compare between the minimum between the (top, bottom) from the reflection line, this saves a teeny bit of time

https://github.com/woojiahao/aoc/blob/main/lib/aoc/2023/day_13.ex

Most Liked

bjorng

bjorng

Erlang Core Team

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:

https://github.com/bjorng/advent-of-code-2023/blob/main/day13/lib/day13.ex

midouest

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

Aetherus

I’m so not proud of my code. Just a pile of junk. Ctrl+C & Ctrl+V everywhere. Anyway, I post it here.

https://github.com/Aetherus/advent-of-code/blob/master/2023/day-13.livemd

Where Next?

Popular in Challenges Top

igorb
So… that’s it? Everyone is stuck on part 2? :slight_smile: I looked at Reddit hints and thought I probably wouldn’t have come up with the...
New
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
bjorng
Here is my solution: https://github.com/bjorng/advent-of-code-2021/blob/77bdef7a51b428b58ffb4ab76f540901e636f841/day12/lib/day12.ex
New
sukhmeetsd
All in all, from what I understand, it is better not to use GenServer.cast when we want some concurrent operations to happen for sure, be...
New
liamcmitchell
A frustrating one for me. I spent a long time trying to understand why some combinations resulted in fewer presses and struggled to keep ...
New
gangstead
This is my second year doing AoC in Elixir and my first year doing it with Livebook. When I was doing just plain Elixir I usually set up...
New
seeplusplus
Hello all, hopefully I post this before someone else does and I don’t dupe. IMO Day 4 was much easier than Day 3 (yay, I can sleep befor...
New
stevensonmt
Anyone else think the prompt for this challenge is contradictory? The rules for comparing packets include If both values are lists, c...
New
rvnash
Anyone have a solution to Part 2 today? Part 1 was straight forward, but I can’t figure out a programatic way to do part 2. I understand ...
New
christhekeele
Thought I’d kick today’s thread off! Parsing Enum rocks, so most of my code was actually in parsing input. ▶ Preprocessing input Part 1...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

We're in Beta

About us Mission Statement