#lang scribble/manual @(require (for-label racket)) @title{J concepts in SC} An overview of concepts borrowed from J@section{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{section} Filling arrays iota fills an array with a counter @racketblock[ 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 ] @racketblock[ // 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{section} Creating arrays using dup to create arrays @racketblock[ (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 ] @racketblock[ (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: ] @racketblock[ // partial application _.squared ! 10; _.nthPrime ! 10; // operating on a list (0..9).squared; (0..9).nthPrime; :: ] @section{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. @racketblock[ [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{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. @racketblock[ 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 :: ] @section{subsection} deepCollect deepCollect operates a function at different dimensions or depths in an array. @racketblock[ 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{section} Sections of multidimensional arrays slice can get sections of multidimensional arrays. nil gets all the indices of a dimension. @racketblock[ 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{section} Sorting order generate a random array: @racketblock[ z = {100.rand}.dup(10); :: order returns an array of indices representing what would be the sorted order of the array: ] @racketblock[ 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: ] @racketblock[ p = o.order; x = y[p]; :: ] @section{section} Bubbling bubbling wraps an item in an array of one element. it takes the depth and levels as arguments. @racketblock[ 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. ] @racketblock[ 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{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. @racketblock[ 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{section} reshapeLike reshapeLike allows you to make one nested array be restructured in the same manner as another. @racketblock[ 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: ] @racketblock[ 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: ] @racketblock[ a.reshapeLike(b, \foldAt); a.reshapeLike(b, \clipAt); a.reshapeLike(b, \at); :: ] @section{section} measuring dimensionality and size maxSizeAtDepth allows you to check the maximum array size at a given depth dimension @racketblock[ [[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{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. @racketblock[ [[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{section} allTuples allTuples will generate all combinations of the sub arrays @racketblock[ [[1, 2, 3], [4, 5], 6].allTuples; [[1, 2, 3], [4, 5, 6, 7], [8, 9]].allTuples; :: ]