// 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.


// iota fills an array with a counter

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 2D array

Array.fill2D(3,3,{1.0.rand.round(0.01)});


Array.fill2D(2,3,{|i,j| i@j});


// fill a 3D array

Array.fill3D(2,2,2,{1.0.rand.round(0.01)});


Array.fill3D(2,2,2,{|i,j,k| `[i,j,k]});



// using dup to create arrays

(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;


// ! is an abbreviation of dup

(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;


// other ways to do the same thing:

// partial application

_.squared ! 10;

_.nthPrime ! 10;


// operating on a list

(0..9).squared;

(0..9).nthPrime;


// operator adverbs

// Adverbs are a third argument passed to binary operators that modifies how they iterate over

// SequenceableCollections or Streams.

// see the Adverbs help file

[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


// 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.

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


// deepCollect operates a function at different dimensions or depths in an array.

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);


// slice can get sections of multidimensional arrays.

// nil gets all the indices of a dimension

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;


// sorting order


z = {100.rand}.dup(10); // generate a random array;

// order returns an array of indices representing what would be the sorted order of the array.

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

p = o.order; 

x = y[p];


// bubbling wraps an item in an array of one element. it takes the depth and levels as arguments.

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.

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);


// 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.

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;

};

)


// reshapeLike allows you to make one nested array be restructured in the same manner as another.


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:


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:


a.reshapeLike(b, \foldAt);


a.reshapeLike(b, \clipAt);


a.reshapeLike(b, \at);


// allTuples will generate all combinations of the sub arrays

[[1, 2, 3], [4, 5], 6].allTuples;

[[1, 2, 3], [4, 5, 6, 7], [8, 9]].allTuples;