Advertisement
richarduie

AplArrays.js

Mar 6th, 2019
456
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.    Filename..........AplArrays.js - Version 0.1.0
  3.    Purpose...........add support for APL-style Array operations not native to
  4.                      the base JavaScript Array object
  5.  
  6.    Author............RichardUIE (richarduie[at]yahoo[dot]com)
  7.    Created On........2012-07-31
  8.    Copyright (C) 2012 by richarduie  
  9.  
  10.    This program is free software; you can redistribute it and/or modify it under
  11.    the terms of the GNU General Public License as published by the Free Software
  12.    Foundation; either version 2 of the License, or (at your option) any later
  13.    version. This program is distributed in the hope that it will be useful, but
  14.    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15.    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  16.    more details.
  17.  
  18.    NOTE: the strained artificiality of some line breaks and text flow satisfies
  19.          the demand that the document width be no more than 80 characters.
  20. */
  21.  
  22. function AplArrays(objectName)
  23. {
  24. /*
  25.    Thinking about Arrays like an APL programmer for most function names (reduce,
  26.      scan, take, drop etc.) and the nature of abstract array processes.
  27.  
  28.    Glossary:
  29.       axis            by analogy to algebraic arrays, a dimension on which an
  30.                         array has elements ("dimension" and "axis" are used
  31.                         interchangeably)
  32.       homogeneous    overall structure of an array is the same on all axes ; an
  33.                        homogeneous array has the same structure for every
  34.                        element on a particular axis. For example, the array
  35.                        A = [[1,2],[3,4],[5,6]] has two dimensions - one axis can
  36.                        be conceived as rows and the other as columns...
  37.                        representing it visually in this fashion:
  38.  
  39.              +-------+
  40.              | 1 | 2 |   each row contains a sub-array of two elements
  41.              +-------+   each column contains a sub-array of three elements
  42.        A =   | 3 | 4 |   the elements within each box are of the same structure,
  43.              +-------+   i.e., scalar numbers in this example, and hence,
  44.              | 5 | 6 |   A is an homogeneous 3 x 2 array of numbers
  45.              +-------+
  46.  
  47.       nest           enclose as an array within an array; a nest can referenced
  48.                        by a single index into its parent, e.g., the array
  49.                        [1, [2, 3, 4], 5] has the nested element [2, 3, 4] at
  50.                        index 1
  51.       rank          number of axes (dimensions) of an array, e.g., a
  52.                       2-dimensional array is said to be of rank 2
  53.       vector        array that is addressed only on its first axis; in that
  54.                       sense, all JavaScript arrays are vectors, i.e., higher-
  55.                       order JavaScript arrays are vectors of vectors.
  56.  
  57.    NOTE: as primitive elements of Arrays, all Strings are treated as having
  58.          length 1 by convention.
  59.  
  60.    APL Primitive Function Symbols (for use in documentation):
  61.       ÷ × ∊ ⍴ ~ ∆ ⍙ ¨ ⌿ ⍀ < ≤ = ≥ > ≠ ∨ ∧ ↑ ↓ ⍳ ⌈ ⌊ ∇ ⊂ ⊃ ⊥ ⊤ | ⍱ ⍲ ⍒ ⍋ ⍉ ⌽ ⊖ !
  62.       ⍕ ⍎ ≡ ≢ ø ⍷ ← → ⎕
  63.  
  64.    NOTE: expression samples for APL usage reflect APL's Reverse Polish
  65.          Notation ([Polish] Postfix Notation]).
  66. */
  67.  
  68.    // public functions - - - - - - - - - - - - - - - - - - -
  69.    //
  70.    // Return Boolean AND of values of a with values of b.
  71.    // APL: a ∧ b
  72.    // apl is (optional) Boolean indicator of whether incoming values and return
  73.    // are JS- or APL-style Boolean values:
  74.    //    true ==> 0 for false and 1 for true - false (default) ==>  JS-style
  75.    //    true/false values
  76.    this.and = function(a, b, apl) {var self = {name:'and'};
  77.       // If argument not given, set default value.
  78.       if ('undefined' == typeof apl) apl = false;
  79.       // Verify a and b are arrays of same shape and all elements are boolean.
  80.       if (!this.validateArgs(self, [a,b], ['array','shape'])) return null;
  81.       // Verify a and b are boolean of correct style (APL or JS).
  82.  
  83.       if (
  84.          (apl && (!allBooleans(this.ravel(a), true) ||
  85.             !allBooleans(this.ravel(b), true))) ||
  86.          (!apl && (!allBooleans(this.ravel(a), false) ||
  87.             !allBooleans(this.ravel(b), false)))
  88.       ) throw AplArraysException(
  89.            null, self.name, 'InvalidArgumentTypeException', arguments
  90.         );
  91.       try {
  92.          // Copy elements of a and b into simple vectors.
  93.          var newA = this.ravel(a); var newB = this.ravel(b);
  94.          // Initialize return.
  95.          var c = new Array();
  96.          // For each pair of elements of a and b, assess and record logical AND.
  97.          for (var i = 0; i < newA.length; i++) {
  98.             // Assess and assign according to requested style.
  99.             c.push(apl?((1 == newA[i] && 1 == newB[i])?1:0):
  100.               (newA[i] && newB[i]));
  101.          }
  102.          // Reshape c into same form as a (and b) and return.
  103.          return this.reshape(c, this.shapeOf(a));
  104.       }
  105.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  106.    };
  107.    // Return concatenation of vectors (one-dimensional array) a with b in that
  108.    // order. a and b can be scalars or vectors.
  109.    // APL: a , b
  110.    this.catenate = function(a, b) {var self = {name:'catenate'};
  111.       if ( (this.isScalar(a) || this.isVector(a)) &&
  112.            (this.isScalar(b) || this.isVector(b))
  113.          )
  114.          throw AplArraysException(
  115.             null, self.name, 'InvalidArgumentTypeException', arguments
  116.          );
  117.       try {
  118.          // If scalar a, convert it to vector.
  119.          if (this.isScalar(a)) a = this.ravel(a);
  120.          return a.concat(b);
  121.       }
  122.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  123.    };
  124.    // Return copy of a (by value - no reference associations).
  125.    // APL: b ← a  (copula/assignment is by value in APL - no side effects)
  126.    this.clone = function(a) { var self = {name:'clone'};
  127.       // Verify a is array.
  128.       if (!this.validateArgs(self, [a], ['array'])) return null;
  129.       try {return this.scatterIndex(a, this.index(a.length));}
  130.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  131.    };
  132.    // Return elements of a that correspond to value of true in b by index. If b
  133.    // is composed of zeros and ones, 0 = false and 1 = true.
  134.    // APL: b / a  OR  b ⌿ a  for rank-2+ array a
  135.    this.compress = function(a, b) {var self = {name:'compress'};
  136.       // Verify a and b are arrays of same length and all elements of b are
  137.       // Boolean.
  138.       if (!this.validateArgs(self, [a,b], ['array','length']) ||
  139.           !this.validateArgs(self, [b], ['boolean'])
  140.       ) return null;
  141.       try {
  142.          // If APL Booleans convert to JS equivalent.
  143.          if (0 == b[0] || 1 == b[0]) b = convertBooleans(b);
  144.          // Initialize return.
  145.          var newA = new Array();
  146.          // If element of b is true, keep corresponding element of a.
  147.          for (var i = 0; i < a.length; i++) {if (b[i]) newA.push(a[i]);}
  148.          return newA;
  149.       }
  150.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  151.    };
  152.    // Form "cross-product" of all elements of x and y by operation op, e.g.,
  153.    // [1,2] op [3,4] ==> [1 op 3, 1 op 4, 2 op 3, 2 op 4]
  154.    // APL: a op ¨ b
  155.    this.crossVectors = function(a, b, op) {var self = {name:'crossVectors'};
  156.       // Verify a and b are vectors.
  157.       if (!this.validateArgs(self, [a,b], ['vector'])) return null;
  158.       try {
  159.          // Set default operation to {+} (add/concatenate), if not given.
  160.          if ('undefined' == typeof op) op = '+';
  161.          var c = new Array();
  162.          // For each element of a...
  163.          for (var i = 0; i < a.length; i++) {
  164.          // ...for each element of b...
  165.             for (var j = 0; j < b.length; j++) {
  166.                // ...evaluate action of operation on current values of a an b.
  167.                c.push(eval(a[i] + op + b[j]));
  168.             }
  169.          }
  170.          return c;
  171.       }
  172.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  173.    };
  174.    // Return a.length - len elements of a on first axis.
  175.    // len > 0 ==> drop elements from start...len < 0 ==> drop elements from end
  176.    // APL: len ↓ a
  177.    this.drop = function(a, len) {var self = {name:'drop'};
  178.       // Verify a is an array.
  179.       if (!this.validateArgs(self, [a], ['array'])) return null;
  180.       try {
  181.          // If |len| greater than length of a, return empty.
  182.          if (len > a.length || -len > a.length) return [];
  183.          // if len is positive, get last a.length - len elements of a -
  184.          // remove/drop first len elements.
  185.          if (0 > len) a = this.scatterIndex(a, this.index(a.length + len));
  186.          // Otherwise, get last a.length - len values of a - remove/drop last
  187.          // len elements.
  188.          else a = this.mirror(this.scatterIndex(
  189.                      this.mirror(a), this.index(a.length - len))
  190.                   );
  191.          return a;
  192.       }
  193.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  194.    };
  195.    // Return a with an additional level of nesting (enclosure as an array) of
  196.    // entire a.
  197.    // APL: a ⊂ b produces a scalar - this actually produces a singleton vector
  198.    // like: , a ⊂ b  (ravel a enclose b)
  199.    this.enclose = function(a) {return [a];};
  200.    // Return Boolean indicators of whether a and b are equal on an element-wise
  201.    // basis. Return is array of same structure as arguments with true for
  202.    // matches, and false for mismatches. If shapes don't match, returns -1. apl
  203.    // is (optional) Boolean indicator of whether to return JS or APL.
  204.    // Booleans: true ==> return 0 for false and 1 for true - false (default) ==>
  205.    // return JS true/false values
  206.    // APL: a = b
  207.    this.equals = function(a, b, apl) {var self = {name:'equals'};
  208.       try {
  209.          // If argument not given, set default.
  210.          if ('undefined' == typeof apl) apl = false;
  211.          // If both are scalars, exit immediately, returning simple equals
  212.          // assessment in either JS or APL terms.
  213.          if (this.isScalar(a) && this.isScalar(b))
  214.             return apl?((a == b)?1:0):a == b;
  215.          // (implicit else) If a is scalar, and b is Array, convert a to Array.
  216.          if (this.isScalar(a) && this.isArray(b))
  217.             a = this.ravel(this.reshape(a, this.shapeOf(b)));
  218.          // If b is scalar, and a is Array, convert b to Array.
  219.          if (this.isArray(a) && this.isScalar(b))
  220.             b = this.ravel(this.reshape(b, this.shapeOf(a)));
  221.          // Record shape of a.
  222.          var shape = this.shapeOf(a);
  223.          // If shapes don't match, exit immediately, returning -1.
  224.          if (!this.match(shape, this.shapeOf(b))) return -1;
  225.          // (implicit else) Convert a and b to vectors by value.
  226.          var newA = this.ravel(this.clone(a));
  227.          var newB = this.ravel(this.clone(b));
  228.          // Initialize return.
  229.          var isEqual = new Array();
  230.          // For each element of a (and b), assess equality by value.
  231.          for (var i = 0; i < newA.length; i++) {
  232.             isEqual.push(newA[i] == newB[i]);
  233.          }
  234.          // If APL-style Booleans requested, convert.
  235.          if (apl) isEqual = convertBooleans(isEqual, true);
  236.          return this.reshape(isEqual, shape);
  237.       }
  238.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  239.    };
  240.    // Return array flipped top-to-bottom (row-order reversed for rank-2 array)
  241.    // on first axis.
  242.    // APL: ⊖ a
  243.    this.flip = function(a) {var self = {name:'flip'};
  244.       // Only valid, if a has at least rows and columns (rank-2 minimum)
  245.       if (2 > this.rankOf(a))
  246.          throw AplArraysException(null, self.name, 'RankException', arguments);
  247.       // Return elements reversed on indexes of first axis.
  248.       try {return this.scatterIndex(a, this.mirror(this.index(a.length)));}
  249.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  250.    };
  251.    // Return frequencies of unique elements of a.
  252.    this.freq = function(a) {var self = {name:'freq'};
  253.       // Verify a is array.
  254.       if (!this.validateArgs(self, [a], ['array'])) return null;
  255.       try {
  256.          // Get sorted vector of values of primitive elements of a.
  257.          var r = (this.ravel(this.clone(a))).sort();
  258.          // Get unique values of r in serial order of occurrence (see nub() and
  259.          // kernel() for possible exceptions about order of elements).
  260.          var n = this.nub(r);
  261.          // Get number of unique elements.
  262.          var last = n.length;
  263.          // Initialize frequency counts Array.
  264.          var f = new Array();
  265.          // For each unique element...
  266.          for (var i = 0; i < last; i++) {
  267.             // ...get total number of elements of a that match current r.
  268.             f.push(this.reduce(this.equals(n[i],r,true)));
  269.          }
  270.          return this.reshape(this.catenate(n,f),[2,last]);
  271.       }
  272.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  273.    };
  274.    // Return integer vector of length n, starting at io (index origin -
  275.    // hypothetically 0 or 1 in APL terms). returns [0,1,2,...,n-1] or
  276.    // [1,2,3,...,n].
  277.    // NOTE: current implementation can also (sort of mis)use io to generate
  278.    // integer vectors of form [io, io+1, io+2,..., io+n-1] for io not 0 or 1.
  279.    // APL: ⍳ n  (with ⎕IO, index origin, sensitivity)
  280.    this.index = function(n, io) {var self = {name:'index'};
  281.       // If n is not an integer greater than zero, throw error.
  282.       if (n < 0 || n != Math.floor(n))
  283.          throw AplArraysException(
  284.             null, self.name, 'InvalidArgumentTypeException', arguments
  285.          );
  286.       try {
  287.          // If index-origin not given, set default of 0.
  288.          if ('undefined' == typeof io) io = 0;
  289.          // Set lower bound for iteration.
  290.          var last = io + n;
  291.          // Initialize return.
  292.          var idx = new Array();
  293.          // Set value.
  294.          for (var i = io; i < last; i++) {
  295.             idx.push(i);
  296.          }
  297.          return idx;
  298.       }
  299.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  300.    };
  301.    // Return integer vector of index positions of first occurrences of elements
  302.    // of vector b in vector a, starting at io (index origin - hypothetically 0
  303.    // or 1 in APL terms). If element not found, value of it index is one greater
  304.    // than greatest index of a.
  305.    // APL: a ⍳ b  (dyadic ⍳ with ⎕IO, index origin, sensitivity)
  306.    this.indexOf = function(a, b, io) {var self = {name:'indexOf'};
  307.       // If index origin not defined, set default value (same for APL and JS,
  308.       // i.e., 0).
  309.       if ('undefined' == typeof io) io = 0;
  310.       // Extend b to singleton vector, if needed.
  311.       if(this.isScalar(b)) b = this.ravel(b);
  312.       // Verify a and b are arrays.
  313.       if (!this.validateArgs(self, [a,b], ['array'])) return null;
  314.       // If io not zero or one, throw error.
  315.       if (0 != io && 1 != io)
  316.          throw AplArraysException(
  317.             null, self.name, 'InvalidArgumentTypeException', arguments
  318.          );
  319.       try {
  320.          // How many elements in b.
  321.          var last = b.length;
  322.          // Initialize return.
  323.          var idxs = new Array();
  324.          // For each element in b...
  325.          for (var i = 0; i < last; i++) {
  326.             // ...set current value of index to take io into account.
  327.             var idx = a.length + io;
  328.             // ...for each element of a...
  329.             for (var j = 0; j < a.length; j++) {
  330.                // ...if current element of b matches current a, set index and...
  331.                // ...exit loop - limited search-&-destroy.
  332.                if (this.match(b[i], a[j])) {
  333.                   idx = j + io;
  334.                   break;
  335.                }
  336.             }
  337.             // ...capture value.
  338.             idxs.push(idx);
  339.          }
  340.          return idxs;
  341.       }
  342.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  343.    };
  344.    // Form inner product of rank-2 arrays a and b, using operations op and op2
  345.    // (default to + and *) number of columns of a must be same that of of b.
  346.    // Return is rank-2 array with number of rows of a and number of columns as
  347.    // the number of rows b with all elements of rows of a combined element-wise
  348.    // with corresponding (by index) elements of rows of b, using op2 - then
  349.    // those compositions reduced by op.
  350.    // APL: a op . op2 b
  351.    // NOTE: in matrix-theory/linear-algebra, this is the standard inner product,
  352.    // although conformability would require columns of a and rows of b to match  
  353.    // by length; here rows and rows must match by length, i.e., the current
  354.    // implementation treats rows of b as columns in the linear algebraic sense.
  355.    // If you know linear algebra, the transpose of b would be the right-hand
  356.    // argument.
  357.    // NOTE: a variety of interesting and possibly unexpected results are
  358.    // available from this, e.g., || with != identifies whether any elements of
  359.    // the rows of a differ from those of the elements of the rows of b, e.g.,
  360.    // innerProduct([[1,2],[3,4]],[[1,2],[3,4]], '||','!=') ==>
  361.    // [
  362.    //      [((1 != 1) || (2 != 2)),((1 != 3) || (2 != 4))],
  363.    //      [((3 != 1) || (4 != 2)),((3 != 3) || (4 != 4))]
  364.    //   ]  ==>
  365.    // [ [(false||false),(true||true)],   [(true||true),(false||false)] ]  ==>
  366.    // [ [false, true], [true,false] ]
  367.    this.innerProduct = function(a, b, op, op2) {
  368.       // If either is vector, nest one level deeper.
  369.       if (this.isVector(a)) a = this.enclose(a);
  370.       if (this.isVector(b)) b = this.enclose(b);
  371.       // Verify a and b are arrays.
  372.       if (!this.validateArgs(self, [a,b], ['array'])) return null;
  373.       // Column length of a must equal that of b - if not throw error.
  374.       if (!this.validateArgs(self, [a[0],b[0]], ['length'])) return null;
  375.       // If not given, set default operations to give arithmetic inner product.
  376.       if ('undefined' == typeof op) op = '+';
  377.       if ('undefined' == typeof op2) op2 = '*';
  378.       // Calls first axis of a ("rows") and first of b ("cols").
  379.       var rows = this.shapeOf(a)[0]; var cols = this.shapeOf(b)[0];
  380.       // Initialize return.
  381.       var p = new Array();
  382.       // For each row-vector of a...
  383.       for (var i = 0; i < rows; i++) {
  384.          // ...for each column-vector (actually also row) of b...
  385.          for (var j = 0; j < cols; j++) {
  386.             // ...generate op2-product of elements of a and b - then op-reduce.
  387.             p.push(this.reduce(this.op(a[i], b[j], op2), op));
  388.          }
  389.       }
  390.       return this.reshape(p, [rows,cols]);
  391.    };
  392.    // Return Boolean indicator of whether a is an Array.
  393.    this.isArray = function(a) {var self = {name:'isArray'};
  394.       try {return -1 != a.constructor.toString().indexOf('Array');}
  395.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  396.    };
  397.    // Return Boolean indicator of whether a an b are homogeneous and (shape-
  398.    // wise) conformable.
  399.    this.isConformable = function(a, b) {var self = {name:'isConformable'};
  400.       try {
  401.          return this.isHomogeneous(a) && this.isHomogeneous(b) &&
  402.             this.equals(this.rankOf(a), this.rankOf(b)) &&
  403.             this.reduce(this.equals(this.shapeOf(a), this.shapeOf(b)), '&&');
  404.       }
  405.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  406.    };
  407.    // Return Boolean indicator of whether a is homogeneous. If a is empty,
  408.    // return true.
  409.    this.isHomogeneous = function(a) {var self = {name:'isHomogeneous'};
  410.       try {return -1 != this.shapeOf(a);}
  411.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  412.    };
  413.    // Return Boolean indicator of whether all elements at outermost level are of
  414.    // same length.
  415.    this.isLevelHomogeneous = function(a) {var self = {name:'isLevelHomogeneous'};
  416.       // Verify a is array.
  417.       if (!this.validateArgs(self, [a], ['array'])) return null;
  418.       try {
  419.          // Declare Boolean state of whether a homogeneous on current axis in
  420.          // block scope
  421.          var isLvlHomo;
  422.          // For each element of a on first axis...
  423.          for (var i = 0; i < a.length; i++) {
  424.             // ...homogeneity obtains, if all are Strings.
  425.             if (this.isString(a[0])) isLvlHomo = this.isString[i];
  426.             // ...or homogeneity obtains, if all are of same length.
  427.             else isLvlHomo = this.isScalar(a[i]) || a[0].length == a[i].length;
  428.             // ...limited search-&-destroy...stop checking, if any fails.
  429.             if (!isLvlHomo) break;
  430.          }
  431.          return isLvlHomo;
  432.       }
  433.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  434.    };
  435.    // Return Boolean indicator of whether a is scalar.
  436.    // APL: 0 = ⍴ ⍴ a
  437.    this.isScalar = function(a) {var self = {name:'isScalar'};
  438.       try {return (this.isArray(a))?false:a == (this.ravel(a))[0];}
  439.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  440.    };
  441.    // Return Boolean indicator of whether a is a String.
  442.    this.isString = function(a) {var self = {name:'isString'};
  443.       try {return -1 != a.constructor.toString().indexOf('String');}
  444.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  445.    };
  446.    // Return Boolean indicator of whether a is vector (rank-1, i.e.,
  447.    // 1-dimensional array).
  448.    // APL: 1 = ⍴ ⍴ a
  449.    this.isVector = function(a) {var self = {name:'isVector'};
  450.       try {return 1 == this.rankOf(a);}
  451.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  452.    };
  453.    // Return unique elements of a on first dimension. If scalar a, return a.
  454.    // APL: extends idiom {(( a ⍳ a ) = ⍳ ⍴ a ) / a} to multi-dimensional arrays
  455.    // NOTE: does not guarantee that elements of return will be in original
  456.    // serial order (as is the case with the nub idiom), since toString()
  457.    // operations on array elements may not go into a JS associative Array in an
  458.    // order that preserves that serial order of a for later retrieval under the
  459.    // {for (in)} enumeration applied here.
  460.    this.kernel = function(a) {var self = {name:'kernel'};
  461.       try {
  462.          // Set hashtable style associative array working storage.
  463.          var r = new Array();
  464.          // For each element in a...
  465.          for (var i = 0; i < a.length; i++) {
  466.             // ...assign key/value pair - repeated values have same key and
  467.             // replace one another, leaving only single instance of value.
  468.             r[a[i].toString()] = a[i];
  469.          }
  470.          // Initialize return.
  471.          var k = new Array();
  472.          // For each key in r...
  473.          for (var el in r) {
  474.             // ...set value into index-oriented array.
  475.             k.push(r[el]);
  476.          }
  477.          return k;
  478.       }
  479.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  480.    };
  481.    // Return row or column-wise concatenation of multi-dimension arrays a and b
  482.    // in that order. axis specifies dimension on which lamination is performed
  483.    // as: 0 ==> (default) a ON b (row lamination) - 1 ==> a BY b (column
  484.    // lamination).
  485.    // APL: a , [n] b
  486.    this.laminate = function(a, b, axis) {var self = {name:'laminate'};
  487.       // If axis argument not given, set default value to row(s).
  488.       if ('undefined' ==  typeof axis) axis = 0;
  489.       // Initialize return.
  490.       var l = new Array();
  491.       // If requested axis is invalid, throw error.
  492.       if (0 != axis && 1 != axis)
  493.          throw AplArraysException(null, self.name, 'AxisException', arguments);
  494.       try {
  495.       // If axis requested is "row"...
  496.       if (0 == axis) {
  497.             // Copy a and b by value to avoid side effects.
  498.             var newA = this.clone(a);var newB = this.clone(b);
  499.             // Extend a or b as needed to provide shape conformability.
  500.             if (1 == this.rankOf(a))
  501.                newA = this.reshape(newA, [1,this.shapeOf(a)]);
  502.             if (1 == this.rankOf(b))
  503.                newB = this.reshape(newB, [1,this.shapeOf(b)]);
  504.             // Record shapes of a and b.
  505.             var sA = this.shapeOf(newA); sB = this.shapeOf(newB);
  506.             // If not shape-wise conformable below level of first axis, quit
  507.             // immediately, returning -1.
  508.             if (!this.match(this.drop(sA,1),this.drop(sB,1))) return -1;
  509.             // (implicit else) Set new shape of intermediate result.
  510.             var dim = this.catenate([sA[0] + sB[0]], this.drop(sA,1));
  511.             // Reshape concatenated results.
  512.             l = this.reshape(
  513.                    this.catenate(this.ravel(newA),this.ravel(newB)),dim
  514.                 );
  515.          }
  516.       // Otherwise, axis requested is "column"...
  517.          else {
  518.             // If a and b are vectors, combine into rank-2 array.
  519.             if (1 == this.rankOf(a) && 1 == this.rankOf(b))
  520.                for (var i = 0; i < a.length; i++ ) {
  521.                   l.push([a[i], b[i]]);
  522.                }
  523.             // Otherwise, transpose, recurse, and (un)transpose.
  524.             else l = this.transpose(
  525.                         this.laminate(this.transpose(a),this.transpose(b))
  526.                      );
  527.          }
  528.          return l;
  529.       }
  530.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  531.    };
  532.    // Return Boolean indicator of whether a and b are equal structurally and
  533.    // element-wise by values.
  534.    // APL: a ≡ b
  535.    this.match = function(a, b) {var self = {name:'match'};
  536.       try {
  537.          // If a and b are Arrays of same length on first axis...
  538.          if (this.isArray(a) && this.isArray(b) && a.length == b.length) {
  539.             // ...declare logical state of matching detection to block scope.
  540.             var isMatch;
  541.             // ...for each element pair of a and b...
  542.             for (var i = 0; i < a.length; i++) {
  543.                // ...recursively update state of match detection.
  544.                isMatch = this.match(a[i], b[i]);
  545.                // ...limited search-&-destroy - quit for detection of unmatched.
  546.                if (!isMatch) break;
  547.             }
  548.             return isMatch;
  549.          }
  550.          else return a == b;
  551.       }
  552.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  553.    };
  554.    // Return maximum of elements of numerical array a.
  555.    // APL: ⌈ / a
  556.    this.max = function(a) {var self = {name:'max'};
  557.       // Return first element of reversed items when sorted ascending.
  558.       try {return (this.mirror((this.ravel(a)).sort()))[0];}
  559.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  560.    };
  561.    // Return arithmetic mean (average) of elements of numerical array a.
  562.    // APL: ( + / a ) ÷ ⍴ a
  563.    this.mean = function(a) {var self = {name:'mean'};
  564.       // Plus-reduce elements and divide by number of elements,
  565.       try {var r = this.ravel(a);return this.reduce(r)/r.length;}
  566.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  567.    };
  568.    // Return minimum of elements of numerical array a.
  569.    // APL: ⌊ / a
  570.    this.min = function(a) {var self = {name:'min'};
  571.       // Return first element of items when sorted ascending.
  572.       try {return ((this.ravel(a)).sort())[0];}
  573.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  574.    };
  575.    // Reverse left-right order of elements of a on first axis. For scalar a,
  576.    // simply return a.
  577.    // APL: ⌽ a
  578.    this.mirror = function(a) {var self = {name:'mirror'};
  579.       try {
  580.          // Return a immediately, if scalar.
  581.          if (this.isScalar(a)) return a;
  582.          // (implicit else) Initialize return.
  583.          var rev = new Array();
  584.          // Set elements of a into rev in reverse order on first axis.
  585.          for (var i = 0; i < a.length; i++) {rev.unshift(a[i]);}
  586.          return rev;
  587.       }
  588.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  589.    };
  590.    // Remove depth levels of nesting from a.
  591.    // APL: ⊃ a  (applied depth times)
  592.    this.mix = function(a, depth) {var self = {name:'mix'};
  593.       // Verify a is array.
  594.       if (!this.validateArgs(self, [a], ['array'])) return null;
  595.       // If depth is given but beyond nesting levels of a, throw error.
  596.       if ('undefined' != typeof depth && (depth < 0 || depth > this.rankOf(a)))
  597.          throw AplArraysException(
  598.                   null, self.name, 'InvalidDepthException', arguments
  599.                );
  600.       try {
  601.          // Set default value, if not given.
  602.          if ('undefined' == typeof depth) depth = 0;
  603.          // Set default value, if not given.
  604.          if ('undefined' == typeof newA) var newA = new Array();
  605.          // For each element of a, add to newA.
  606.          for (var i = 0; i < a.length; i++) {newA = newA.concat(a[i]);}
  607.          // If more level to be disclosed, recurse.
  608.          if (0 < depth) return this.mix(newA, depth - 1);
  609.          // Otherwise, return newA.
  610.          else return newA;
  611.       }
  612.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  613.    };
  614.    // For an homogeneous array of Booleans, return the negations of all values.
  615.    // Values can be given as JS true/false or APL-style 1/0, and return will
  616.    // contain negations of same style.
  617.    // APL: ~ a
  618.    this.not = function(a) {var self = {name:'not'};
  619.       // Verify a is homogeneous (and) array.
  620.       if (!this.validateArgs(self, [a], ['array', 'homogenous'])) return null;
  621.       // Record original shape.
  622.       var s = this.shapeOf(a);
  623.       // Remove all internal nesting and clone.
  624.       var notA = this.clone(this.ravel(a));
  625.       // If elements are APL-style true/false, negate that way.
  626.       if (allBooleans(notA, true)) {
  627.          for (var i = 0; i < notA.length; i++) {notA[i] = (1 + notA[i])%2;}
  628.       }
  629.       // Otherwise, if elements are JS-style true/false, negate that way.
  630.       else if (allBooleans(notA)) {
  631.          for (var i = 0; i < notA.length; i++) {notA[i] = !notA[i];}
  632.       }
  633.       // Otherwise, throw error.
  634.       else throw InvalidArgumentTypeException;
  635.       return this.reshape(notA, s);
  636.    };
  637.    // Return unique elements of a in order encountered on first axis.
  638.    // APL: (( a ⍳ a ) = ⍳ ⍴ a ) / a  (for vectors)
  639.    // NOTE: unlike APL nub idiom, if kernel() is called, the serial order of the
  640.    // elements of the return is not guaranteed to be that of the original serial
  641.    // order of those (nub) elements as they occur in a.
  642.    this.nub = function(a) {var self = {name:'nub'};
  643.       // If scalar a, exit immediately, returning a.
  644.       if (this.isScalar(a)) return a;
  645.       try {
  646.          // If rank-1 Array, use classic idiom and return immediately.
  647.          if (this.isVector(a))
  648.             return this.compress(
  649.                a, this.ravel(
  650.                   this.equals(this.indexOf(a, a), this.index(a.length))
  651.                )
  652.             );
  653.          // Otherwise (higher-order Array), call alternate method.
  654.          else return this.kernel(a);
  655.       }
  656.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  657.    };
  658.    // Return result of applying op to element-pairs of a and b index-wise. op is
  659.    // some JS operation enclosed in quotes, e.g., '*'.
  660.    // APL: a op b  (for shape-wise conformable arrays)
  661.    this.op = function(a, b, op) {var self = {name:'op'};
  662.       // Verify a and b are arrays of same shape.
  663.       if (!this.validateArgs(self, [a,b], ['array', 'shape'])) return null;
  664.       try {
  665.          // If op not given, set default (add/concatenate).
  666.          if ('undefined' == typeof op) op = '+';
  667.          // Reshape both to simple, rank-1 vectors.
  668.          var newA = this.ravel(a);var newB = this.ravel(b);
  669.          // For each item pair...
  670.          for (var i = 0; i < newA.length; i++) {
  671.             // ...capture evaluation of operation on current element-pair.
  672.             eval('newA[i] = newA[i] ' + op + ' newB[i]');
  673.          }
  674.          // Reshape to that of original and return.
  675.          return this.reshape(newA, this.shapeOf(a));
  676.       }
  677.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  678.    };
  679.    // Return Boolean OR of values of a with values of b.
  680.    // APL: a ∨ b
  681.    // apl is (optional) Boolean indicator of whether to return JS or APL
  682.    // Booleans: true ==> return 0 for false and 1 for true - false (default) ==>
  683.    // return JS true/false values
  684.    this.or = function(a, b, apl) {var self = {name:'or'};
  685.       // Verify a and b are arrays of same shape.
  686.       if (!this.validateArgs(self, [a,b], ['array', 'shape'])) return null;
  687.       try {
  688.          // If argument not given, set default.
  689.          if ('undefined' == typeof apl) apl = false;
  690.          var newA = this.ravel(a); var newB = this.ravel(b);
  691.          var all = true;
  692.          // If 1/0 truth values requested, verify that both a and b have them
  693.          if (apl) all = allBooleans(newA, apl) && allBooleans(newB, apl);
  694.          // Otherwise, verify that both a and b have JS Booleans
  695.          else all = allBooleans(newA, false) && allBooleans(newB, false);
  696.          if (!all)
  697.             throw AplArraysException(
  698.                      null, self.name, 'InvalidArgumentTypeException', arguments
  699.                   );
  700.          var c = new Array();
  701.          for (var i = 0; i < newA.length; i++) {
  702.             c.push(apl?(1 == newA[i] || 1 == newB[i]):(newA[i] || newB[i]));
  703.          }
  704.          return this.reshape(c, this.shapeOf(a));
  705.       }
  706.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  707.    };
  708.    // Return rank (number dimensions) of homogeneous a. If a is not homogeneous,
  709.    // return -1.
  710.    // APL: ⍴ ⍴ a  (literally, this is the shape of the shape)
  711.    this.rankOf = function(a) {var self = {name:'rankOf'};
  712.       try {
  713.          // If scalar, exit immediately, returning 0.
  714.          if (this.isScalar(a)) return 0;
  715.          // Otherwise, return shape of shape.
  716.          return (-1 != this.shapeOf(a))?this.shapeOf(this.shapeOf(a)):-1;
  717.       }
  718.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  719.    };
  720.    // Remove nesting of multi-dimensionsional array and return vector of all
  721.    // elements of a from top-to-bottom, left-to-right, etc. - also converts
  722.    // scalar to singleton vector.
  723.    // APL: , a
  724.    this.ravel = function(arg, flat) {var self = {name:'ravel'};
  725.       // If flat - accumulator array - not yet defined, create it.
  726.       if ('undefined' == typeof flat) flat = new Array();
  727.       try {
  728.          // If arg is an array, iterate over the elements in index order.
  729.          if (this.isArray(arg)) {
  730.             // Call self recursively to get elements or process next, outermost
  731.             // level of nesting.
  732.             for (var i = 0; i < arg.length; i++) {this.ravel(arg[i],flat);}
  733.          }
  734.          // Otherwise, just add simple element to accumulator.
  735.          else flat.push( arg );
  736.          // Return accumulated values.
  737.          return flat;
  738.       }
  739.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  740.    };
  741.    // Insert operator op between each element of a and evaluate expression
  742.    // (default is +). op is a JS operation enclosed in quotes, e.g., '=='.
  743.    // APL: op / a
  744.    this.reduce = function(a, op) {var self = {name:'reduce'};
  745.       // Verify a is array.
  746.       if (!this.validateArgs(self, [a], ['array'])) return null;
  747.       try {
  748.          // Set default op (add/concatenate), if not given.
  749.          if ('undefined' == typeof op) op = '+';
  750.          // Initialize command to evaluate.
  751.          var cmd = '';
  752.          // Compose command string - concatenate each element with op.
  753.          for (var i = 0; i < a.length; i++) {cmd += a[i] + op;}
  754.          // Remove, extraneous, trailing instance of op and return evaluation.
  755.          return eval(cmd.substring(0, cmd.length - op.length));
  756.       }
  757.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  758.    };
  759.    // Reshape a according to the dimensions in dim. If number of elements
  760.    // required is greater than the number of elements in a, repeat the elements
  761.    // of a as often as needed to fill required shape.
  762.    // APL: dim ⍴ a
  763.    this.reshape = function(a, dim, isRecurse) {var self = {name:'reshape'};
  764.       try {
  765.          // If not recursing.
  766.          if ('undefined' == typeof isRecurse) {
  767.             // If dim is scalar, convert to singleton vector.
  768.             if (this.isScalar(dim)) dim = this.ravel(dim);
  769.             // Convert values of a to rank-1 array (vector).
  770.             var newA = this.ravel(this.isScalar(a)?a:this.clone(a));
  771.             // How many elements required in all to fill dimensions.
  772.             var len = this.reduce(dim,'*');
  773.             // Repeat values of a to at least number required.
  774.             while (newA.length < len) {newA = this.catenate(newA, newA)};
  775.             // Take as many as need to fit total number required.
  776.             newA = this.take(newA, len);
  777.             // Recurse to do actual work.
  778.             return this.reshape(newA, dim, true);
  779.          }
  780.          // Otherwise, build the required structure.
  781.          else {
  782.             // Number of elements required on last dimension.
  783.             var len = this.take(dim, -1);
  784.             // Length of blocks of a to fill current level.
  785.             var last = Math.round(a.length/len);
  786.             // Copy values of a.
  787.             var newA = this.clone(a);
  788.             // Initialize raw return.
  789.             var newB = new Array();
  790.             // For each block of elements of a...
  791.             for (var i = 0; i < last; i++) {
  792.                // ...capture len elements from a for this block.
  793.                newB.push(this.take(this.drop(newA,i*len), len));
  794.             }
  795.             // If not at last dimension, recurse - otherwise, remove outermost
  796.             // level of nesting.
  797.             return (1 < dim.length)?
  798.                       this.reshape(newB, this.drop(dim, -1),true):
  799.                       this.mix(newB);
  800.          }
  801.       }
  802.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  803.    };
  804.    // Apply scalar s to each element of a using operation op (default is +). If
  805.    // (optional) rev(erse) is true, apply op as {s op a[i]} for each i - if rev
  806.    // is false (default), apply op as {a[i] op s}
  807.    // APL: a op s
  808.    this.scalarOp = function(a, s, op, rev) {var self = {name:'scalarOp'};
  809.       // If arguments of wrong types, throw error.
  810.       if (!this.isScalar(s) || !this.isArray(a))
  811.          throw AplArraysException(
  812.                   null, self.name, 'InvalidArgumentTypeException', arguments
  813.                );
  814.       try {
  815.          // Set defaults, if not given.
  816.          if ('undefined' == typeof op) op = '+'; if ('undefined' == typeof rev) rev = false;
  817.          // Create conformable array with shape of a with s for all elements.
  818.          var sa = this.reshape(this.ravel(s), this.shapeOf(a));
  819.          // Call op() with argument order dependent on rev.
  820.          return rev?this.op(sa,a,op):this.op(a,sa,op);
  821.       }
  822.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  823.    };
  824.    // Reduce each index-wise subset of a, according to operation op (default is
  825.    // +), i.e., subsets of elements of a are drawn by indexes like
  826.    // {0}, {0,1}, {0,1,2}...{0,1,2,...,n}. op is a JS operation enclosed in
  827.    // quotes, e.g., '*'.
  828.    // APL: op \ a
  829.    this.scan = function(a, op) {var self = {name:'scan'};
  830.       // Verify a is array.
  831.       if (!this.validateArgs(self, [a], ['array'])) return null;
  832.       try {
  833.          // If not given, set to default (add/concatenate).
  834.          if ('undefined' == typeof op) op = '+';
  835.          // Initialize return.
  836.          var newA = new Array();
  837.          // For each element of a...
  838.          for (var i = 1; i <= a.length; i++) {
  839.             // ...capture reduction of subsets of elements from first (index 0)
  840.            // to current index.
  841.             newA.push(this.reduce(this.scatterIndex(a, this.index(i)), op));
  842.          }
  843.          return newA;
  844.       }
  845.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  846.    };
  847.    // Return elements of a with index positions in (vector or scalar) idxs on
  848.    // first axis of a in the order given by idxs.
  849.    // APL: a[idxs]  (for vectors)
  850.    this.scatterIndex = function (a, idxs) {var self = {name:'scatterIndex'};
  851.       // If idxs is scalar, convert to singleton vector.
  852.       if (this.isScalar(idxs)) idxs = this.ravel(idxs);
  853.       try {
  854.          // Initialize return.
  855.          var newA = new Array();
  856.          // For each element of idxs, add corresponding element to return.
  857.          for (var i = 0; i < idxs.length; i++) {newA.push(a[idxs[i]]);}
  858.          return newA;
  859.       }
  860.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  861.    };
  862.    // Return shape of homogeneous a - if not homogeneous, return -1. Shape is a
  863.    // vector of the length on each axis in axis order, e.g., shape of
  864.    // [1,2,3] is [3], shape of [[1,2,3],[4,5,6]] is [2,3].
  865.    // APL: ⍴ A
  866.    this.shapeOf = function(a, dim) {var self = {name:'shapeOf'};
  867.       // If a has no length, exit immediately, returning [0].
  868.       if ('undefined' != typeof a &&
  869.           'undefined' != typeof a.length &&
  870.           0 == a.length
  871.          ) return [0];
  872.       // If this is not array, exit immediately, returning empty.
  873.       if (!this.isArray(a)) return '';
  874.       // If not yet defined (not yet recursing), initialize return.
  875.       if ('undefined' == typeof dim) dim = new Array();
  876.       try {
  877.          // If in first recursion.
  878.          if (0 == dim.length) {
  879.             // Add length of a on first axis.
  880.             dim.push(a.length);
  881.             // If String, return current dim.
  882.             if (this.isString(a[0])) return dim;
  883.             // Assess length of current element (could be undefined).
  884.             var len = a[0].length;
  885.             // If all elements homogeneous at this level.
  886.             if (this.isLevelHomogeneous(a)) {
  887.                // If length is defined, update dim with this length.
  888.                if ('undefined' != typeof len) dim.push(len);
  889.                // Recurse to next nesting level.
  890.                return this.shapeOf(this.mix(a), dim);
  891.             }
  892.             // Otherwise, return -1 to indicate a is not homogeneous.
  893.             else return -1;
  894.          }
  895.          else {
  896.             // Get length of a on next axis.
  897.             var len = a[0].length;
  898.             // If length does not exist, exit immediately, returning dim.
  899.             if ('undefined' == typeof len) return dim;
  900.             // If element is String, exit immediately, returning dim.
  901.             if (this.isString(a[0])) return dim;
  902.             // (implicit else) Capture length on current axis.
  903.             if (this.isLevelHomogeneous(a)) {dim.push(len);
  904.             // Recurse to next level of nesting and exit.
  905.             return this.shapeOf(this.mix(a), dim);}
  906.          }
  907.          return dim;
  908.       }
  909.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  910.    };
  911.    // Return elements in sub-array specified by index idx on second axis of a.
  912.    this.sliceByIdx = function(a, idx) {var self = {name:'sliceByIdx'};
  913.       // Verify a is array.
  914.       if (!this.validateArgs(self, [a], ['array'])) return null;
  915.       // If explicitly requested idx not valid, throw error.
  916.       if (idx > this.rankOf(a))
  917.          throw AplArraysException(
  918.                   null, self.name, 'IndexOutOfBoundsException', arguments
  919.                );
  920.       try {
  921.          // If not defined, set default value.
  922.          if ('undefined' == typeof idx) idx = 0;
  923.          // Initialize return.
  924.          var sliced = new Array();
  925.          // For each element on first axis of of a, capture value.
  926.          for (var i = 0; i < a.length; i++) {sliced.push(a[i][idx]);}
  927.          return sliced;
  928.       }
  929.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  930.    };
  931.    // Sort all elements of array a according to sort order of elements in sub-
  932.    // array at index idx on second axis of a.
  933.    this.sortByIdx = function(a, idx, descend) {var self = {name:'sortByIdx'};
  934.       // Verify a is array.
  935.       if (!this.validateArgs(self, [a], ['array'])) return null;
  936.       // If idx points to axis not defined for a, throw error.
  937.       if (idx > this.rankOf(a))
  938.          throw AplArraysException(
  939.                   null, self.name, 'IndexOutOfBoundsException', arguments
  940.                );
  941.       try {
  942.          // Set default values, if none given.
  943.          if ('undefined' == typeof idx) idx = 0;
  944.          if ('undefined' == typeof descend) descend = false;
  945.          // Create new Array to hold String versions of contents of a.
  946.          var sortable = new Array();
  947.          // Load String versions with index values attached as least significant
  948.          // substring for sorting process.
  949.          for (var i = 0; i < a.length; i++) {
  950.             sortable[i] = padLeft(a[i][idx],6) + '#' + padLeft(['' + i],6);
  951.          }
  952.          // Sort contents ascending by Unicode collating sequence.
  953.          sortable.sort();
  954.          // Initialize return.
  955.          var newA = new Array();
  956.          // For each element of a, add to return in calculated index position.
  957.          for (var i = 0; i < a.length; i++) {
  958.             var j = parseInt(
  959.                        sortable[i].substring(1 + sortable[i].indexOf('#'))
  960.                     );
  961.             newA[i] = a[j];
  962.          }
  963.          // If requested order was descending, reverse elements on first axis.
  964.          return (descend)?this.mirror(newA):newA;
  965.       }
  966.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  967.    };
  968.    // Return len sequential elements of a on first axis. len > 0 ==> take
  969.    // elements from beginning...len < 0 ==> take element from end. If len
  970.    // greater than a.length, pad a with type-specific elements, i.e., blanks for
  971.    // Strings or zeros for Numbers.
  972.    // APL: len ↑ a
  973.    this.take = function(a, len) {var self = {name:'take'};
  974.       try {
  975.          // Need to dissociate a, since push() and unshift() would change a.
  976.          var newA = this.isScalar(a)?[a]:this.clone(a);
  977.          // If non-negative len...
  978.          if (0 <= len) {
  979.             // ...calculate difference between requested len and length of a.
  980.             var last = len - newA.length;
  981.             // ...if more requested than contained in a, need to extend a...
  982.             if (0 < last) {
  983.                // ...create filler element by type elements of a.
  984.                var x = (this.isString(newA[0]))?' ':0;
  985.                // ...add instances of x to end of a as needed to produce length.
  986.                for (var i = 0; i < last; i++) {newA.push(x);}
  987.             }
  988.             // Otherwise, just keep first len elements.
  989.             else newA = this.scatterIndex(newA, this.index(len));
  990.          }
  991.          // Otherwise (negative len) keep values from right-hand end.
  992.          else {
  993.             len = -len;var last = len - newA.length;
  994.             // If, more requested than contained in a, need to extend a.
  995.             if (0 < last) {
  996.                // If a is String, extend with single, blank strings - else, 0's.
  997.                var x = (this.isString(newA[0]))?' ':0;
  998.                // Add instances of x to front of a as needed to produce length.
  999.                for (var i = 0; i < last; i++) {newA.unshift(x);}
  1000.             }
  1001.             // Otherwise, just keep last len elements.
  1002.             else newA = this.scatterIndex(newA, this.index(len, -last));
  1003.          }
  1004.          return newA;
  1005.       }
  1006.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  1007.    };
  1008.    // Convert first element of array a (any shape) to scalar.
  1009.    // APL: '' ⍴ 1 ↑ , a
  1010.    this.toScalar = function(a) {var self = {name:'toScalar'};
  1011.       // Convert a to rank-1 Array (vector) and return value of 0th element.
  1012.       try {return (this.ravel(a))[0];}
  1013.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  1014.    };
  1015.    // Return a with rows and columns transposed (for rank-1 or -2 array a).
  1016.    // APL: ⍉ a
  1017.    // NOTE: rank-1 (row vector) array will be promoted to rank-2 (1 column
  1018.    // array).
  1019.    this.transpose = function(a) {var self = {name:'transpose'};
  1020.       // Verify a is homogeneous array.
  1021.       if (!this.validateArgs(self, [a], ['array', 'homogeneous'])) return null;
  1022.       // If not rank-1 or rank-2, throw error, unless rank-3 and all elements of
  1023.       // a are strings.
  1024.       if ( 2 < this.rankOf(a) &&
  1025.           !(3 == this.rankOf(a) &&
  1026.           allStrings(this.ravel(a)))
  1027.          )
  1028.          throw AplArraysException(null, self.name, 'RankException', arguments);
  1029.       try {
  1030.          // Initialize return.
  1031.          var newA = new Array();
  1032.          // Record shape and rank of a.
  1033.          var s = this.shapeOf(a);var r = this.rankOf(a);
  1034.          // If a has more than one axis...
  1035.          if (1 < r) {
  1036.             // ...number of columns is second element of shape of a.
  1037.             var cols = (this.shapeOf(a))[1];
  1038.             // ...add columns of a as rows to return.
  1039.             for (var i = 0; i < cols; i++) {
  1040.                newA.push(this.sliceByIdx(a, i));
  1041.             }
  1042.          }
  1043.          // Otherwise, for rank-1 row vector...
  1044.          else {
  1045.             // ...number of columns will be length of a - return will be a one
  1046.             // column, rank-2 array.
  1047.             var cols = a.length;
  1048.             // ...add each element of a with an additional level of nesting.
  1049.             for (var i = 0; i < cols; i++) {
  1050.                newA.push([a[i]]);
  1051.             }
  1052.          }
  1053.          return newA;
  1054.       }
  1055.       catch (e) {AplArraysException(e, self.name, null, arguments);}
  1056.    };
  1057.    // protected access to private variables
  1058.    this.getObjectName = function() {return objName;};
  1059.    this.getObjectType = function() {return objType;};
  1060.  
  1061.    // Validation for arguments and tests that can be expressed simply. Failures
  1062.    // throw corresponding AplArraysException types from here without actually
  1063.    // returning to caller (leaves calling stack pendent at point of exception).
  1064.    this.validateArgs = function(caller, args, tests) {
  1065.       var x = null;
  1066.       for (var j = 0; j < tests.length; j++) {
  1067.          switch (tests[j]) {
  1068.             // Each element of args must be Array.
  1069.             case 'array': {
  1070.                for (var i = 0; i < args.length; i++) {
  1071.                   if (!this.isArray(args[i])) {
  1072.                      x = 'NonArrayArgumentException';
  1073.                      break;
  1074.                   }
  1075.                }
  1076.                if (null != x) break;
  1077.                else continue;
  1078.             }
  1079.             // All elements of args must be Boolean.
  1080.             case 'boolean': {
  1081.                for (var i = 0; i < args.length; i++) {
  1082.                   if (!allBooleans(this.ravel(args[i]))) {
  1083.                      x = 'InvalidArgumentTypeException';
  1084.                      break;
  1085.                   }
  1086.                }
  1087.                if (null != x) break;
  1088.                else continue;
  1089.             }
  1090.             // All elements of args must be homogeneous.
  1091.             case 'homogeneous': {
  1092.                for (var i = 0; i < args.length; i++) {
  1093.                   if (!this.isHomogeneous(args[i])) {
  1094.                      x = 'NonHomogeneousArrayException';
  1095.                      break;
  1096.                   }
  1097.                }
  1098.                if (null != x) break;
  1099.                else continue;
  1100.             }
  1101.             // Lengths of args must the same.
  1102.             case 'length': {
  1103.                if (args[0].length != args[1].length) {
  1104.                   x = 'MismatchedArgumentLengthsException';
  1105.                   break;
  1106.                }
  1107.                else continue;
  1108.             }
  1109.             // Shapes of args must the same.
  1110.             case 'shape': {
  1111.                if (!this.isConformable(args[0], args[1])) {
  1112.                   x = 'NonConformableArgumentsException';
  1113.                   break;
  1114.                }
  1115.                else continue;
  1116.             }
  1117.             // args must be vectors.
  1118.             case 'vector': {
  1119.                for (var i = 0; i < args.length; i++) {
  1120.                   if (!this.isVector(args[i])) {
  1121.                      x = 'InvalidArgumentTypeException';
  1122.                      break;
  1123.                   }
  1124.                   else continue;
  1125.                }
  1126.                if (null != x) break;
  1127.                else continue;
  1128.             }
  1129.          }
  1130.       }
  1131.       var validated = null == x;
  1132.       if (!validated) throw AplArraysException(null, caller.name, x, args);
  1133.       return validated;
  1134.    };
  1135.  
  1136.    // private functions - - - - - - - - - - - - - - -
  1137.    //
  1138.    // Return Boolean assessment of whether all elements of vector a are type
  1139.    // Boolean. apl is (optional) Boolean indicator of whether to verify as JS or
  1140.    // APL Booleans: true ==> treat as APL-style 1/0 Booleans - false ==> treat
  1141.    // as JS-style true/false Booleans
  1142.    function allBooleans(a, apl) {var self = {name:'allBooleans'};
  1143.       // Set default by examining first element, if not given.
  1144.       if ('undefined' == typeof apl) apl = 'boolean' != typeof a[0];
  1145.       // Initialize assessment for logical ANDing.
  1146.       var allAplBool = apl; var allJsBool = !apl;
  1147.       for (var i = 0; i < a.length; i++) {
  1148.          if (apl)
  1149.             allAplBool = allAplBool &&
  1150.             ('boolean' != typeof a[i]) && ((0 == a[i] || 1 == a[i]));
  1151.          else allJsBool = allJsBool && 'boolean' == typeof a[i];
  1152.       }
  1153.       return (apl)?allAplBool:allJsBool;
  1154.    };
  1155.    // Return Boolean assessment of whether all elements of vector a are type
  1156.    // String.
  1157.    function allStrings(a) {var self = {name:'allStrings'};
  1158.       for (var i = 0; i < a.length; i++)
  1159.          {if ('string' != typeof a[i]) return false;}
  1160.       return true;
  1161.    };
  1162.    // Return Boolean assessment of whether all elements of vector a are type
  1163.    // Number.
  1164.    function allNumbers(a) {var self = {name:'allNumbers'};
  1165.       for (var i = 0; i < a.length; i++)
  1166.          {if ('number' != typeof a[i]) return false;}
  1167.       return true;
  1168.    };
  1169.    // Convert Boolean vectors between APL and JS equivalents.
  1170.    // apl is (optional) Boolean indicator of whether to convert to APL Booleans:
  1171.    // true ==> convert JS-style to APL-style 1/0 Booleans - false ==> convert
  1172.    // APL-style to JS-style true/false Booleans
  1173.    function convertBooleans(b, apl) {var self = {name:'convertBooleans'};
  1174.       // Set default by examining first element, if not given.
  1175.       if ('undefined' == typeof apl) apl = 'boolean' != typeof b[0];
  1176.       // Initialize return.
  1177.       var newB = new Array();
  1178.       // If converting to APL-style...
  1179.       if (apl) {
  1180.          // If all values are JS-style, convert JS to APL.
  1181.          if (allBooleans(b),true)
  1182.             for (var i = 0; i < b.length; i++) {newB.push(b[i]?1:0);}
  1183.          // Otherwise, throw error.
  1184.          else throw AplArraysException(
  1185.             null, self.name, 'InvalidArgumentTypeException', arguments
  1186.          );
  1187.       }
  1188.       // Otherwise, converting to JS-style...if all are APL-style.
  1189.       else {
  1190.          // If all values are APL-style, convert APL to JS.
  1191.          if (allBooleans(b, false))
  1192.             for (var i = 0; i < b.length; i++) {newB.push(1 == b[i]);}
  1193.          // Otherwise, throw error.
  1194.          else throw AplArraysException(
  1195.             null, self.name, 'InvalidArgumentTypeException', arguments
  1196.          );
  1197.       }
  1198.       return newB;
  1199.    };
  1200.    // Define object-specific exception handling for AplArrays.
  1201.    // NOTE: exceptions created this way are all Strings for the virtue of
  1202.    //       compound composition across potentially multiple levels of
  1203.    //       throw/catch among the function members of the AplArrays object.
  1204.    //
  1205.    // "checked" exception in the current context refers to the intentional
  1206.    //    assessment of bad states of calls, due to incorrect argument types,
  1207.    //    etc., i.e., member functions will assess invalid/inconsistent states
  1208.    //    and intentionally invoke throw.
  1209.    //
  1210.    // To throw for checked conditions, usage will be like:
  1211.    //      if (_condition_)
  1212.    //         throw AplArraysException(null, self.name, _exception_, arguments);;
  1213.    //      where:
  1214.    //         _condition_ is some detectable flaw in the state of the system -
  1215.    //            typically a flaw in the contents or structure of one of the
  1216.    //            arguments to a function
  1217.    //         self.name is a private property declared by the function on itself
  1218.    //         _exception_ is a String name that describes the particular
  1219.    //            exception, e.g., 'RankException'
  1220.    //       arguments is the arguments property of the function that threw the
  1221.    //          exception
  1222.    //
  1223.    // To throw for unchecked, runtime conditions, usage will be like:
  1224.    //      try {
  1225.    //         <function block>
  1226.    //      }
  1227.    //      catch (e) {AplArraysException(e, self.name, null, arguments);}
  1228.    //      where:
  1229.    //         e is the runtime exception caught and passed to this handler        
  1230.    //         self.name is a private property declared by the function object on
  1231.    //            itself
  1232.    //         arguments is the arguments property of the function that threw the
  1233.    //            exception
  1234.    //
  1235.    //   Checked (predefined, checkable) exception types specific to the
  1236.    //      AplArrays object are:
  1237.    //
  1238.    //      AxisException
  1239.    //         an indicated dimension (axis) is undefined for given Array object
  1240.    //      IndexOutOfBoundsException
  1241.    //         value of an index exceeds the extent of Array object on an
  1242.    //         indicated axis
  1243.    //      InvalidArgumentTypeException
  1244.    //         argument passed to function is an invalid type for the signature
  1245.    //      InvalidDepthException
  1246.    //         number of levels of nesting of Array is not sufficient to satisfy
  1247.    //         request
  1248.    //      MismatchedArgumentLengthsException
  1249.    //         Arrays that are required to have same length do not
  1250.    //      NonArrayArgumentException
  1251.    //         object required to be an Array is not
  1252.    //      NonConformableArgumentsException
  1253.    //         Arrays required to have conformable shape structure
  1254.    //         (conformability can vary by type of operation requested) do not
  1255.    //      NonHomogeneousArrayException
  1256.    //         Array required to be homogeneous is not
  1257.    //      RankException
  1258.    //         Array fails to meet rank (number of dimensions/axes) requirement
  1259.    //
  1260.    //   NOTE: there's a useful side-effect of the mixture of checked and
  1261.    //         unchecked exception handling in the APL-style base functions; if a
  1262.    //         checked exception is thrown from one of the base functions as a
  1263.    //         result of a call from some other base function or one of higher
  1264.    //         order functions, the exception reported to the console reveals the
  1265.    //         call stack; the checked type is reported as such, and the
  1266.    //         remaining stack is revealed as a series of reports of unchecked
  1267.    //         exceptions at all remaining stack frames; this can speed debugging
  1268.    //         by providing a set of candidates for watch- and break-points.
  1269.    //
  1270.    //   Arguments are:
  1271.    //      e........JavaScript native exception type for unchecked calls
  1272.    //      callee...name of function throwing exception
  1273.    //      msg......text of named exception type for checked exceptions - could
  1274.    //               be more as described in the note above
  1275.    //      args.....arguments array of function that threw the exception
  1276.    function AplArraysException(e, callee, msg, args) {
  1277.       var self = {name:'AplArraysException'};
  1278.       // If called with a null value for message (msg), this is a runtime error
  1279.       // or possibly a chain of exceptions from multiple frames that began with
  1280.       // a checked exception in some base function.
  1281.       if ('undefined' == typeof msg || null ==  msg) msg = 'runtime exception';
  1282.       // Name of function had the problem.
  1283.       var fn = '\nfunction ' + objName + '.' + callee;
  1284.       // Report this object's type.
  1285.       var type = ' (from object of type ' + objType + ')';
  1286.       // Initialize other possible reporting variables.
  1287.       var rArgs = '';var ex = '';var caller = '';
  1288.       // If this was a runtime exception, there's a JS value to report.
  1289.       if (null != e) ex = '\nerror reported was:\n' + e.toString();
  1290.       // Collect any arguments that were available to the caller.
  1291.       for (var i = 0; i < args.length; i++)
  1292.          {rArgs += '\nargs[' + i + '] = ' + args[i];}
  1293.       // Deprecated, but, if available, may help in analysis of stack.
  1294.       if (
  1295.          'undefined' != typeof args &&
  1296.          'undefined' != typeof args.callee &&
  1297.          'undefined' != typeof args.callee.caller &&
  1298.          null != args.callee.caller &&
  1299.          'undefined' != typeof args.callee.caller.name &&
  1300.          '' != args.callee.caller.name
  1301.       ) caller = '\nwhen called by ' + args.callee.caller.name;
  1302.       // Compose the collected information and rethrow.
  1303.       throw (fn + type + ' threw ' + msg + rArgs + caller + ex);
  1304.    };
  1305.    
  1306.    // private variables - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1307.    //
  1308.    //    mainly useful for exceptions handling, testing and debugging  
  1309.    // These values are set once - at object construction; they are accessible
  1310.    // after construction via public "getters" but have no "setter" (mutators).
  1311.    // name of an instance:
  1312.    // - can be set at construction like: var myArrays = new Arrays('myArrays');
  1313.    // - defaults to short, convenience name "axApl"
  1314.    //     (for a[rray e]x[tender]APL) in which case, construction should be:
  1315.    //     var aau = new Arrays() (if accurate error reporting required)
  1316.    var objName = ('undefined' != typeof objectName)?objectName:'axApl';
  1317.    // type of this object
  1318.    var objType = 'AplArrays';
  1319. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement