n grouping

The year is slowing down, which means my focus can once again shift to pointless bullshit. Was giving Shakti K a spin once again and thought it might make sense to build up an arsenal of helper functions. Unlike just about every other language, there seems to be a cultural bias against abstraction in the APL family, where "idioms" of primitive operators are favored over named functions. I suspect this is largely to preserve the cool runic flavor of APL against Roman incursion, so it doesn't bother me so much in K.

This entry builds upon my permutation exercise. Rather than returning every valid subset of an array, I'd like a function that gives every possible coupling of items, every triplet of items, … , every n-grouping of items, so that

groupings[2;`dog`cat`magpie`galah`ibis]

yields

`galah`ibis  
`magpie`ibis 
`magpie`galah
`cat`ibis    
`cat`galah   
`cat`magpie  
`dog`ibis    
`dog`galah   
`dog`magpie  
`dog`cat     

and

groupings[3;`dog`cat`magpie`galah`ibis]

yields

`magpie`galah`ibis
`cat`galah`ibis   
`cat`magpie`ibis  
`cat`magpie`galah 
`dog`galah`ibis   
`dog`magpie`ibis  
`dog`magpie`galah 
`dog`cat`ibis     
`dog`cat`galah    
`dog`cat`magpie

and

groupings[5;`dog`cat`magpie`galah`ibis]

yields

,`dog`cat`magpie`galah`ibis

Implementation

I initially considered building this function off of a cartesian product, but no such primitive exists in Shakti at the moment, and that's a bit boring anyway. I then remembered the cool permutation generating functionality of a transposed iteration matrix. That is, for an array n items long, then

+!21 … 2n

will return a truth matrix of all the array's subsets.

xs:`dog`cat`magpie`galah`ibis

perms:{+!(#x)#2}

 perms xs

0 0 0 0 0
0 0 0 0 1
0 0 0 1 0
0 0 0 1 1
0 0 1 0 0
0 0 1 0 1
0 0 1 1 0
0 0 1 1 1
0 1 0 0 0
0 1 0 0 1
0 1 0 1 0
0 1 0 1 1
0 1 1 0 0
..

If we wished to isolate the n-groupings, it'd simply be a matter of locating the rows that sum to n. So for groups of 3, we'd do

 ps:perms xs

 (+/)'ps

0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4 1 2 2 3 2 3 3 4 2 3 3 4 3 4 4 5

 (3=)'(+/)'ps

0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 1 1 0 1 0 0 0

An array can have its indices referenced in multiple dimensions.

 xs[(0 0 1 1 1;0 1 0 1 1)]

`dog`dog`cat`cat`cat
`dog`cat`dog`cat`cat

Using & turns a list of indices into a truth mask instead.

 xs[(&0 0 1 1 1; &0 1 0 1 1)]

`magpie`galah`ibis
`cat`galah`ibis  

Two truth masks produce the final grouping. First, the permutations must have a truth mask for rows whose sum is n.

 ps[&(3=)'(+/)'ps]

0 0 1 1 1
0 1 0 1 1
0 1 1 0 1
0 1 1 1 0
1 0 0 1 1
1 0 1 0 1
1 0 1 1 0
1 1 0 0 1
1 1 0 1 0
1 1 1 0 0

Then each of these rows must serve as a mask against the primary array.

 (&)'ps[&(3=)'(+/)'ps]

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

 xs[(&)'ps[&(3=)'(+/)'ps]]

`magpie`galah`ibis
`cat`galah`ibis   
`cat`magpie`ibis  
`cat`magpie`galah 
`dog`galah`ibis   
`dog`magpie`ibis  
`dog`magpie`galah 
`dog`cat`ibis     
`dog`cat`galah    
`dog`cat`magpie

The function definition.

groupings:{ps:perms y;y[(&)'ps[&(x=)'(+/)'ps]]}

Application

Cribbed from an old Advent of Code problem. If there are only two items in an array that divide evenly, find their quotient. This is obviously best performed in a dynamic manner (boooooooring!) but for real world calculations, it's fine to brute force a problem like this.

 xs: 5 9 2 8

 grouping[2;5 9 2 8]

2 8
9 8
9 2
5 8
5 2
5 9

Order the pairs descending for proper division.

 (|^)'grouping[2;5 9 2 8]

8 2
9 8
9 2
8 5
5 2
9 5

Apply the division function to each pair.

 qs:{(%) . x}'(|^)'grouping[2;5 9 2 8];qs

4 1.125 4.5 1.6 2.5 1.8

Filter the quotients with no fractional part and use it as a truth map. Take the first (only) item from that array.

 first qs[&{0=x-_x}@qs]

4

Cool. Kool.