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.
|
||||
|
||||
<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
|
||||
|
||||
|
|
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