AdventOfCode/2020/elixir/day18
2022-08-11 16:22:22 +09:00
..
day18part1.exs Move 2020 elixir solutions to 2020/elixir in prep for zig 2022-08-11 16:22:22 +09:00
day18part2.exs Move 2020 elixir solutions to 2020/elixir in prep for zig 2022-08-11 16:22:22 +09:00
input Move 2020 elixir solutions to 2020/elixir in prep for zig 2022-08-11 16:22:22 +09:00
README Move 2020 elixir solutions to 2020/elixir in prep for zig 2022-08-11 16:22:22 +09:00

Day 18 Notes

+--------+
| Part 1 |
+--------+

$ elixir day18part1.exs
21347713555555

Thoughts:
Since the order of precedence is the same, we can walk the token list, building up a left operand
and operator as we go, then applying the operator as soon as we get the right operand.  For groups,
use recursion to resolve the group, and push the result back onto the beginning of the token list.


+--------+
| Part 2 |
+--------+

$ elixir day18part2.exs
275011754427339

Thoughts:

Now the order of precedence is relevant, so can no longer just walk the token list, need to know the
full context.
Addition basically becomes implicit brackets.

If we have a * operand, for example 2 in 1 * 2 + 3, we can't apply * without first looking ahead
to see if there is a higher precedence +.

I decided to build an Abstract Syntax Tree, and then evaluate it. To build the AST, I used the
following strategy:

1. First pass - resolve + operations.
   For numbers and * operators, store them in a _later_ buffer until we know what to do with them.
   For + operators, we can safely add the previous operand in the later buffer with the next
   operand.
   Recurse into brackets.
2. Second pass - resolve * operations.
   The second pass runs at the end of any bracket group, or the end of string.
   All the additions have been handled, so can safely just multiply these.
3. Finally, evaluate the AST recursively to get the answer.

I spent far too long on this and it was very difficult to debug. The examples provided were very
useful and hit a lot of edge cases. Again, I really struggle to reason about any form of recursion
that is more complex than a simple loop. There were lots of IO.inspects going on in the development
of this. The answer can probably be simplified further, but I'm pretty happy that I managed to
implement it using an AST with multiple function clauses as a proof of concept.

It's pretty hard to tell what all the clauses do at first glance, but then that would be true for a
a huge function with lots of embedded conditionals too. I will definitely be seeing how other people
implemented this one.

+------------------+
| Overall Thoughts |
+------------------+

Part 1 was very simple due to it not having precedence, and I think it can be implemented nicely in
Elixir. Part 2's deceptively simple requirements were surprisingly difficult - probably because of
the approach I took with building an AST.

Considering I have less time to spend on the last week of questions, I decided to chip away at this
one over a few days. Now I'm spoilt for choice for which one to do next! 17, 19, or 20...


Update: this solution is great. Wish I'd thought of it. What I was trying to do here, but simple and
elegant: https://elixirforum.com/t/advent-of-code-2020-day-18/36300/6