Aetherus

Aetherus

Advent of Code 2023 - Day 12

I tried to use combinatorial to solve today’s puzzles but failed (my brain burned out :exploding_head:). In the end I just used brute force with memoization.

Task.async_stream turned out to be very helpful. It both let me handle each line of input concurrently, and allows me to abuse process dictionaries :grin:

defmodule AoC2023.Day12 do

  # `input` for both parts are things like
  #
  # [
  #   {"???.###", [1,1,3]},
  #   {".??..??...###.", [1,1,3]},
  #   ...
  # ]

  @spec part1([{String.t(), [pos_integer()]}]) :: non_neg_integer()
  def part1(input) do
    input
    |> Task.async_stream(fn {springs, counts} ->
      aux(springs, ".", counts)
    end, ordered: false)
    |> Stream.map(&elem(&1, 1))
    |> Enum.sum()
  end

  @spec part2([{String.t(), [pos_integer()]}]) :: non_neg_integer()
  def part2(input) do
    input
    |> Enum.map(fn {springs, counts} ->
      {
        List.duplicate(springs, 5) |> Enum.join("?"),
        List.duplicate(counts, 5) |> List.flatten()
      }
    end)
    |> part1()
  end

  @spec aux(
    springs :: String.t(),
    previous_spring :: String.t(),
    counts :: [pos_integer()]
  ) :: non_neg_integer()
  defp aux("", _, []), do: 1

  defp aux("", _, [0]), do: 1

  defp aux("", _, _), do: 0

  defp aux("#" <> _, _, []), do: 0

  defp aux("#" <> _, _, [0 | _]), do: 0

  defp aux("#" <> rest, _, [h | t]), do: aux(rest, "#", [h - 1 | t])

  defp aux("." <> rest, _, []), do: aux(rest, ".", [])

  defp aux("." <> rest, "#", [0 | t]), do: aux(rest, ".", t)

  defp aux("." <> _, "#", [_ | _]), do: 0

  defp aux("." <> rest, ".", counts), do: aux(rest, ".", counts)

  defp aux("?" <> rest, "#", []), do: aux(rest, ".", [])

  defp aux("?" <> rest, "#", [0 | t]), do: aux(rest, ".", t)

  defp aux("?" <> rest, "#", [h | t]), do: aux(rest, "#", [h - 1 | t])

  defp aux("?" <> rest, ".", []), do: aux(rest, ".", [])

  defp aux("?" <> rest, ".", [0 | t]), do: aux(rest, ".", t)

  defp aux("?" <> rest, ".", [h | t]) do
    memoized({rest, [h | t]}, fn ->
      aux(rest, "#", [h - 1 | t]) + aux(rest, ".", [h | t])
    end)
  end

  defp memoized(key, fun) do
    with nil <- Process.get(key) do
      fun.() |> tap(&Process.put(key, &1))
    end
  end
end

Most Liked

Aetherus

Aetherus

I still need to try your solution in a debugging way to fully understand it.

Here’s what I think about dynamic programming. It’s just brute force with some sort of caching. When we talk about caching, we know each entry needs a key. I think it doesn’t matter what the key is, as long as it’s consistent. If the keys are continuous non-negative integers or tuples of integers, in imperative languages we usually use an array or a 2D array or a 3D array as our store. But we don’t have to restrict ourselves to that kind of caching mechanism. We can use maps, GenServers, ETS tables, process dictionaries, or even databases as the cache store as long as it provides a fast way of lookup for an exact match, and what the type of the keys are just doesn’t matter.

Aetherus

Aetherus

I don’t know whether it’s O(1) or O(log N) either. According to my benchmark (not for this puzzle), process dictionary is faster than ETS, which in turn is faster than a plain map, and Agent is the slowest.

bjorng

bjorng

Erlang Core Team

I spent most time to get part 1 to work; I solved part 2 by adding memoization:

igorb

igorb

@Aetherus I love your process dictionary trick. I just set up an ETS for each thread :grin:. I wonder though if process dictionaries are O(1) or O(log N) like other maps in Elixir?

My solution (not very happy with how it turned out, feels like way too much code):

Runs in around 0.2 seconds.

igorb

igorb

Just tried changing my solution to use process dictionaries instead — besides a much cleaner solution, it seems that this version is indeed around 10% faster than my old ETS-based approach. Never used process dictionaries in this way, thanks for sharing your solution! Learned a new thing today.

Where Next?

Popular in Challenges Top

LostKobrakai
This topic is about Day 9 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
bismark
Took me a minute to remember my binary math :smile: :grimacing:… import Bitwise __DIR__ |&gt; Path.join("puzzle.txt") |&gt; File.stream...
New
Aetherus
This topic is about the Advent of Code 2021 - Day 4. Thanks to @bjorng , we now have a new Private Leaderboard. The entry code is: 370...
New
bjorng
This topic is about Day 18 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
igorb
I found today a bit tedious: advent-of-code-2024/lib/advent_of_code2024/day15.ex at main · ibarakaiev/advent-of-code-2024 · GitHub.
New
bjorng
Note: This topic is to talk about Day 2 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can joi...
New
Aetherus
This topic is about Day 4 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
rugyoga
Fairly straightforward Dijkstra’s algorithm import AOC aoc 2023, 17 do def compute(input, candidates) do {{max_row, max_col}, ite...
New
bjorng
This topic is about Day 6 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums ): https://adve...
New
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability 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
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement