Aetherus
I tried to use combinatorial to solve today’s puzzles but failed (my brain burned out
). 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 ![]()
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
Trending in Challenges
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself.
My main conc...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
bjorng
I spent most time to get part 1 to work; I solved part 2 by adding memoization:
https://github.com/bjorng/advent-of-code-2023/blob/main/day12/lib/day12.ex
igorb
@Aetherus I love your process dictionary trick. I just set up an ETS for each thread
. 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):
https://github.com/ibarakaiev/advent-of-code-2023/blob/main/lib/advent_of_code/day_12.ex
Runs in around 0.2 seconds.
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.
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.
ramuuns
Well I split the springs into groups (so
??..??.#?becomes["??", "??", "#?"], and then I ran my (originally non-memoized) fit-counter, which I then memoized once I encountered part 2 with a dumbMap, that I just send up and down the recursive functionhttps://github.com/ramuuns/aoc/blob/master/2023/day-12.ex
woojiahao
Today was tricky, I got the naive solution but optimizing for part 2 was not easy:
https://github.com/woojiahao/aoc/blob/main/lib/aoc/2023/day_12.ex
lud
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…
Ok so after hours and hours failing, I looked a bit online and found some tips to implement a fast solution.
The problem is that is was only in mutable imperative languages, so I had a hard time to find my own thing in Elixir. And it still could be better.
I could not have made it alone, I was too lost.
But anyway, this solutions takes 100ms for part 2 which is satisying. : adventofcode/lib/solutions/2023/day12.ex at main · lud/adventofcode · GitHub
Aetherus
I tried a pure functional approach. Here’s my heavily commented code:
lud
Nice !
So your memo cache is just the current group index and the remaining group counters.
I tried a memoized solution but I could not get it right. I had either no cache hits or wrong results
I think that if you map each possibility at each step, and sum the indentical states counts, then that would be equivalent to my solution.
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.