Search code examples
j

A non-awkward way to "chain" in J?


This comes up fairly often, and I always find myself fighting against it. So I'd like the definitive solution, if there is one.

The essential problem boils down to this conflict:

  1. J likes to work with homogeneous lists / tables etc. (ie, no lists with items of different lengths, eg)
  2. Sometimes you want to apply a verb to each item of list, where the result of the verb is itself a list whose number of items varies.

This is typically solved using chain (or flat_map) in functional languages.

example to demonstrate the general problem

To take a concrete example, say you want to list all possible pairs from the list 0 1 2 3, where the first is strictly less than the second:

0 1
0 2
0 3
1 2
1 3
2 3

Of course, you could table ,/ or catalog { to get the full cross product, and then filter so you're left with just the upper triangle:

That is take the result of ,"0/~ i.4:

0 0
0 1
0 2
0 3

1 0
1 1
1 2
1 3

2 0
2 1
2 2
2 3

3 0
3 1
3 2
3 3

Actually, to make the geometry more clear, let's display it as ;/"2 ,"0/~ i.4:

┌───┬───┬───┬───┐
│0 0│0 1│0 2│0 3│
├───┼───┼───┼───┤
│1 0│1 1│1 2│1 3│
├───┼───┼───┼───┤
│2 0│2 1│2 2│2 3│
├───┼───┼───┼───┤
│3 0│3 1│3 2│3 3│
└───┴───┴───┴───┘

And now the result we seek is the upper half the triangle. But this approach has drawbacks:

  1. We have to do double the work we need.
  2. We have to introduce a separate filter step to remove the extra results we don't work.
  3. The above two things obscure the intent of our code.

Solutions using { have similar issues.

it would be nice if...

The chain approach would look something like this:

g=. ,"0 i.
(g 0);(g 1);(g 2);(g"0 i.3);<(<@g"0 (1+i.3))

which produces:

┌──┬───┬───┬───┬─────────────┐
│  │1 0│2 0│0 0│┌───┬───┬───┐│
│  │   │2 1│0 0││1 0│2 0│3 0││
│  │   │   │   ││   │2 1│3 1││
│  │   │   │1 0││   │   │3 2││
│  │   │   │0 0│└───┴───┴───┘│
│  │   │   │   │             │
│  │   │   │2 0│             │
│  │   │   │2 1│             │
└──┴───┴───┴───┴─────────────┘

The last two columns are close to what I want, but in the penultimate column we have the nuisance of automatic fill obscuring our result, and in the final column our correct results are boxed, but unboxing them returns the fill.

What is a good (and idiomatically J) way to solve issues like this?

NOTE: I'm not looking for an ad hoc solution to the problem in the example, but a solution to the general problem that is solved by chain in other languages.


Solution

  • I think that this answer is too specific for what you want, but it does suggest that the solution to part 2 of the challenge (variable length results) is to use each=:&.> so that the padding can be avoided.

       (< 0 1 2 3) ;"1@:((],.>#[) each) 0 1 2 3
    0 1
    0 2
    0 3
    1 2
    1 3
    2 3
       (< 0 1 2 3) ((],.>#[) each) 0 1 2 3
    +---+---+---+--+
    |0 1|1 2|2 3|  |
    |0 2|1 3|   |  |
    |0 3|   |   |  |
    +---+---+---+--+