I’ve been a huge fan of the Advent of Code challenges since I first stumbled across them a few years ago. Last year, I completed all of the 2017 challenges using Elixir. This year, I decided to challenge myself and use a much more esoteric language that’s held my interest for the past year or so.
It’s my goal to complete all of this year’s Advent of Code challenges using the J programming language.
Before we get into this, I should make it clear that I’m no J expert. In fact, I wouldn’t even say that I’m a J beginner. I’ve used J a handful of times, and repeatedly struggled under the strangeness of the language. That being said, there’s something about it that keeps pulling me back. My hope is that after a month of daily exposure, I’ll surmount the learning curve and get something valuable out of the experience and the language itself.
The first Advent of Code challenge of 2018 asks us to read in a series of “changes” as input, and apply those changes, in order, to a starting value of zero. The solution to the challenge is the value we land on after applying all of our changes. Put simply, we need to parse and add a list of numbers.
The first thing I wanted to do was read my input from a file. It turns out that I do know one or two things about J, and one of the things I know is that you can use foreigns to read files. In particular, the 1!:1
foreign reads and returns the contents of a file.
Or does it?
1!:1 'input'
file name error
Apparently 1!:1
doesn’t read relative to the current script. I’m guessing it reads relative to the path of the jconsole
executable? Either way, using an absolute path fixes the issue.
input =. 1!:1 < '/Users/pcorey/advent_of_code_2018/day_01/input'
Now input
is a string with multiple lines. Each line represents one of our frequency changes. We could use ".
to convert each of those lines into a number, but because input
is a single string, and not an array of lines, can’t map ".
over input
:
0 "./ input
0
After scrambling for ways of splitting a string into an array of lines, I stumbled across cutopen
, which takes a string and puts each line into a box. That’s helpful.
boxed =. cutopen input
┌──┬──┬──┬──┐
│+1│-2│+3│+1│
└──┴──┴──┴──┘
Now if we open boxed
, we’ll have our array of lines:
lines =. > boxed
+1
-2
+3
+1
And now we can map ".
over that array to get our array of numbers.
numbers =. 0 "./ lines
1 _2 3 1
And the answer to our problem is the sum of numbers.
+/ numbers
3
Here’s my first working solution:
input =. 1!:1 < '/Users/pcorey/advent_of_code_2018/day_01/input'
boxed =. cutopen input
lines =. > boxed
numbers =. 0 "./ lines
+/ numbers
Part Two
My first instinct for solving this problem is to do it recursively. I might be able to define a dyadic verb that accepts my current list of frequencies and a list of changes. If the last frequency in my array exists earlier in the array, I’ll return that frequency. Otherwise, I’ll append the last frequency plus the first change to my frequencies array, rotate my changes array, and recurse.
After many struggles, I finally landed on this solution:
input =. 1!:1 < '/Users/pcorey/advent_of_code_2018/day_01/sample'
boxed =. cutopen input
lines =. > boxed
numbers =. 0 "./ lines
change_frequency =. 4 : 0
frequency =. {: x
change =. {. y
frequency_repeated =. frequency e. (}: x)
next_x =. x , (frequency + change)
nexy_y =. 1 |. y
next =. change_frequency ` ({:@}:@[) @. frequency_repeated
next_x next next_y
)
0 change_frequency numbers
This works great for example inputs, but blows the top off my stack for larger inputs. It looks like J’s max stack size is relatively small. Recursion might not be the best approach for these problems.
Looking into other techniques for working without loops, I learned that you can use the ^:_
verb to “converge” on a result. It will repeatedly apply the modified verb until the same result is returned.
I refactored my verb to take and return my frequencies array and my changes array as a boxed tuple, and converge on that verb until I get a repeated result. That repeated result holds my repeated frequency:
input =. 1!:1 < '/Users/pcorey/advent_of_code_2018/day_01/sample'
boxed =. cutopen input
lines =. > boxed
numbers =. 0 "./ lines
package_next =. 4 : 0
(x,({:x)+({.y));(1|.y)
)
package_result =. 4 : 0
x;y
)
change =. 3 : 0
frequencies =. >@{. y
changes =. >@{: y
frequency =. {: frequencies
change =. {. changes
repeated =. frequency e. (}: frequencies)
next =. package_next ` package_result @. repeated
frequencies next changes
)
result =. change^:_ (0;numbers)
echo 'Repeated frequency:'
{:@:{.@:> result
Notes
$:
doesn’t seem to refer to the outermost named verb. Recursion wasn’t working as I expected with$:
. Replacing it with the named verb worked perfectly.- J seems to have a short stack. Note to self: avoid deep recursion.
- J doesn’t support tail call optimization.
^:_
and variants can be used as an iterative alternative to recursion.- Use boxes like tuples.
- Use
echo
for debug printing.