lud
This one lost me at the beginning, but after a little inspection of the input, there is a clear path to the answer.
Explainations in part_two/1
defmodule AdventOfCode.Y23.Day20 do
alias AoC.Input, warn: false
def read_file(file, _part) do
Input.stream!(file, trim: true)
end
def parse_input(input, _part) do
{modules, out_from} = Enum.map_reduce(input, _out_from = %{}, &parse_line/2)
# now for each inverter we need to initialize state will all its possible inputs
modules =
modules
|> Enum.map(fn
{key, {:conj, :uninitialized, outs}} ->
state = Map.new(Map.fetch!(out_from, key), fn k -> {k, 0} end)
{key, {:conj, state, outs}}
other ->
other
end)
|> Map.new()
{modules, out_from}
end
defp parse_line(line, out_from) do
[name, outs] = String.split(line, " -> ")
{name, kind, state} =
case name do
"broadcaster" -> {"broadcaster", :bcast, nil}
"%" <> name -> {name, :flip, :off}
"&" <> name -> {name, :conj, :uninitialized}
end
outs = String.split(outs, ", ")
out_from = Enum.reduce(outs, out_from, fn out, acc -> Map.update(acc, out, [name], &[name | &1]) end)
module = {name, {kind, state, outs}}
{module, out_from}
end
def part_one({modules, _}) do
{count_low, count_high, _} =
Enum.reduce(1..1000, {0, 0, modules}, fn _, {count_low, count_high, modules} ->
{count_low_add, count_high_add, modules} = push_button(modules)
{count_low + count_low_add, count_high + count_high_add, modules}
end)
count_low * count_high
end
def part_two({modules, out_from}) do
# modules =
# Map.new(modules, fn
# {key, {:flip, _, _}} -> {key, :flip}
# {key, {:conj, _, _}} -> {key, :conj}
# {key, {:bcast, _, _}} -> {key, :bcast}
# end)
# Rule:
#
# &jm -> rx
#
# For rx to receive a low pulse, &jm must remember a high pulse for all its
# inputs
#
# Then we have that:
#
# &sg -> jm
# &lm -> jm
# &dh -> jm
# &db -> jm
#
# So we need all of them to send a high input in the same cycle.
#
# The parents are those. Note that sg, lm, dh and db have each one a single
# input, so they are actually regular not gates, or "%" modules.
#
# &bc -> _, _, _, _, dh, _, _
# &bx -> _, _, db
# &qq -> lm, _, _, _, _, _, _
# &gj -> _, _, sg, _
#
# For sg, lm, dh and db to send a high pulse in the same time, we need bc,
# bx, qq and qj to send a low pulse in the same time.
#
# So we count how much cycles it takes for each one to send a low pulse, and
# the LCM of those cycle numbers is the answer.
#
# Though I have a feeling that the input is very tailored for that solution
# because any input would not guarantee that if bc, bx, qq and qj send a low
# pulse after N first cycles, they would acutally send a low pulse every
# other N cycles.
#
cyclics = Enum.flat_map(["rx"], &Map.fetch!(out_from, &1))
cyclics = Enum.flat_map(cyclics, &Map.fetch!(out_from, &1))
cyclics = Enum.flat_map(cyclics, &Map.fetch!(out_from, &1))
counts = count_cycles_until_low_pulse(modules, cyclics)
counts |> Map.values() |> Enum.reduce(fn a, b -> trunc(lcm(a, b)) end)
end
defp count_cycles_until_low_pulse(modules, watch_list) do
infinite_ints = Stream.iterate(1, &(&1 + 1))
cycle_counts = Map.new(watch_list, &{&1, false})
Enum.reduce(infinite_ints, {modules, cycle_counts}, fn i, {modules, cycle_counts} ->
# We cannot inspect the states after the button is pushed because the
# modules we are looking for are resetting before the modules are
# returned.
#
# So we need to inspect the emitted pulses and return from that.
{modules, cycle_counts} =
push_button(modules, cycle_counts, fn pulses, counts ->
Enum.reduce(pulses, counts, fn
{_, 1, _}, counts ->
counts
{from, 0, _}, counts ->
case Map.get(counts, from) do
false -> Map.put(counts, from, i)
_ -> counts
end
end)
end)
if Enum.all?(cycle_counts, fn {_, count} -> count end) do
throw({:counts, cycle_counts})
end
{modules, cycle_counts}
end)
catch
{:counts, counts} -> counts
end
defp push_button(modules) do
init_pulse = {"button", 0, "broadcaster"}
{_count_low, _count_high, _modules} = reduce([init_pulse], modules, 0, 0)
end
defp reduce([], modules, count_low, count_high) do
{count_low, count_high, modules}
end
defp reduce(pulses, modules, count_low, count_high) do
{count_low, count_high} = count_pulses(pulses, count_low, count_high)
{new_pulses, new_modules} =
Enum.flat_map_reduce(pulses, modules, fn {_, _, to} = p, modules ->
case Map.fetch(modules, to) do
{:ok, module} ->
{next_pulses, new_module} = handle_pulse(p, module)
modules = Map.put(modules, to, new_module)
{next_pulses, modules}
:error ->
{[], modules}
end
end)
reduce(new_pulses, new_modules, count_low, count_high)
end
defp push_button(modules, acc, f) do
init_pulse = {"button", 0, "broadcaster"}
{_modules, _acc} = run([init_pulse], modules, acc, f)
end
defp run([], modules, acc, _f) do
{modules, acc}
end
defp run(pulses, modules, acc, f) do
{new_pulses, new_modules} =
Enum.flat_map_reduce(pulses, modules, fn {_, _, to} = p, modules ->
case Map.fetch(modules, to) do
{:ok, module} ->
{next_pulses, new_module} = handle_pulse(p, module)
modules = Map.put(modules, to, new_module)
{next_pulses, modules}
:error ->
{[], modules}
end
end)
new_acc = f.(new_pulses, acc)
run(new_pulses, new_modules, new_acc, f)
end
defp handle_pulse({_, kind, me}, {:bcast, _, outs} = this) do
# There is a single broadcast module (named broadcaster). When it receives a
# pulse, it sends the same pulse to all of its destination modules.
sends = send_all(outs, me, kind)
{sends, this}
end
defp handle_pulse({_, 0, me}, {:flip, state, outs}) do
# if a flip-flop module receives a low pulse, it flips between on and off.
# If it was off, it turns on and sends a high pulse. If it was on, it turns
# off and sends a low pulse.
{new_state, send_kind} =
case state do
:off -> {:on, 1}
:on -> {:off, 0}
end
sends = send_all(outs, me, send_kind)
this = {:flip, new_state, outs}
{sends, this}
end
defp handle_pulse({_, 1, _}, {:flip, _, _} = this) do
# If a flip-flop module receives a high pulse, it is ignored and nothing
# happens.
{[], this}
end
defp handle_pulse({from, kind, me}, {:conj, state, outs}) do
# Conjunction modules (prefix &) remember the type of the most recent pulse
# received from each of their connected input modules; they initially
# default to remembering a low pulse for each input. When a pulse is
# received, the conjunction module first updates its memory for that input.
# Then, if it remembers high pulses for all inputs, it sends a low pulse;
# otherwise, it sends a high pulse.
state = Map.replace!(state, from, kind)
send_kind = if all_high?(state), do: 0, else: 1
sends = send_all(outs, me, send_kind)
this = {:conj, state, outs}
{sends, this}
end
defp all_high?(map) do
Enum.all?(map, fn
{_, 1} -> true
_ -> false
end)
end
defp send_all(outs, me, kind) do
Enum.map(outs, &{me, kind, &1})
end
defp count_pulses([{_, 0, _} | pulses], count_low, count_high) do
count_pulses(pulses, count_low + 1, count_high)
end
defp count_pulses([{_, 1, _} | pulses], count_low, count_high) do
count_pulses(pulses, count_low, count_high + 1)
end
defp count_pulses([], count_low, count_high) do
{count_low, count_high}
end
defp lcm(0, 0), do: 0
defp lcm(a, b), do: a * b / Integer.gcd(a, b)
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 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
exists
Part 1 was essentially just parsing and implementing the instructions from the question.
I must say that my solution to part 2 is not quite a general solution, but depends on the shape of the input graph…
Spoilers
I used
dotfrom graphviz to plot the input graph, and I noticed that there are 4 distinct components: branching directly from thebroadcaster, and merging into a single “conjunction” vertex than then feeds intorx. Each of the components by themselves can be processed by just running the same thing as in part 1 - for me they produce the desired signal as their output in about ~3900 steps. Then the desired number is just the least common multiple of those (curiously, all 4 were primes in my case, so I could just multiply them).Code
bjorng
It took me a while to wrap my head abut the rules for the modules in part 1 and do the actual implementation.
For part 2 I suspected that it was possible to somehow divide-and-concur the problem, but I found that some modules were involved in when I tried to convert the input to a tree. It turns out that the cycles don’t matter, because the input can still be cleanly partitioned into separate parts, which each has a broadcaster with a single destination. By partitioning the input data into separate parts I could re-use most of my code from part 1 for part 2.
https://github.com/bjorng/advent-of-code-2023/blob/main/day20/lib/day20.ex
woojiahao
Very tricky day and it took me a while to clean up the code to remove the use of ETS entirely.
Key Observations for Part 2
rx&c, it has inputs frommother conjunctions&csends a low pulse torx, we need to find the LCM of the first times that every connected conjunction sends a high pulse to&csince that means all of them sent a high pulse at once, causing&cto send a low pulse torxMaybe not the cleanest but I’m happy it works:
https://github.com/woojiahao/aoc/blob/main/lib/aoc/y2023/day_20.ex
seeplusplus
Edit I fixed this by calling the protocol method directly, will share more when finished. I can’t delete this post.
Apologies if this isn’t the right place to ask for help, but since everyone here is already familiar with the problem, I figured it would be the best place to ask.
I wanted to have each node type (broadcaster, flipflop, conjunction) etc as its own module and store them together in a map (code attached). The issue I’m running into is that the map is a homogeneous collection of nodes, so how can I call the underlying
send_signalmethod for the correct nodes? I thought implementing the same protocol for each would help, but I’m missing something. Here’s my attached code with comments to clarify my question:midouest
Part 2 thoroughly stumped me. I finally cracked it by rendering the graph and the internal flip flop states with
Kino.Mermaid.After looking at how the flip-flop states were changing over several iterations, it became clear that the machine consisted of 4 independent 12-bit number channels. Each 12-bit number channel had an arbitrary number of bits connected to a NAND gate that fed to both rx and the lowest bit. I figured out the order of the bits and which ones were connected to rx and then used that to find the LCMs of each channel. Then I found the LCM of the 4 channels!
Part 1
Part 2
You can see I used the for-loop here to explore the graph at different iterations.
pehbehbeh
Part 1: Implementing a working queue system for the machine and press it 1000 times.
Part 2: Press the button as long as the grand children (conjunction) modules of
rxeach sent a high. Then get the LCM of these highs. Some hints from Reddit and rendering the graph really helped…https://github.com/pehbehbeh/adventofcode/blob/main/2023/20.livemd