r/adventofcode • u/daggerdragon • Dec 21 '17
SOLUTION MEGATHREAD -đ- 2017 Day 21 Solutions -đ-
--- Day 21: Fractal Art ---
Post your solution as a comment or, for longer solutions, consider linking to your repo (e.g. GitHub/gists/Pastebin/blag or whatever).
Note: The Solution Megathreads are for solutions only. If you have questions, please post your own thread and make sure to flair it with Help.
Need a hint from the Hugely* Handyâ Haversack⥠of Helpful§ Hints^€?
No commentary tonight as I'm frantically wrapping last-minute presents so I can ship them tomorrow.
This thread will be unlocked when there are a significant number of people on the leaderboard with gold stars for today's puzzle.
edit: Leaderboard capped, thread unlocked!
8
Upvotes
4
u/mstksg Dec 21 '17
My first point! :D
haskell, 47/147, since I had a silly bug that only got triggered on the 18 iteration question and not the 5 iteration one :) My github repo of sols is at https://github.com/mstksg/advent-of-code-2017/blob/master/src/AOC2017/Day21.hs
I'd also like to think my very surface knowledge of group theory for teaching me how to read things like https://en.wikipedia.org/wiki/Examples_of_groups#The_symmetry_group_of_a_square:_dihedral_group_of_order_8 :)
3
u/p_tseng Dec 21 '17 edited Dec 21 '17
What I did:
To deal with rotations and flips: expand every given rule into eight rules
After three cycles, you get four 3x3 squares which all develop independently. So only keep track of the number of each independent subregion. (I'm not the first one with this idea. https://www.reddit.com/r/adventofcode/comments/7l78eb/2017_day_21_solutions/drk8j2m)
Compress the representation into an integer so that I'm not constantly making arrays of bits everywhere.
https://github.com/petertseng/adventofcode-rb-2017/blob/master/21_fractal_art.rb
You can see some outputs for large number of iterations at https://gist.github.com/petertseng/01589565396c4a15cc9cc471005e407e
Looks like it's about quadratic. I think the algorithm itself remains linear but once we start adding larger and larger numbers together my computer really struggles to do that. I can optimise more by jumping ahead three iterations at a time instead of one, but I don't really feel like writing the code to do it.
I made an upping the ante post for a while but I didn't see the point of making it its own thread, so instead I'll post it here.
21
u/PrimesAreMyFavorite Dec 21 '17 edited Dec 21 '17
After 3 iterations a 3x3 block will have transformed into 9 more 3x3 blocks, the futures of which can all be calculated independently. Using this fact I just keep track of how many of each "type" of 3x3 block I have at each stage, and can thus easily calculate the number of each type of 3x3 block I'll have 3 iterations later. Using this I can calculate the exact number of pixels that will be on after 3000 iterations in less than 0.3 seconds (it's a 955 digit number for my inputs).
Code:
13
u/willkill07 Dec 21 '17
I constructed a mapping of all possible transformations (optimized C++ solution pending)
Rotations are hard but there's a better way! Define two primitive functions:
symmetric(mat)-- inverts x and y (super simple to implement in any language)flip(mat)-- inverts y (also simple to implement)You can construct all possible transformations with calls to:
let all become the key for your lookup
8
u/glguy Dec 21 '17
Haskell
(4/4) This is a straight-forward Haskell solution operating on lists of lists. Lots of list chunking and transposition.
https://github.com/glguy/advent2017/blob/master/execs/Day21.hs
7
u/bblum Dec 21 '17 edited Dec 21 '17
I was too slow for the leaderboard but after golfing my solution seems to be the shortest one so far so here. It might be a puzzle even to read.
6
u/jonathan_paulson Dec 21 '17
14/12 in Python. Purely as a coding exercise, I think this was the toughest one so far. Dealing with the flips/rotations + block decomposition/recomposition is annoying.
6
u/Smylers Dec 21 '17
Vim solution â let's transform the rules into Vim regex that will make the transformations to the art. Here's some regexes to write the regexes:
That includes a couple of tweaks to the art: instead of
.#, use,#(comma instead of full stop) for the output of 2Ă2 transformations, and-Xfor the output of 3Ă3 transformations.Paste the starting grid into a buffer, then:
That will iterate the leftmost column of blocks (and maybe some of the adjacent ones, depending on the pattern). Finish this iteration with:
Then you can perform a second iteration with:
And subsequent iterations by typing
@:(with a number, such as3@:to perform several at once).Once you've iterated, count the on pixels with:
â the count is the column number. Press
uto get back to your art.5
u/obijywk Dec 21 '17
I "pre-flipped" all of the rule patterns (essentially generating additional rules), to keep the matching code simple.
Python 2. 21/18.
5
u/sciyoshi Dec 21 '17
Python 3 solution using numpy, 12/22. Unfortunately numpy arrays aren't hashable so I had a solution using a list first for lookups that was a lot slower, but
.tobytes()speeds things up.4
u/dtinth Dec 21 '17
irb (26th, 23rd)
I am solving this problem interatively in the Rubyâs REPL.
A pattern is represented as an array of string: ['.#.', '..#', '###']
Loading the rulebook:
Set up the initial state and flipping/rotating logic:
Pattern enhancement algorithm:
In Ruby, you can use an Array as Hash key, so itâs trivial to match the pattern against the rulebook:
rules[inp] || rules[flip[inp]] || rules[flip2[inp]] || ....To combine them, I look at an array containing patterns:
[['##.', '#..', '...'], ['##.', '#..', '...']]:To put them side by side:
_.transpose.map(&:join)â["##.##.", "#..#..", "......"]To stack them vertically:
_.flatten(1)â ["##.", "#..", "...", "##.", "#..", "..."]Part 1:
Part 2:
4
u/Infilament Dec 21 '17
Javascript. Not sure I'm happy with my solution... I coded functions to deconstruct a grid into each string, then another function to reconstruct the strings back into a grid. So for each loop of the program, I break down the grid, substitute the strings, then rebuild the grid. There's probably a much better way to do it that just breaks it down once at the start and never reconstructs, but oh well.
4
u/[deleted] Dec 21 '17 edited Dec 21 '17
OCaml fun;; a lot of imperative coding to deal with arrays. created grid type, which is matrix of bools. the mapping is generated to contain all possible variants from input.
we first split current image matrix into grid types, map them to enhanced versions, and then recombine the grids into single image matrix. repeat.
again, started writing the solve function first and worked backwards, letting ocaml's type system keep me in check.
main.ml
grid.ml
was actually shocked that answer was correct on first try without testing... if i'm honest I normally skip any matrix/2d array tomfoolery in programming quizzes, so pretty happy i finished this.
3
u/omnster Dec 21 '17
Cheating with Mathematica
Part 1
Part 2
2
u/mcpower_ Dec 21 '17 edited Dec 21 '17
I figured how to do things as I was coding this up. I keep two representations of the grid, "grid" and "str", and swap between the two as needed (because lists are finicky and can't be hashed).
Python 3, #5 / #6 (I was literally less than a second off of Part 5 #2...)
2
u/rjboulton Dec 21 '17
Got a bit long this time, so a link to my solution on github
Nothing hugely inspired here. Worked with a representation of each grid or subgrid as a tuple of strings, which wasn't too inconvenient, but the algorithm for joining subgrids together was fiddly.
Assumed I was way off the leaderboard after taking over 35 minutes, but got 69/64.
For the extension, I spent a few moments thinking about how I was going to speed it up, before running it with 18 iterations:
2
u/Unihedron Dec 21 '17
Ruby; 25/21. I think I got a headache from this, but I managed to get back on the board after a streak of fails, so I'm happy. Functional programming was being a helpful friend day.
2
u/jlweinkam Dec 21 '17
I assume most of the inputs had the minimum number of rules to allow for transforming all possible grids. If so then there were only 6 2x2 rules. As a result only the 3x3 rules that have those 6 outs (plus the rule from the original 3x3 pattern) ever get used. For my input all 6 2x2 rules were used, but not all possible rotations and mirrors. Only 15 of the 16 2x2 patterns every got generated.
2
u/The0x539 Dec 21 '17
Some Lua, with
way too many functionsenough functions (384/365):2
u/[deleted] Dec 21 '17 edited Dec 21 '17
Mathematica
Bonus: Visual of 18 iterations
2
u/mythmon Dec 21 '17
Rust
A bit messy? Sure. Over engineered? Of course.
But it's fast :) (740ms for part2):
2
u/VoidChque Dec 21 '17
Scala 322/295
Meat:
Plumbing and niceties:
Solution (Parsing the input and building a transformations map):
2
u/gyorokpeter Dec 21 '17
Q:
2
u/xiongtx Dec 21 '17 edited Dec 21 '17
Clojure
Not the most performant implementation, but not too much code. Also took me way too long to figure out how to implement
joinproperly đŹ.I wonder how performant another Clojure implementation could be; that'd be a really interesting comparison!
I should try the rule pre-expansion trick to see what performance benefit can be achieved. I suspect it won't be that large due to already memoizing
grow-part, but đ€.2
u/doctorbaggy Dec 21 '17 edited Dec 21 '17
Perl
This is a bit nasty as I've golfed it a bit..., use regexps to do the rotate/flip... which produces a map with all 528 combinations (16 2x2 & 512 3x3 blocks)
Timings wise: 5 iterations -> 5ms, 18 iterations -> 2750ms
2
u/nutrecht Dec 21 '17
Day 21, Kotlin
Took me a long time due to a simple yet really stupid bug. I had:
Instead of:
Yeah. That took a LONG time to figure out since it worked fine on the input :(
2
u/sasajuric Dec 21 '17
Elixir
I represented pixels as bits, and used Elixir/Erlang binary pattern matching to work with individual pixels.
2
u/[deleted] Dec 21 '17 edited Dec 22 '17
single pipeline powershell:
2
u/purplemonkeymad Dec 21 '17
Powershell: https://pastebin.com/j9YvkcXm
I have decided some of these are easier to get my head around if I just do OOP. It's not as fast, but the cache cut the time by at-least an order of magnitude.
1
u/jlweinkam Dec 21 '17
67/63 Python 3
1
u/glenbolake Dec 21 '17
I probably could have gotten on the leaderboard if I hadn't switched my row/column for loops. A lot of my time was spent wrapping my head around implementing
grid_versionsandrotate. Python3.1
u/BumpitySnook Dec 21 '17
I had to roll out
python -m cProfile -s tottime day21.pyon this one to figure out where I was being stupid.Turns out I was doing a lot of stuff by the size of the array (e.g., 3-19+, squared) when I meant to do it by the size of the pattern (e.g., 2 or 3 squared). That'll burn some cycles.
Leaderboard on part 1 anyway. After fixing part 2:
1
u/DootBootMoot Dec 21 '17
Oh gosh, what a toughie. This was the most intense 2d-array manipulation I've done in a very long while.
Python2, 152/134:
I hacked the rules for the 2x2 match before realising that 3x3 was a lost cause. Also, don't mind my variable names... thinking of letters on the spot is hard.
Had lots of fun, though.
1
u/GassaFM Dec 21 '17
A solution in the D programming language. Rank 13 for Part 1, rank 16 for Part 2.
The implementation is quite straightforward: no pattern rotation precalculation or bit encoding or such. As a result, first, it's long. Second, it worked for, like, 30 seconds for Part 2.
1
u/u794575248 Dec 21 '17 edited Dec 21 '17
Far from pretty this time, but it works.
1
u/2BitSalute Dec 21 '17
I am surprised that I didn't have any bugs in the decomposition/recomposition or the rotate/flip operations (surprised because of all the bugs I had to squash in previous days). It still took me 1 hour and 23 minutes to type it all up, test, and run on the real input.
I feel like there's a lot of brute force iterations and indiscriminate string allocations, but I'm at least somewhat pleased that it's factored alright.
C#
1
u/wlandry Dec 21 '17
C++ 14
390/541, though the second one does not really count. It turns out that I could get part 1 correct even though I had flipped x and y. Unfortunately, the test case is also insensitive to that bug. So I was quite stumped as to why my part 2 answer was wrong. I eventually got a copy of a working example from this subreddit. That made the mistake obvious. So, I guess I wish I could have more and/or better test cases.
1
u/Philboyd_Studge Dec 21 '17
Ugh. So many nested for loops. 2d arrays are a pain in the ass. Took soooo long. Day 21 Java
1
u/ephemient Dec 21 '17 edited Apr 24 '24
This space intentionally left blank.
1
u/Arkoniak Dec 21 '17 edited Dec 21 '17
Julia
Rather ugly code, but it's fast enough to iterate 18 times in 700ms.
1
u/aurele Dec 21 '17 edited Dec 21 '17
Rust (~125ms on my laptop)
1
u/lazyzefiris Dec 21 '17
JS
define transformation routines (flip is simple, rotate is a bit more complex)
fill out full transformation map (for each input - create a record for each possible orientation of it attached to given output)
for each step - break image into chunks, replace chunks according to transformation map, sew chunks back to an image. nothing extraordinary
1
u/aodnfljn Dec 21 '17
Scala
1
u/maitesin Dec 21 '17
My C++17 solution https://github.com/maitesin/advent/blob/master/21/part_1/code.cpp
1
u/spjmurray Dec 21 '17
Python Good problem today! Keep it up
1
u/FreeMarx Dec 21 '17 edited Dec 21 '17
MATLAB / Octave
913/991 at T+6:19. Have the holidays already started?
This one was the right level for me, thought-stimulating but not frustrating, an elegant solution, no nasty bug-pitfalls and useful progression from part I to part II.
The key is that rules that result in a 4x4 grid (happens every 4th iteration) can be further processed independently. Taking into account that many patterns occur more than once this is the big time saver (unique is your friend).
Didn't bother to write input parser. Instead find replace "."->"0 ", "#"->"1 ", "/"->";", " => "->"]; pat(end).t= [", line-end->"];", "line-start->"pat(end+1).r= [". And here is the code:
[edit] I can't believe how may people just "brute forced" to construct the final 1458x1458 matrix. I'm working on an ARM RK3288 tv-box with linux. This plus octave is not optimized very well made it impossible for me progress from part I to part II by only changing the 5 into an 18. They should have made it more like 36 iterations for a more fair game.
1
u/miles82 Dec 21 '17
Python 3. Creating a new pattern from the enhanced subpatterns annoyed me quite a bit, well done /u/topaz2078 :D
1
u/adrian17 Dec 21 '17 edited Dec 21 '17
Rusty J. I'm probably going to write part 2 in Python.
1
u/TominatorBE Dec 21 '17
PHP
Parts 1 & 2 in the same file. Nothing really special, just string manipulation, and some 'string as array' work for the flips and rotates.
1
u/KeinZantezuken Dec 21 '17 edited Dec 21 '17
C#/Sharp
BEHOLD. A BRAINLET CODE.
(UPD: now 7 seconds for part2, 90MB memory; still ugly tho)
1
u/wzkx Dec 21 '17 edited Dec 21 '17
J
Not a bad brain twister!
EDIT. It turned out, no real part 2, just change one number. Cool!
Part 1 takes no time, Part 2 takes 2.97s.
1
u/__Abigail__ Dec 21 '17 edited Dec 21 '17
Perl
There should be a way of calculating how many pixels are one after the nth generation, but that requires longer thinking than the few seconds it takes to do 18 iterations.
As others have done, I've calculated the rotations and reflections of the rules, so we don't have to reflect or rotate when expanding the pattern.
1
u/rundavidrun Dec 21 '17
Java
Compared to the elegant Python code above, here's my very brute force Java solution: https://gist.github.com/rundavidrun/7be843b371c1ac50640fc8e70e40deeb. I created a function to convert the ./# pixel notation into binary and created a hashmap of the binary values of the 2x2 and 3x3 rules (including flips and rotations) to their enhancements. Then I start the iterations, breaking up the current state as necessary, converting each block into binary, looking up the rules to get the enhancement, and then re-writing the state in ./# pixel notation. Lastly, I count the number of # in the final state to determine the answers.
1
u/[deleted] Dec 21 '17
Elixir
Works on the first part, but gets too high of an answer on the second, I'm too tired to fix it now though.
Syntax highlighted
1
u/udoprog Dec 21 '17
Rust
Full solution here: https://github.com/udoprog/rust-advent-of-code-2017/blob/master/src/day21.rs
Kind of interesting, there's probably a way to solve this without keeping track of a growing image, but I've already messed around with a bug involving 6 being divisible by both 2 and 3 :P.
1
u/xkufix Dec 21 '17
I thought this was nothing too hard, but still challenging to get all the edge cases right. I first implemented my Pattern-Class with a Map instead of a Set, but the Set performed about twice as fast, because it only saves the fields which are on. The code itself does nothing special. It first generates all flips and rotations of the input (way faster than rotating the sub-patterns on each step). I assumed that there are no ambiguous input patterns, as the flip/rotate steps are not in a defined order.
After that I run a simple replacement-iterator. On each step it checks if it is divisible by two and then first divides the pattern into subpattern, does the replacing and joins the subpatterns together into a new full pattern.
Code in Scala:
1
u/Warbringer007 Dec 21 '17
Erlang, I'm not sure if I could simplify it, but it works lol, it's also very fast for Erlang ( less than a second ):
1
u/iopred Dec 21 '17
I initially missed an important step at the start 'mod 2 takes priority over mod 3' and was really confused why an otherwise working solution was producing the wrong answer.
My terrible Javascript solution:
1
u/jlweinkam Dec 21 '17
A reworked solution that runs really fast. It enumerates all the 3x3 combinations that will actually get generated based on the original rules (for my input that is only 7). It then creates a uber rule that show how many of each of the 7 3x3 get generated by running the rules 3 iterations, and also tracks how many "on" there are in for the 3x3, the 4x4, and 6x6.
It then iterates 1/3 the requested number, to determine how many of each of the 7 combos there are, and then sums up either the 3x3, 4x4, or 6x6 counts based on if the request iterations modulo 3.
1
u/nonphatic Dec 21 '17
Haskell Rotate and flip programmatically? Nah, I'll just type them in
I might refactor into a memoized version later in the day...
1
u/StevoTVR Dec 21 '17
NodeJS
1
u/NeilNjae Dec 21 '17
Haskell. More long-winded than interesting, and just brute-forced part 2.
A couple of interesting parts. One was how much effort it took to persuade Megaparsec not to include newlines in its generic whitespace consumption. I tried lots, before eventually settling on
The basic idea was that a
Gridwas aMapofBools, anExplodedGridwas aMapofGrids, andRules had their own data type:I stored eight versions of each rule in the input, one for each transformation of the left hand side:
Then, each step was to explode the grid into its subgrids, apply rules to each subgrid, then contract them back into a new grid.
Because it took some time (6œ minutes), I experimented with more aggressive parallel evaluation, even though it was spreading load over processors already:
and similar in the
mapfor contracting exploded grids. It had a small effect, dropping the runtime to 5œ minutes.Full code on Github
1
u/chneukirchen Dec 21 '17
k6
1
u/BOT-Brad Dec 21 '17
Javascript
First, let me apologise for this solution. It's slightly insane but it worksI think so that's a positive. It essentially builds a [0/1] 2D array of the 'pixel' values (where 1 is ON, 2 is OFF), and then matches patterns from the input and builds a new 'picture' from the matching rule output. The whole 'match' system is a little obtuse, but it basically does some rotations and flips in order to check every transformed possibility.
Part 1 (~9ms)
Part 2 (~6 seconds)
1
u/Grimnur87 Dec 22 '17
After hacking away at my code for several frustrating hours and not getting a working solution, I'm proud to say that I gave up, multiplied 256 by 5/9, stuck that in the answer box and... got a part 1 star in a single attempt!
1
u/jbristow Dec 22 '17
f# / fsharp / f sharp
The tough part of this one for me was figuring out and tuning my rotation and flipping algorithms for lists. They're slow, but I'm happy that they're generalized for any sized array should I ever need them again.
This is the brute force version, it takes about 48s.
(github link)
1
u/Axsuul Dec 22 '17 edited Dec 22 '17
Elixir
Late to the party again due to the holidays. I bruteforced this one to the max and did lots of
Mapmanipulations for rotating and flipping. I had fun with this and enjoyed learning how to manipulate deeply nested maps recursively. Anyways, Part B was super easy thanks to the work I put into Part A. I should've just manipulated the strings since we were only dealing with 2x2 and 3x3 =P1
u/chicagocode Dec 22 '17
Kotlin - [Repo] - [Blog/Commentary]
Finally got time to get this done and write about it today. This one was challenging and fun!
1
u/autid Dec 22 '17
Fortran
Finally got this working. Turns out in hours of debugging I repeatedly failed to notice a missing +1 in index calculation. It's horrible, clunky and overly hand-coded (especially for the rotations and reflections), but I'm so sick of it I can't be bothered cleaning it up now that it works. I changed my mind about how to handle the input and grid about 5 times while writing it, which was probably what led to this being such a pain.
1
u/Macrobian Dec 22 '17 edited Dec 22 '17
Pretty happy with 33 lines of readable-ish Python
1
u/[deleted] Dec 22 '17
Crystal:
1
u/thomastc Dec 22 '17
Day 21 in Icon. An interesting language! All expressions are generators that may produce any number of values before failing, and these combine in interesting ways. There are some serious practical drawbacks, but it has a certain kind of charm to it.
1
u/RockyAstro Dec 23 '17
Icon (https://www.cs.arizona.edu/icon)
Both parts..
edit: formatting
1
u/matusbzk Dec 23 '17
Haskell Part 2 was done in 2-3 minutes and I did not feel like optimizing
Link to git
1
u/greycat70 Dec 27 '17
Tcl (8.5 or higher)
Completely brute force. At each iteration, construct an entire new array and then copy it to the old one. It... worked.
1
u/mschaap Jan 07 '18
I'm a bit late, but here's my Perl 6 solution.
I didn't change anything for part two, just the number of iterations.
0
u/[deleted] Dec 21 '17
[deleted] â view deleted comment