264 lines
6.8 KiB
Text
264 lines
6.8 KiB
Text
|
title:: J concepts in SC
|
||
|
summary:: An overview of concepts borrowed from J
|
||
|
categories:: Language
|
||
|
|
||
|
The J programming language is a successor of APL (http://www.jsoftware.com). These languages are made for processing arrays of data and are able to express
|
||
|
complex notions of iteration implicitly.
|
||
|
|
||
|
The following are some concepts borrowed from or inspired by J.
|
||
|
Thinking about multidimensional arrays can be both mind bending and mind expanding.
|
||
|
It may take some effort to grasp what is happening in these examples.
|
||
|
|
||
|
section:: Filling arrays
|
||
|
iota fills an array with a counter
|
||
|
code::
|
||
|
z = Array.iota(2, 3, 3);
|
||
|
z.rank; // 3 dimensions
|
||
|
z.shape; // gives the sizes of the dimensions
|
||
|
z = z.reshape(3, 2, 3); // reshape changes the dimensions of an array
|
||
|
z.rank; // 3 dimensions
|
||
|
z.shape;
|
||
|
::
|
||
|
|
||
|
fill a multidimensional array
|
||
|
code::
|
||
|
// 2 dimensions
|
||
|
Array.fill([3,3], { 1.0.rand.round(0.01) });
|
||
|
Array.fill([2,3], {|i,j| i@j });
|
||
|
|
||
|
// 3 dimensions
|
||
|
Array.fill([2, 2, 2], { 1.0.rand.round(0.01) });
|
||
|
Array.fill([2, 3, 4], {|i,j,k| [i, j, k].join });
|
||
|
|
||
|
// a shorter variant of the above:
|
||
|
{|i,j,k| [i, j, k].join } ! [2, 3, 4];
|
||
|
|
||
|
// more dimensions
|
||
|
Array.fill([2, 2, 4, 2], {|...args| args.join });
|
||
|
::
|
||
|
|
||
|
|
||
|
section:: Creating arrays
|
||
|
using dup to create arrays
|
||
|
code::
|
||
|
(1..4) dup: 3;
|
||
|
100.rand dup: 10;
|
||
|
{100.rand} dup: 10;
|
||
|
{100.rand} dup: 3 dup: 4;
|
||
|
{{100.rand} dup: 3} dup: 4;
|
||
|
{|i| i.squared} dup: 10;
|
||
|
{|i| i.nthPrime} dup: 10;
|
||
|
{ |i, j, k| i * j } dup: [5, 5]; // multidimensional dup
|
||
|
::
|
||
|
! is an abbreviation of dup
|
||
|
code::
|
||
|
(1..4) ! 3;
|
||
|
100.rand ! 10;
|
||
|
{100.rand} ! 10;
|
||
|
{100.rand} ! 3 ! 4;
|
||
|
{{100.rand} ! 3} ! 4;
|
||
|
{|i| i.squared} ! 10;
|
||
|
{|i| i.nthPrime} ! 10;
|
||
|
{ |i, j| i * j } ! [5, 5];
|
||
|
::
|
||
|
|
||
|
other ways to do the same thing:
|
||
|
code::
|
||
|
// partial application
|
||
|
_.squared ! 10;
|
||
|
_.nthPrime ! 10;
|
||
|
|
||
|
// operating on a list
|
||
|
(0..9).squared;
|
||
|
(0..9).nthPrime;
|
||
|
::
|
||
|
|
||
|
section:: Operator adverbs
|
||
|
Adverbs are a third argument passed to binary operators that modifies how they iterate over
|
||
|
SequenceableCollections or Streams. See the link::Reference/Adverbs:: help file for more information.
|
||
|
code::
|
||
|
[10, 20, 30, 40, 50] + [1, 2, 3]; // normal
|
||
|
[10, 20, 30, 40, 50] +.f [1, 2, 3]; // folded
|
||
|
[10, 20, 30, 40, 50] +.s [1, 2, 3]; // shorter
|
||
|
[10, 20, 30, 40, 50] +.x [1, 2, 3]; // cross
|
||
|
[10, 20, 30, 40, 50] +.t [1, 2, 3]; // table
|
||
|
::
|
||
|
|
||
|
section:: Operator depth
|
||
|
J has a concept called verb rank, which is probably too complex to understand and implement in SC, but operator depth is similar and simpler.
|
||
|
A binary operator can be given a depth at which to operate. Negative depths iterate the opposite operand.
|
||
|
These are better understood by example. It is not currently possible to combine adverb and depth.
|
||
|
code::
|
||
|
z = Array.iota(3,3);
|
||
|
y = [100, 200, 300];
|
||
|
z + y;
|
||
|
z +.0 y; // same as the above. y added to each row of z
|
||
|
z +.1 y; // y added to each column of z
|
||
|
z +.2 y; // y added to each element of z
|
||
|
z +.-1 y; // z added to each element of y
|
||
|
::
|
||
|
|
||
|
subsection:: deepCollect
|
||
|
deepCollect operates a function at different dimensions or depths in an array.
|
||
|
code::
|
||
|
z = Array.iota(3, 2, 3);
|
||
|
f = {|item| item.reverse };
|
||
|
z.deepCollect(0, f);
|
||
|
z.deepCollect(1, f);
|
||
|
z.deepCollect(2, f);
|
||
|
|
||
|
f = {|item| item.stutter };
|
||
|
z.deepCollect(0, f);
|
||
|
z.deepCollect(1, f);
|
||
|
z.deepCollect(2, f);
|
||
|
::
|
||
|
|
||
|
section:: Sections of multidimensional arrays
|
||
|
slice can get sections of multidimensional arrays.
|
||
|
nil gets all the indices of a dimension.
|
||
|
code::
|
||
|
z = Array.iota(4, 5);
|
||
|
z.slice(nil, (1..3));
|
||
|
z.slice(2, (1..3));
|
||
|
z.slice((2..3), (0..2));
|
||
|
z.slice((1..3), 3);
|
||
|
z.slice(2, 3);
|
||
|
|
||
|
z = Array.iota(3, 3, 3);
|
||
|
z.slice([0,1], [1,2], [0,2]);
|
||
|
z.slice(nil, nil, [0,2]);
|
||
|
z.slice(1);
|
||
|
z.slice(nil, 1);
|
||
|
z.slice(nil, nil, 1);
|
||
|
z.slice(nil, 2, 1);
|
||
|
z.slice(nil, 1, (1..2));
|
||
|
z.slice(1, [0,1]);
|
||
|
z.flop;
|
||
|
::
|
||
|
|
||
|
section:: Sorting order
|
||
|
generate a random array:
|
||
|
code::
|
||
|
z = {100.rand}.dup(10);
|
||
|
::
|
||
|
order returns an array of indices representing what would be the sorted order of the array:
|
||
|
code::
|
||
|
o = z.order;
|
||
|
y = z[o]; // using the order as an index returns the sorted array
|
||
|
::
|
||
|
calling order on the order returns an array of indices that returns the sorted array to the
|
||
|
original scrambled order:
|
||
|
code::
|
||
|
p = o.order;
|
||
|
x = y[p];
|
||
|
::
|
||
|
|
||
|
section:: Bubbling
|
||
|
bubbling wraps an item in an array of one element. it takes the depth and levels as arguments.
|
||
|
code::
|
||
|
z = Array.iota(4,4);
|
||
|
z.bubble;
|
||
|
z.bubble(1);
|
||
|
z.bubble(2);
|
||
|
z.bubble(0,2);
|
||
|
z.bubble(1,2);
|
||
|
z.bubble(2,2);
|
||
|
::
|
||
|
similarly, unbubble unwraps an Array if it contains a single element.
|
||
|
code::
|
||
|
5.unbubble;
|
||
|
[5].unbubble;
|
||
|
[[5]].unbubble;
|
||
|
[[5]].unbubble(0,2);
|
||
|
[4,5].unbubble;
|
||
|
[[4],[5]].unbubble;
|
||
|
[[4],[5]].unbubble(1);
|
||
|
z.bubble.unbubble;
|
||
|
z.bubble(1).unbubble(1);
|
||
|
z.bubble(2).unbubble(2);
|
||
|
::
|
||
|
|
||
|
section:: Laminating with the +++ operator
|
||
|
the +++ operator takes each item from the second list and appends it to the corresponding item
|
||
|
in the first list. If the second list is shorter, it wraps.
|
||
|
code::
|
||
|
z = Array.iota(5,2);
|
||
|
z +++ [77,88,99];
|
||
|
z +++ [[77,88,99]];
|
||
|
z +++ [[[77,88,99]]];
|
||
|
z +++ [ [[77]],[[88]],[[99]] ];
|
||
|
// same as:
|
||
|
z +++ [77,88,99].bubble;
|
||
|
z +++ [77,88,99].bubble(0,2);
|
||
|
z +++ [77,88,99].bubble(1,2);
|
||
|
|
||
|
z +++ [11,22,33].pyramidg;
|
||
|
z +++ [11,22,33].pyramidg.bubble;
|
||
|
z +++ [[11,22,33].pyramidg];
|
||
|
z +++ [[[11,22,33].pyramidg]];
|
||
|
|
||
|
|
||
|
(
|
||
|
z = (1..4);
|
||
|
10.do {|i|
|
||
|
z.pyramid(i+1).postln;
|
||
|
z.pyramidg(i+1).postln;
|
||
|
"".postln;
|
||
|
};
|
||
|
)
|
||
|
::
|
||
|
|
||
|
section:: reshapeLike
|
||
|
reshapeLike allows you to make one nested array be restructured in the same manner as another.
|
||
|
code::
|
||
|
a = [[10,20],[30, 40, 50], 60, 70, [80, 90]];
|
||
|
b = [[1, 2, [3, 4], [[5], 6], 7], 8, [[9]]];
|
||
|
a.reshapeLike(b);
|
||
|
b.reshapeLike(a);
|
||
|
::
|
||
|
If the lengths are different, the default behaviour is to wrap:
|
||
|
code::
|
||
|
a = [[10,20],[30, 40, 50]];
|
||
|
b = [[1, 2, [3, 4], [[5], 6], 7], 8, [[9]]];
|
||
|
a.reshapeLike(b);
|
||
|
::
|
||
|
but you can specify other index operators:
|
||
|
code::
|
||
|
a.reshapeLike(b, \foldAt);
|
||
|
|
||
|
a.reshapeLike(b, \clipAt);
|
||
|
|
||
|
a.reshapeLike(b, \at);
|
||
|
::
|
||
|
|
||
|
section:: measuring dimensionality and size
|
||
|
maxSizeAtDepth allows you to check the maximum array size at a given depth dimension
|
||
|
code::
|
||
|
[[1, 2, 3], [[41, 52], 5, 6], 1, 2, 3].maxSizeAtDepth(2);
|
||
|
[[1, 2, 3], [[41, 52], 5, 6], 1, 2, 3].maxSizeAtDepth(1);
|
||
|
[[1, 2, 3], [[41, 52], 5, 6], 1, 2, 3].maxSizeAtDepth(0);
|
||
|
(0..3).collect([[1, 2, 3], [[41, 52], 5, 6], 1, 2, 3].maxSizeAtDepth(_)) // max sizes for each dimension
|
||
|
::
|
||
|
|
||
|
section:: inverting dimensions
|
||
|
flopDeep allows you to to invert the outermost dimension with a dimension at any depth. This is analogous to flop, which does the same for 2-dimensional arrays.
|
||
|
code::
|
||
|
[[1, 2, 3], [[41, 52], 5, 6]].flopDeep(2);
|
||
|
[[1, 2, 3], [[41, 52], 5, 6]].flopDeep(1);
|
||
|
[[1, 2, 3], [[41, 52], 5, 6]].flopDeep(0);
|
||
|
[[1, 2, 3], [[41, 52], 5, 6]].flopDeep; // without argument, flop from the deepest level
|
||
|
|
||
|
[[[10, 100, 1000], 2, 3], [[41, 52], 5, 6]].flopDeep(2); // shorter array wraps
|
||
|
[].flopDeep(1); // result is always one dimension higher.
|
||
|
[[]].flopDeep(4);
|
||
|
::
|
||
|
|
||
|
|
||
|
section:: allTuples
|
||
|
allTuples will generate all combinations of the sub arrays
|
||
|
code::
|
||
|
[[1, 2, 3], [4, 5], 6].allTuples;
|
||
|
[[1, 2, 3], [4, 5, 6, 7], [8, 9]].allTuples;
|
||
|
::
|
||
|
|