NYCJUG/2023-09-12
Beginner's regatta
Bob Therriault tells us about what has changed in the revised J Primer. He is interested in feedback on this. We will also look at the very useful function dyadic I. and what it does.
J Primer
I suppose that the first question that should be answered is 'Why update the J Primer?'
Aside from updating the content - only oldtimers remember the J Form Editor - most of the changes were to improve readability and navigation.
Particularly of note is simplification of navigation for both the top
and the bottom sections.
As well as clearer highlighting of example code and primitives.
It's important to have a number of easily accessible introduction for beginners. In the J Primer, a newcomer can start from scratch in J and progress all the way to implementing a basic GUI application.
Now that the J Primer has been updated to work with current versions of J in the JQt environment, we hope that newcomers can be directed towards the J Primer as a learning tool and have reduced frustration due to out of date references.
For your consideration, we present the Updated J Primer.
Dyadic I.
Only recently did I take a look at dyadic I. "interval index" and I was able to solve a moderately complicated little problem with ease using it. Some of the context of this problem is given below in Show and Tell.
Getting File Data in a Useful Form
I have a large number of files containing directory listings of backup discs I've made over the years. They look something like what is shown here in an elided form:
Volume in drive D is Ph060414_0122 Volume Serial Number is 8989-8C54
Directory of D:\ 04/13/2006 08:48 PM <DIR> amisc 04/13/2006 04:16 PM <DIR> Data 04/05/2006 02:18 PM 37,088 .emacs 1 File(s) 37,088 bytes ... Directory of D:\amisc\J 04/13/2006 08:48 PM <DIR> .. ... 04/29/1999 02:24 AM 154 util.txt 32 File(s) 487,914 bytes ... Directory of D:\amisc\pix\Photos\2006Q2 04/14/2006 01:16 AM <DIR> 20060413 04/13/2006 12:54 PM <DIR> 20060412-13 0 File(s) 0 bytes Directory of D:\amisc\pix\Photos\2006Q2\20060412-13 04/13/2006 08:48 PM <DIR> .. ... 04/12/2006 01:41 PM 3,036,574 DSCF7064.JPG 202 File(s) 534,786,174 bytes Directory of D:\amisc\pix\Photos\2006Q2\20060413 04/14/2006 01:16 AM <DIR> .. 04/14/2006 01:16 AM 4,740,394 pspbrwse.jbf ... 04/13/2006 01:07 PM 3,252,373 DSCF7379.JPG 413 File(s) 1,137,214,458 bytes ... Directory of D:\temp\WD\JDrive 04/13/2006 06:16 PM <DIR> .. ... 03/16/2006 01:49 PM 328,900 FLSZS.DAT 7 File(s) 2,636,746 bytes
This directory listing is the result of the DOS command "dir /o-d /s ..." which orders entries within a directory by ascending date and recursively lists all sub-directories as well.
Notice how each sub-directory section is preceded by the text Directory of ... and ends with text like 413 File(s) .... Using these text strings, I want to extract what's between each pair of starting/ending text indicators. This is complicated by the fact that I only care about certain sub-directories, only those under "\amisc\pix\Photos".
To match a string of text on each line, I used e.'s (member) bigger sister E. (find matches). Whereas e. works on atoms, E. understands vectors. For example,
'hi' E. 'hihohi' 1 0 0 0 1 0 'hi' e. 'hihohi' 1 1
We see that E. lets us search for occurrences of vectors unlike e. which works on scalars.
To extract the data we want, we first read in the file and break it into a vector of lines of text based on the assumption that line-feed characters terminate each line.
#fl=. fread 'Ph060414_0122.dir' 1331502 $fl=. <;._2 ] LF (],[#~[~:[:{:]) CR-.~fl 27605 3{.fl +-----------------------------------+----------------------------------++ | Volume in drive D is Ph060414_0122| Volume Serial Number is 8989-8C54|| +-----------------------------------+----------------------------------++
So we have about 1.3 million bytes and over 27 thousand lines in this case and we can see that the first three lines are the same as the first three lines of the sample file as shown above.
Extracting Information Based on Start/End Strings
In order to extract information about selected sub-directories, we need to find the start and end line number of the sections in which we are interested.
We locate the start of a directory section by finding occurrences of the string "\amisc\pix\Photos\" since these are the only ones we care about.
#whst=. I. +./&> (<'\amisc\pix\Photos\') E.&.>fl NB. Start of sub-directories of interest 3 whst 21498 21506 21714
Now that we have our three starting line numbers, where does each end?
We find the end of all sub-directories by searching for the string " Files(s) ".
#whend=. I. +./&> (<' File(s) ') E.&.>fl NB. End of each sub-directory listing 1490
We have many more ending than starting positions. How do we find the endings which correspond only to the starting lines of interest? Why, by using monadic I. or "interval index".
whend I. whst 971 972 973
This indicates that the starting points are in the intervals indexed by the result. That is,
whend{~whend I. whst NB. Which end-points correspond to these starting points? 21504 21712 22131 whst,.whend{~whend I. whst NB. Show each start/end pair 21498 21504 21506 21712 21714 22131
Having these points makes it easy to extract only the sections of interest from the large file or simply to count the number of entries in each of the sections of interest by subtracting the starting point from the ending point (and subtracting 4 to account for the two header and one trailer lines as well as the entries for "." and "..").
4-~whst-~whend{~whend I. whst 2 202 413
We can verify the first one of these simply by inspection of the source file:
Directory of D:\amisc\pix\Photos\2006Q2 04/14/2006 01:16 AM <DIR> 20060413 04/13/2006 08:48 PM <DIR> . 04/13/2006 08:48 PM <DIR> .. 04/13/2006 12:54 PM <DIR> 20060412-13 0 File(s) 0 bytes
We see that there are indeed only two entries for this sub-directory, discounting the ubiquitous directory entries for "." and "..".
Show-and-tell
Ed Gottsman will demonstrate his information perusal tool J Viewer which is a J GUI application for delving into the various facets of the J wiki, forums and J code on GitHub.
Searching the Wiki, Forums and GitHub
The J Viewer, which supplies fast, fluid access to the J Wiki and the J Forums, now supports full-text code/English search of the Wiki, the Forums, and most of the jsoftware section of GitHub. The image below shows a full-text search for /:/: with the results divided into categories. We're actively looking for beta testers to help us resolve any remaining bugs--please send an email to mailto:edward.j.gottsman@gmail.com if you're interested in participating. (Thanks.)
If you do happen to try out the beta, there is a video demo that may give you some more areas to explore. https://www.youtube.com/watch?v=emVBSN6EZFI
Reconciling Large Sets of Files
Lately I've been disposing of the two thousand or so DVDs, CDs, and BluRays I've been using as a primary long-term backup medium. Unfortunately I can't simply refill the recycling bin downstairs and be done with it, oh no, I've got to look over each disc before I get rid of it to ensure I'm not losing my only copy of something important.
This effort requires comparing what's on each disc with what I have on my main RAID drive to figure out what I'm missing. Fortunately, when I created most of these discs, I also copied all the directory information for the whole disk to a uniquely-named file. This allows me to figure out what I may need to copy from a disc without actually having to read it unless it has files that I need.
This effort led to my discovery of the usefulness of dyadic I. as shown in the Beginner's Regatta above, as well as the "toggle" expression examined below in Advanced Topics.
Much of what I did is specific to this project and not of general interest, so I will show just the result of one of the tools I developed. The display below searches a directory file - as seen in the Dyadic I. section above - for files under two specific directories - "\amisc\pix\Photos" and "\amisc\pix\Sel" - and counts the number of .JPG files both there and on the RAID drive.
Here is an example of running this for files offloaded on 1/20/2010. The df variable is set to the stem of the name of the directory listing file which is assumed to be in the current directory.
TYPE ZvsCD dirFlNm=. 'D:\amisc\ofldinfo\',df,'.dir'[smoutput TYPE=: ;TYPES{~-.TYPES i. <TYPE [ df=. 'Ofld100120_2230' Sel +--+-----------------------------------+--+ |0 |D:\amisc\pix\Sel\2006Q1 |3 | +--+-----------------------------------+--+ |49|D:\amisc\pix\Sel\2006Q1\20060120-22|49| +--+-----------------------------------+--+ |25|D:\amisc\pix\Sel\2006Q1\20060322 |25| +--+-----------------------------------+--+ |57|D:\amisc\pix\Sel\2006Q1\20060325-28|57| +--+-----------------------------------+--+ TYPE ZvsCD dirFlNm=. 'D:\amisc\ofldinfo\',df,'.dir'[smoutput TYPE=: ;TYPES{~-.TYPES i. <TYPE [ df=. 'Ofld100120_2230' Photos +---+----------------------------------------+---+ |0 |D:\amisc\pix\Photos\2009Q3 |9 | +---+----------------------------------------+---+ |37 |D:\amisc\pix\Photos\2009Q3\20090831-1010|39 | +---+----------------------------------------+---+ |111|D:\amisc\pix\Photos\2009Q3\20090825 |112| +---+----------------------------------------+---+ |372|D:\amisc\pix\Photos\2009Q3\20090829-30 |373| +---+----------------------------------------+---+ |173|D:\amisc\pix\Photos\2009Q3\20090902-03 |174| +---+----------------------------------------+---+ |98 |D:\amisc\pix\Photos\2009Q3\20090903 |99 | +---+----------------------------------------+---+ |132|D:\amisc\pix\Photos\2009Q3\20090904 |133| +---+----------------------------------------+---+ |134|D:\amisc\pix\Photos\2009Q3\20090910 |135| +---+----------------------------------------+---+ |252|D:\amisc\pix\Photos\2009Q3\20090907-08 |84 | +---+----------------------------------------+---+ |351|D:\amisc\pix\Photos\2009Q3\20090909 |124| +---+----------------------------------------+---+
In the first invocation we look at files under "\amisc\pix\Sel" and see that the numbers match so there is no reason to read the actual disc to retrieve these files. The second invocation toggles the directory of interest to "\amisc\pix\Photos" and shows similar results though the numbers differ slightly. Small differences like this exist for a number of reasons so there is again no point in reading the disc.
This helped speed up the selection process considerably since it avoids many fruitless readings of the discs.
Advanced topics
Here we revisit a topic we've touched on in other meetings: solving a problem by exhaustive versus generative methods.
Finding a Special Number
Recently there was an exchange on the forums to solve this problem from the Quora site, relayed by Skip Cave: Can you arrange the digits 1-9 to make a nine-digit number such that the first digit is divisible by one, the first two digits are divisible by two, the first three divisible by three and so on?
Initial (Exhaustive) Solution
Skip found the answer like this:
perm=: i.@! A. i. NB. From https://code.jsoftware.com/wiki/Essays/Permutations to=: [+[:i.[:>:]-[ NB. My guess based on context NB. ea=.&.> NB. Don't bother
Skip uses a cover for "each" (&.>) but I prefer the raw J. He also uses perm which I found defined on the J wiki, and on, for which I deduced a likely definition based on the context in which it is used below. I also eliminated a spurious assignment, giving us this:
{:"1 at#~*./"1[0=a|"1 at=.>10#. &.> (a=.362880 9$1 to 9){. &.> { >:perm 9 381654729
Checking that this answer is correct, we break it into its numeric digits, take the cumulative product of those digits and check that each of these products is evenly divisible by one through nine:
;".&.>":381654729 3 8 1 6 5 4 7 2 9 */\;".&.>":381654729 3 24 24 144 720 2880 20160 40320 362880 (>:i.9)|*/\;".&.>":381654729 NB. Should have all zeros 0 0 0 0 0 0 0 0 0
Taking a look at how this works, we see that this is an exhaustive approach: generate all possible nine-digit numbers with no zeros, with >:perm 9 and filter out the ones not meeting the criteria. We can guess that this is exhaustive by noticing that the number 362880 is !9 which is the total number of combinations of nine things.
This approach can be expensive as seen here; running the expression ten times gives an average of 2.8 seconds on my machine.
(10) 6!:2 '{:"1 at#~*./"1[0=a|"1 at=.>10#. &.> (a=.362880 9$1 to 9){. &.> { >:perm 9' 2.78245
This expense is negligible considering this is needs only to be run once but, as we will see, there are much more efficient ways to do this.
Other (Generative) Solutions
Progressively Building a Solution
A reply from Raul Miller shows a different kind of solution. Rather than generating all possibilities and filtering them down, this code builds up a solution a digit at a time by starting with all possible digits for the last digit of the result because any number based on a permutation of the digits one through nine is divisible by nine (otherwise the problem is unsolvable because of "casting out nines"[1]..
Raul Miller via forums.jsoftware.com Sun, Sep 10, 6:09 PM (2 days ago) to programming I guess to simplify it, I would filter the possibilities at each step. first digit divisible by 1: N1=: 1+i.9 Second digit divisible by 2: N2=: ;(10*N1)+each (<2*1+i.4)-.each N Third digit divisible by 3: digits=: 10&#.inv N3=: (#~ 0=3|]);(10*N2)+each (<1+i.9) (-. digits) each N2 And, so on... and since we're basically doing the same thing at each step, we could encapsulate and parameterize this step as a function. F=: {{(#~ 0=x|]);(10*y)+each (<N1) (-. digits) each y}} 9 F 8 F 7 F 6 F 5 F 4 F 3 F 2 F N1 381654729 timespacex '9 F 8 F 7 F 6 F 5 F 4 F 3 F 2 F N1' 0.0006679 38400
Fixing a small typo in the definition of F and timing this on the same machine as for the above timing, we get this:
digits=: 10&#.^:(_1) F=: {{(#~ 0=x|]);(10*y)+each (<>:i.9) (-. digits) each y}} $2 F >:i.9 32 9 F 8 F 7 F 6 F 5 F 4 F 3 F 2 F >:i.9 381654729 (10) 6!:2 '9 F 8 F 7 F 6 F 5 F 4 F 3 F 2 F >:i.9' 0.00042433
So this generative solution is about 6,000 times as fast as the exhaustive one.
Thoughtful Generation
Ben Gorte asked
If we do a bit of work by ourselves? Consider that the 5 has to be in fifth position (starting from 1), and that there must be even digits (four of them) on the even positions. The other four odd digits go to the remaining odd positions. Then there are only 576 possibilities left.
He proceeds to build the solution like this:
odds =: (i.24) A. '1379' NB. there's a better trick, is there? evens =: (i.24) A. '2468' all =: ,/ odds {{ }. x 1 3 7 9 } y 2 4 6 8 } '0oeoe5eoeo' }}"1/ evens all #~ (1+i.9) {{*./ 0 = x | ". x {."0 1 y }}"1 all 381654729
Like Raul, Ben made use of direct definition by including a phrase in double curly braces.
Combining the final two lines of this definition, we see that it performs very well.
(10) 6!:2 'all #~ (1+i.9) {{*./ 0 = x | ". x {."0 1 y }}"1 ,/ odds {{ }. x 1 3 7 9 } y 2 4 6 8 } ''0oeoe5eoeo'' }}"1/ evens' 0.0022552
Using Power
Michael Day also submitted a direct definition solution which uses the power conjunction (^:).
app=:{{ if. 10=nd =. >: #":{.y do. y return. elseif. 2 = nd do. y =.1 3 5 7 9 end. par=. 2|<:{:y NB. plagiarising Ben Gorte's idea re odds & evens! q =. q#~(=<.) nd%~ q=. ,(par+2*i.5) +/ 10*y q#~(-:~.)@":"0 q }}
This gives the answer very quickly.
app^:_] 0 381654729 (10) 6!:2 'app^:_] 0' 0.00015497
Speed Champ
A later entry from Pascal Jasmin seemed to hold the speed record.
I =: ]F.: NB. insert using fold for shape flexibility (10) 6!:2 '(>:i.9) ([ (] #~ [ = 10&#.inv(#@~."1@:)@]) [ (] #~ 0 = |) (>: i.9) +("1 0)(,@:) 10 * ])I 9 8 7 6 5 4 3 2' 0.0002164
This is about twice as fast as the next fastest.
However, Pascal then topped himself with this improvement of about 10%:
BASE10DIVbyINDEX =: '';(>:i.9); 2 4 6 8;(>:i.9); 2 4 6 8;5;2 4 6 8;(>:i.9);2 4 6 8;(>:i.9) v =: ([ (] #~ [ = 10&#.inv(#@~."1@:)@]) [ (] #~ 0 = |) (BASE10DIVbyINDEX {::~ [) +("1 0)(,@:) 10 * ])I (10) 6!:2 '(>:i.9) v 9 8 7 6 5 4 3 2' 0.00019186
A Stochastic Method
Jose Quintana submitted a semi-exhaustive solution that randomly samples the search space until it finds an answer.
o=. @: entry=. 0&{:: try=. 1&{:: sample=. (>: o ?~ o 9:) ; 1 + try NB. counting tries to avoid a premature convergence good=. (-: 0&*) o ((1+i.9) | 10&#:^:_1\) tries=. ', random tries ' , ": o try answer=. 'Answer ', ": o (10&#:^:_1) o entry while=. ^: (^:_) NB. parentheses are not necessary for the latest generation solve=. (answer , tries) o (sample while (-. o good o entry)) o (0 ;~ i.o 9:) f. solve'' Answer 381654729, random tries 295587 (10) 6!:2 'solve''''' 1.21938
We see that this performs better, on average, than the fully exhaustive solution by a factor of more than two.
General Conclusions
Exhaustive solutions are often attractive because they are apparently correct because if we start with all possibilities, the one we care about has to be in there as well. Also, in a loose language like J, it's trivially easy to whip up a monster solution with very little effort. There's an old saying that a day of programming can replace an hour of thought.
An exhaustive solution is good as far as it goes but its Achilles heel is poor scalability. What if the proposed number is in hexadecimal? Unlike the !9 (362880) possibilities for this problem, we would have !15 (1.3e12) for the hexadecimal extension of it.
Though a generative solution will nearly always outperform an exhaustive one, it requires more thought up front. So, especially for limited utility code like this one for finding a single number once, it seems the short time to code an exhaustive solution may overwhelm the time it takes to think carefully about the problem. In general, we have to trade off up-front design time against time lost from inefficient computation. For a problem like this, it would seem all that beautiful efficient code was wasted time since in only took 2 seconds to run the exhaustive version.
However, if we had been asked to find a 15-digit hexadecimal number with the same properties as the 9-digit one, the trade-off would shift strongly to favor up-front design.
How much time might the exhaustive solution take in this case, assuming we could straightforwardly extrapolate our existing timings?
Learning and Teaching J
This is not so much about teaching J as warning that some simple code can have unexpected behavior in part due to J's promiscuity: the basic tokens of J are easy to mix together to get code that runs. This is often a positive but there are times where J's willingness to do something with such a wide variety of inputs can lead to unexpected behavior if apply edge cases.
A Toggle Expression
I had a situation where I needed a global variable to toggle between two different text strings each time I re-executed a particular command. By toggling the global variable TYPE between the strings photos and sel made it easy to run the two cases in which I was interested.
The initial assignment sets two globals: TYPES which is a vector of the strings between which we are switching and TYPE which is one or the other of the strings.
TYPE=: ;{. TYPES=: 'Photos';'Sel' NB. Init to 'Photos' TYPE=: ;TYPES{~-.TYPES i. <TYPE NB. Switch type each time
So, it's easy to run for the two types by repeating the same code, like doSomething TYPE=: ;TYPES{~-.TYPES i. <TYPE, twice in a row. Most of the time, both results are negative which tells us we can get rid of this disc and move on to the next. However, sometimes one or the other type will have to be dealt with case-by-case, depending on the output of our doSomething function.
Testing an Expression
We see that the toggle above works by indexng the current value of TYPE in the vector of allowed strings, then re-assigning TYPE by indexing with the logical not of the result of the look up. This works because there are only two items so the allowable indexes are zero and one which are Boolean values to which we can apply logical not -.. However, the monadic domain of this verb extends to integers, so you start to get very odd results if you add items to TYPES without changing the toggle code.
First, let's see how the original toggle code works in action by re-running the same line a few times and displaying the result.
]TYPE=: ;{. TYPES=: 'Photos';'Sel' NB. Initialize TYPE to 1st item Photos ]TYPE=: ;TYPES{~-.TYPES i. <TYPE NB. Switch type each time Sel ]TYPE=: ;TYPES{~-.TYPES i. <TYPE NB. Switch type each time Photos
So we have the toggle we want: every time we assign it this way, the value flips between the two strings. What if we add a new item to TYPES but do not change the toggle code to accommodate the change?
]TYPE=: ;{. TYPES=: 'Photos';'Sel';'Foo' NB. Initialize to 1st item Photos ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Sel ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Photos
It seems to make no difference if we initialize TYPE with either of our original two values. What if we start with the new item?
]TYPE=: ;{: TYPES=: 'Photos';'Sel';'Foo' NB. Initialize to last item Foo ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Foo
Now our toggle does not work because we are stuck on the one value. Why is this? The fault is in J's extension of logical not to be 1-n for any integer n. This conveniently switches between one and zero for Boolean arguments but does something else entirely for integer arguments greater than one.
TYPES i. <'Foo' 2 -.TYPES i. <'Foo' _1 _1{TYPES +---+ |Foo| +---+
So, applying -. to the index makes it a negative number which promiscuous J is happy to use to select from an array selecting, in this case, the _1{ or last item in TYPES.
What happens if we extend TYPES further?
]TYPE=: ;{: TYPES=: 'Photos';'Sel';'Foo';'Bar' Bar ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Foo ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Bar
It cycles between the latter two values if we start with one of them. What if we start with one of our original values?
]TYPE=: ;{. TYPES=: 'Photos';'Sel';'Foo';'Bar' Photos ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Sel ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Photos
The good news is that the original behavior is the same. The bad news is that it's not at all obvious what the toggle rule is as you extend TYPES.
Odd Behavior Explained
Eventually, by experimenting with ever longer versions of TYPES, we found a way to describe the behavior of toggle when applied to vectors longer than two items.
Looking at these experiments, we see that the toggle only ever toggles between two values or remains the same. The reason for this difference has to do with whether TYPES has an even or odd number of elements. For an odd number, some value will only toggle to itself but other initializations toggle between two values.
]TYPE=: ;{: TYPES=: 'Photos';'Sel';'Foo';'Bar';'Fiz';'Buz';'Golly';'Jee' Jee ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Foo ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Jee
So, ignoring the first two items, we see that we are toggling between the first and last values of the latter six Foo and Jee.
Initializing to the 3{ value of Bar, we see that the values toggle between this and the second-to-last item Golly.
]TYPE=: ;3{ TYPES=: 'Photos';'Sel';'Foo';'Bar';'Fiz';'Buz';'Golly';'Jee' Bar ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Golly ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Bar
Finally, the last pair of toggle values is the 4{ and the 5{ of TYPES.
]TYPE=: ;4{ TYPES=: 'Photos';'Sel';'Foo';'Bar';'Fiz';'Buz';'Golly';'Jee' Fiz ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Buz ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Fiz
So, we can see here how toggling behaves depending on the initial value selected.
This can be extended obviously as the vector lengthens: each new pair toggles between opposite values other either end (of all but the first pair in the vector) and adding one new value to the domain also does this except there's now a middle element which toggles to itself.
Domain Error?
What if we initialize TYPE to an illegal value? The toggle behaves well in that it reverts to its default behavior between our two first choices.
TYPES=: 'Photos';'Sel';'Foo';'Bar';'Fiz';'Buz';'Golly';'Jee' TYPE=: ' not a known string' ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Sel ]TYPE=: ;TYPES{~-.TYPES i. <TYPE Photos
Conclusion
The obvious message here is that simple, quick and dirty code can lead to unexpected results if its arguments are unexpected. The more subtle warning is to pay attention to J's generous extensions to different data types.
End Matter
Ewart Shaw recently sent a note to the J forum to mention that he has recently used J to design book covers for a series of math books published by the UK Maths Trust.
Here's his most recent one which displays a recently discovered aperiodic tiling by turtles as discovered by amateur mathematician David Smith in 2023.
Mr. Shaw's signature reminds us of the late Eugene McDonnell's one line Mandelbrot in J in his signature. I was curious about his signature code so I ran it to see what it does:
3 ((4&({*.(=+/))++/=3:)@([:,/0&,^:(i.3)@|:"2^:2))&.>@]^:(i.@[) <#:3 6 2 +-----+---------+-------------+ |0 1 1|0 0 0 0 0|0 0 0 0 0 0 0| |1 1 0|0 1 1 1 0|0 0 0 1 0 0 0| |0 1 0|0 1 0 0 0|0 0 1 1 0 0 0| | |0 1 1 0 0|0 1 0 0 1 0 0| | |0 0 0 0 0|0 0 1 1 0 0 0| | | |0 0 0 0 0 0 0| | | |0 0 0 0 0 0 0| +-----+---------+-------------+
We see that this implements a cellular automata as made popular by John Horton Conway's well-known game of Life. In this example, it produces three iterations of the configuration starting with the R pentomino[2], known as the most active configuration of fewer than six live cells.
At the meeting, someone commented that this code expands the size of the grid as the automata grows unlike many implementations which maintain a fixed grid that wraps top to bottom and side to side, effectively acting as a torus rather than as an infinite plane. One could speculate that this might trace back to the difficulty of dealing with a changing grid size when using a compiled language, which may have led to this change from the original specification.