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

igorb
Today is a brute-force day: advent-of-code-2024/lib/advent_of_code2024/day6.ex at main · ibarakaiev/advent-of-code-2024 · GitHub Takes a...
New
bjorng
Note: This topic is to talk about Day 25 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
Aetherus
This topic is about Day 7 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
shritesh
This was way too easy after the last few days. Simple map, filter and count.
New
Aetherus
Don’t know why the regex ~r/[\W && [^\.]]/x does not work in Elixir. It works pretty well in Ruby. Anyway, here is my solution:
New
New
bjorng
This topic is about Day 5 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums ): https://adve...
New
bjorng
Note: This topic is to talk about Day 5 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
groovyda
Today’s challenge for me was about using reduce: defmodule Prob5 do def move([[h1 | rest] = _list1, list2]) do [rest, [h1 | list2]...
New
bjorng
This topic is about Day 9 of the Advent of Code 2021 . We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
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
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
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
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
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
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
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
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