diff --git a/README.md b/README.md
index 3cf6de6..500d52e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
My (attempted) solutions to [Advent of Code 2020](https://adventofcode.com/2020) in Elixir.
-
+
## Strategy
diff --git a/day23/README b/day23/README
new file mode 100644
index 0000000..6acf3bb
--- /dev/null
+++ b/day23/README
@@ -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).
+
+
diff --git a/day23/day23part1.exs b/day23/day23part1.exs
new file mode 100644
index 0000000..c2131ea
--- /dev/null
+++ b/day23/day23part1.exs
@@ -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()
diff --git a/day23/day23part2.exs b/day23/day23part2.exs
new file mode 100644
index 0000000..4582acc
--- /dev/null
+++ b/day23/day23part2.exs
@@ -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()