Note by the Moderators: This topic is to talk about Day 5 of the Advent of Code.
For general discussion about the Advent of Code 2018 and links to topics of the other days, see this topic.
This is my Day 5. My other days are a bit of a mess (constantly refactoring to try out different ways to make it look better). With day 5 though, I saw a lot of places for a bit of optimization like storing the length in the fully_react reduction.
My take-away from day 5’s code is to keep things simple. And read all of the documentation because I’ve been creating functions when I don’t need to.
My first attempt at the challenge was to do a single pass through the characters in pairs, remove them if the fitted the matching criteria which created a new string to parse and the changing the current ‘cursor’ position to handle the string length change so that I didn’t skip letters. It worked but it was as messy as it sounds (possibly more-so!) and it was really slow.
Solving part 2 whilst part one is slow is possible but I’d have probably had time for a nap whilst it ran so I decided to try a simpler approach. This time I converted the polymers into a character list (I originally used graphemes but saw one of a few great tricks in @sionide21’s solution… see below) so that I could use recursive pattern matched functions to process the polymers. If at the end of a run through the characters the resulting string was shorter than the original I’d re-process the characters and carry on until changes had been exhausted. This was much, much faster.
Part 2 was where I encountered some new standard library functions and in particular Enum.uniq/1 and Enum.reject/2, both of which allowed me to remove Enum.reduce/3 calls. The combination of Map.values/1 and Enum.min/1 were also really helpful at the end of the pipeline and saved me from writing yet another Enum.reduce/3.
After writing my code I looked at @sionide21’s solution which helped me tidy a few things up. He’d used recursive pattern matching functions too but passed a boolean flag between them to indicate whether the polymers had been edited. This is cleaner. He also took advantage of character codes to handle upper and lower case checks: abs(a - b) == 32. I’d used an if statement inside a single function but I thought of @pragdave and changed it and could then use two functions. If I’d retro-fitted the change flag in place of my string length parameter I could have done the same for the last function.
Overall I was really pleased with how today’s solution turned out and it was gratifying to see that I was heading in the same direction as more experienced Elixir developers.
My original solution was a pretty hacky regexy thing in rust. After I’d finished and looked at the solutions mega thread the Haskell foldr five liner at the top was a bit of a blow . I made an elixir version which uses lists rather than binaries which I think is nice too:
(EDIT: very similar to Ryan’s solution above too)
defmodule Day5 do
Day 5 Elixir solutions for Advent of Code 2018
Removes all reacting units from the input and returns the final length
Algorithm from a Haskell implementation in reddit:
def react(input) do
|> List.foldr(, &react/2)
# two units react when the difference in their ASCII code is 32
defguard reacts(unit, prev_unit) when abs(unit - prev_unit) == 32
# handle the start case
defp react(unit, ), do: [unit]
# handle when the last two elements in the list react
defp react(unit, [prev_unit]) when reacts(unit, prev_unit), do: 
# if there's a reaction just return the tail to remove reacting elements
defp react(unit, [prev_unit | tail]) when reacts(unit, prev_unit), do: tail
# all good, just add the unit to the list
defp react(unit, tail), do: [unit | tail]
I’m using Advent of Code as an excuse to learn me some Elixir for great good.
In one of José Valim’s videos he mentioned how it’s always better to append at the head of a list than to append at the bottom of it, and oh wow, does it make a difference!
I had code like this in my functions: reduced = reduced_polymer ++ [head1] all because I wanted to preserve the order in the polymer.
So, my Day5 Part1 was initially clocking in at # Time in seconds: 162.826491.
Then I realized that it didn’t matter because I was not after the final string, only its length, and I could always reverse it at the end and get that if needed. So I changed that code to: reduced = [head1 | reduced_polymer] and it went down to: # Time in seconds: 0.28561000000000003 O_O.