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

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:

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.

tywhisky

tywhisky

I have optimized the code as much as possible to make it appear more comprehensible. If there are any parts that someone doesn’t understand, I am more than willing to add additional comments.

Solution

lud

lud

Easier than yesterday :smiley:

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.

Where Next?

Popular in Challenges Top

DmitriyChernyavskiy
Hello everyone, I’m a new in elexir and functional language. I’m trying to implement Websocket interraction with server. On first layer...
New
shritesh
This was way too easy after the last few days. Simple map, filter and count.
New
Aetherus
This topic is about Day 5 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
New
maennchen
Ok, that was a rough one today. I haven’t found a way to improve the algorithm further. Part 1 runs in .5 seconds, Part 2 in ~ 5 minutes...
New
bjorng
This topic is about Day 14 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/l...
New
bjorng
This topic is about Day 1 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adven...
New
Qqwy
Note by the Moderators: This topic is to talk about Day 6 of the Advent of Code. For general discussion about the Advent of Code 2018 an...
New
code-shoily
Here’s my day 3 code This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata bu...
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

Other popular topics Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New

We're in Beta

About us Mission Statement