Day 23 Parts 1 and 2
This commit is contained in:
parent
2786c71627
commit
c9e6f4bf97
4 changed files with 182 additions and 1 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
My (attempted) solutions to [Advent of Code 2020](https://adventofcode.com/2020) in Elixir.
|
My (attempted) solutions to [Advent of Code 2020](https://adventofcode.com/2020) in Elixir.
|
||||||
|
|
||||||
<img width="982" alt="image" src="https://user-images.githubusercontent.com/498229/103125884-e8a16d00-46cf-11eb-854d-67e600374e82.png">
|
<img width="978" alt="image" src="https://user-images.githubusercontent.com/498229/103139031-545cf780-471b-11eb-9c94-95c49a1391ba.png">
|
||||||
|
|
||||||
## Strategy
|
## Strategy
|
||||||
|
|
||||||
|
|
52
day23/README
Normal file
52
day23/README
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
Day 23 Notes
|
||||||
|
|
||||||
|
+--------+
|
||||||
|
| Part 1 |
|
||||||
|
+--------+
|
||||||
|
|
||||||
|
$ elixir day23part1.exs
|
||||||
|
59374826
|
||||||
|
|
||||||
|
Thoughts:
|
||||||
|
|
||||||
|
Need to represent a circle.
|
||||||
|
Use a combination of lists, and Stream.cycle to handle the wrap-around.
|
||||||
|
|
||||||
|
+--------+
|
||||||
|
| Part 2 |
|
||||||
|
+--------+
|
||||||
|
|
||||||
|
$ elixir day23part2.exs
|
||||||
|
Next two cups: 266262, 251174
|
||||||
|
Answer: 66878091588
|
||||||
|
|
||||||
|
Thoughts:
|
||||||
|
|
||||||
|
Considering the numbers involved (1M items, 10M iterations), the Part 1 solution with its
|
||||||
|
O(n) list operations is just not going to work. We need a solution that works in constant time.
|
||||||
|
|
||||||
|
How to represent the circle: we need a doubly-linked list with random access *in constant time*.
|
||||||
|
Two options I can think of: Erlang's :array, or :ets. :array's documentation doesn't say anything
|
||||||
|
about its performance and the arrays seem resizable, however :ets says it's constant time, so I
|
||||||
|
decided to use ETS.
|
||||||
|
|
||||||
|
Completely re-write the part 1 implementation. Represent the circle in ETS with the keys being the
|
||||||
|
cup label, and the values being a {prev, next} tuple.
|
||||||
|
|
||||||
|
Remove from the circle: Update the two new neighbours to point to each other.
|
||||||
|
Add to the circle: Update the four new neighbours (left & right extremities) to point to each
|
||||||
|
other.
|
||||||
|
|
||||||
|
Even after these efficiency improvements, this still takes 22 seconds on my machine >.<
|
||||||
|
|
||||||
|
+------------------+
|
||||||
|
| Overall Thoughts |
|
||||||
|
+------------------+
|
||||||
|
|
||||||
|
Interesting problem. Produced an elegant solution for Part 1 that simply wasn't scalable for Part
|
||||||
|
2. Part 2 required working around the beam's limitations of number-crunching a large dataset. ETS
|
||||||
|
kind of worked, but was still very slow, so this is obviously not scalable. I will be interested
|
||||||
|
to see if people came up with a solution that completes in less than a second (or even 10 seconds,
|
||||||
|
as per the guideline).
|
||||||
|
|
||||||
|
|
47
day23/day23part1.exs
Normal file
47
day23/day23part1.exs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
defmodule Day23Part1 do
|
||||||
|
# @input "389125467"
|
||||||
|
@input "137826495"
|
||||||
|
def run do
|
||||||
|
@input
|
||||||
|
|> String.graphemes()
|
||||||
|
|> Enum.map(&String.to_integer/1)
|
||||||
|
|> play(100)
|
||||||
|
|> cycle_to(1)
|
||||||
|
|> Enum.drop(1)
|
||||||
|
|> Enum.join()
|
||||||
|
|> IO.puts()
|
||||||
|
end
|
||||||
|
|
||||||
|
def play(cups, 0), do: cups
|
||||||
|
def play(cups, times), do: move(cups) |> play(times - 1)
|
||||||
|
|
||||||
|
def move([current, a, b, c | rest]) do
|
||||||
|
holding = [a, b, c]
|
||||||
|
dest = find_dest(current, holding)
|
||||||
|
|
||||||
|
insert(holding, dest, [current | rest])
|
||||||
|
|> cycle_to(current, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_dest(1, holding), do: find_dest(10, holding)
|
||||||
|
|
||||||
|
def find_dest(current, holding) do
|
||||||
|
dest = current - 1
|
||||||
|
if dest in holding, do: find_dest(dest, holding), else: dest
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert(holding, dest, circle) do
|
||||||
|
[dest | rest] = cycle_to(circle, dest)
|
||||||
|
[dest | holding] ++ rest
|
||||||
|
end
|
||||||
|
|
||||||
|
def cycle_to(circle, target, offset \\ 0) do
|
||||||
|
circle
|
||||||
|
|> Stream.cycle()
|
||||||
|
|> Stream.drop_while(&(&1 != target))
|
||||||
|
|> Stream.take(length(circle) + offset)
|
||||||
|
|> Enum.drop(offset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Day23Part1.run()
|
82
day23/day23part2.exs
Normal file
82
day23/day23part2.exs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
defmodule Day23Part2 do
|
||||||
|
@input "137826495"
|
||||||
|
def run do
|
||||||
|
{a, b} =
|
||||||
|
@input
|
||||||
|
|> String.graphemes()
|
||||||
|
|> Enum.map(&String.to_integer/1)
|
||||||
|
|> init_cups()
|
||||||
|
|> play(10_000_000)
|
||||||
|
|> next_two_after_1()
|
||||||
|
|
||||||
|
IO.puts("Next two cups: #{a}, #{b}")
|
||||||
|
IO.puts("Answer: #{a * b}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def init_cups([first, second | _] = input) do
|
||||||
|
cups = :ets.new(:circle, [])
|
||||||
|
|
||||||
|
(input ++ for(i <- 10..1_000_000, do: i))
|
||||||
|
|> Stream.chunk_every(3, 1)
|
||||||
|
|> Stream.each(fn
|
||||||
|
[a, b, c] ->
|
||||||
|
:ets.insert(cups, {b, {a, c}})
|
||||||
|
|
||||||
|
[penultimate, last] ->
|
||||||
|
:ets.insert(cups, {first, {last, second}})
|
||||||
|
:ets.insert(cups, {last, {penultimate, first}})
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
|
||||||
|
{first, cups}
|
||||||
|
end
|
||||||
|
|
||||||
|
def play({_current, cups}, 0), do: cups
|
||||||
|
def play(state, times), do: move(state) |> play(times - 1)
|
||||||
|
|
||||||
|
def move({current, cups}) do
|
||||||
|
holding = take_three(cups, current)
|
||||||
|
dest = find_dest(holding, current)
|
||||||
|
cups = insert(holding, dest, cups)
|
||||||
|
[{^current, {_prev, next}}] = :ets.lookup(cups, current)
|
||||||
|
|
||||||
|
{next, cups}
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_dest(holding, 1), do: find_dest(holding, 1_000_001)
|
||||||
|
|
||||||
|
def find_dest(holding, current) do
|
||||||
|
dest = current - 1
|
||||||
|
if dest in holding, do: find_dest(holding, dest), else: dest
|
||||||
|
end
|
||||||
|
|
||||||
|
def take_three(cups, current) do
|
||||||
|
[{^current, {prev, a}}] = :ets.lookup(cups, current)
|
||||||
|
[{^a, {^current, b}}] = :ets.lookup(cups, a)
|
||||||
|
[{^b, {^a, c}}] = :ets.lookup(cups, b)
|
||||||
|
[{^c, {^b, next}}] = :ets.lookup(cups, c)
|
||||||
|
[{^next, {^c, last}}] = :ets.lookup(cups, next)
|
||||||
|
:ets.insert(cups, {current, {prev, next}})
|
||||||
|
:ets.insert(cups, {next, {current, last}})
|
||||||
|
[a, b, c]
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert([a, b, c], dest, cups) do
|
||||||
|
[{^dest, {prev, next}}] = :ets.lookup(cups, dest)
|
||||||
|
[{^next, {^dest, last}}] = :ets.lookup(cups, next)
|
||||||
|
:ets.insert(cups, {dest, {prev, a}})
|
||||||
|
:ets.insert(cups, {a, {dest, b}})
|
||||||
|
:ets.insert(cups, {b, {a, c}})
|
||||||
|
:ets.insert(cups, {c, {b, next}})
|
||||||
|
:ets.insert(cups, {next, {c, last}})
|
||||||
|
cups
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_two_after_1(cups) do
|
||||||
|
[{1, {_prev, next}}] = :ets.lookup(cups, 1)
|
||||||
|
[{^next, {1, nextnext}}] = :ets.lookup(cups, next)
|
||||||
|
{next, nextnext}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Day23Part2.run()
|
Loading…
Reference in a new issue