Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- Filename..........AplArrays.js - Version 0.1.0
- Purpose...........add support for APL-style Array operations not native to
- the base JavaScript Array object
- Author............RichardUIE (richarduie[at]yahoo[dot]com)
- Created On........2012-07-31
- Copyright (C) 2012 by richarduie
- This program is free software; you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free Software
- Foundation; either version 2 of the License, or (at your option) any later
- version. This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- NOTE: the strained artificiality of some line breaks and text flow satisfies
- the demand that the document width be no more than 80 characters.
- */
- function AplArrays(objectName)
- {
- /*
- Thinking about Arrays like an APL programmer for most function names (reduce,
- scan, take, drop etc.) and the nature of abstract array processes.
- Glossary:
- axis by analogy to algebraic arrays, a dimension on which an
- array has elements ("dimension" and "axis" are used
- interchangeably)
- homogeneous overall structure of an array is the same on all axes ; an
- homogeneous array has the same structure for every
- element on a particular axis. For example, the array
- A = [[1,2],[3,4],[5,6]] has two dimensions - one axis can
- be conceived as rows and the other as columns...
- representing it visually in this fashion:
- +-------+
- | 1 | 2 | each row contains a sub-array of two elements
- +-------+ each column contains a sub-array of three elements
- A = | 3 | 4 | the elements within each box are of the same structure,
- +-------+ i.e., scalar numbers in this example, and hence,
- | 5 | 6 | A is an homogeneous 3 x 2 array of numbers
- +-------+
- nest enclose as an array within an array; a nest can referenced
- by a single index into its parent, e.g., the array
- [1, [2, 3, 4], 5] has the nested element [2, 3, 4] at
- index 1
- rank number of axes (dimensions) of an array, e.g., a
- 2-dimensional array is said to be of rank 2
- vector array that is addressed only on its first axis; in that
- sense, all JavaScript arrays are vectors, i.e., higher-
- order JavaScript arrays are vectors of vectors.
- NOTE: as primitive elements of Arrays, all Strings are treated as having
- length 1 by convention.
- APL Primitive Function Symbols (for use in documentation):
- ÷ × ∊ ⍴ ~ ∆ ⍙ ¨ ⌿ ⍀ < ≤ = ≥ > ≠ ∨ ∧ ↑ ↓ ⍳ ⌈ ⌊ ∇ ⊂ ⊃ ⊥ ⊤ | ⍱ ⍲ ⍒ ⍋ ⍉ ⌽ ⊖ !
- ⍕ ⍎ ≡ ≢ ø ⍷ ← → ⎕
- NOTE: expression samples for APL usage reflect APL's Reverse Polish
- Notation ([Polish] Postfix Notation]).
- */
- // public functions - - - - - - - - - - - - - - - - - - -
- //
- // Return Boolean AND of values of a with values of b.
- // APL: a ∧ b
- // apl is (optional) Boolean indicator of whether incoming values and return
- // are JS- or APL-style Boolean values:
- // true ==> 0 for false and 1 for true - false (default) ==> JS-style
- // true/false values
- this.and = function(a, b, apl) {var self = {name:'and'};
- // If argument not given, set default value.
- if ('undefined' == typeof apl) apl = false;
- // Verify a and b are arrays of same shape and all elements are boolean.
- if (!this.validateArgs(self, [a,b], ['array','shape'])) return null;
- // Verify a and b are boolean of correct style (APL or JS).
- if (
- (apl && (!allBooleans(this.ravel(a), true) ||
- !allBooleans(this.ravel(b), true))) ||
- (!apl && (!allBooleans(this.ravel(a), false) ||
- !allBooleans(this.ravel(b), false)))
- ) throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- try {
- // Copy elements of a and b into simple vectors.
- var newA = this.ravel(a); var newB = this.ravel(b);
- // Initialize return.
- var c = new Array();
- // For each pair of elements of a and b, assess and record logical AND.
- for (var i = 0; i < newA.length; i++) {
- // Assess and assign according to requested style.
- c.push(apl?((1 == newA[i] && 1 == newB[i])?1:0):
- (newA[i] && newB[i]));
- }
- // Reshape c into same form as a (and b) and return.
- return this.reshape(c, this.shapeOf(a));
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return concatenation of vectors (one-dimensional array) a with b in that
- // order. a and b can be scalars or vectors.
- // APL: a , b
- this.catenate = function(a, b) {var self = {name:'catenate'};
- if ( (this.isScalar(a) || this.isVector(a)) &&
- (this.isScalar(b) || this.isVector(b))
- )
- throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- try {
- // If scalar a, convert it to vector.
- if (this.isScalar(a)) a = this.ravel(a);
- return a.concat(b);
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return copy of a (by value - no reference associations).
- // APL: b ← a (copula/assignment is by value in APL - no side effects)
- this.clone = function(a) { var self = {name:'clone'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {return this.scatterIndex(a, this.index(a.length));}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return elements of a that correspond to value of true in b by index. If b
- // is composed of zeros and ones, 0 = false and 1 = true.
- // APL: b / a OR b ⌿ a for rank-2+ array a
- this.compress = function(a, b) {var self = {name:'compress'};
- // Verify a and b are arrays of same length and all elements of b are
- // Boolean.
- if (!this.validateArgs(self, [a,b], ['array','length']) ||
- !this.validateArgs(self, [b], ['boolean'])
- ) return null;
- try {
- // If APL Booleans convert to JS equivalent.
- if (0 == b[0] || 1 == b[0]) b = convertBooleans(b);
- // Initialize return.
- var newA = new Array();
- // If element of b is true, keep corresponding element of a.
- for (var i = 0; i < a.length; i++) {if (b[i]) newA.push(a[i]);}
- return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Form "cross-product" of all elements of x and y by operation op, e.g.,
- // [1,2] op [3,4] ==> [1 op 3, 1 op 4, 2 op 3, 2 op 4]
- // APL: a op ¨ b
- this.crossVectors = function(a, b, op) {var self = {name:'crossVectors'};
- // Verify a and b are vectors.
- if (!this.validateArgs(self, [a,b], ['vector'])) return null;
- try {
- // Set default operation to {+} (add/concatenate), if not given.
- if ('undefined' == typeof op) op = '+';
- var c = new Array();
- // For each element of a...
- for (var i = 0; i < a.length; i++) {
- // ...for each element of b...
- for (var j = 0; j < b.length; j++) {
- // ...evaluate action of operation on current values of a an b.
- c.push(eval(a[i] + op + b[j]));
- }
- }
- return c;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return a.length - len elements of a on first axis.
- // len > 0 ==> drop elements from start...len < 0 ==> drop elements from end
- // APL: len ↓ a
- this.drop = function(a, len) {var self = {name:'drop'};
- // Verify a is an array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {
- // If |len| greater than length of a, return empty.
- if (len > a.length || -len > a.length) return [];
- // if len is positive, get last a.length - len elements of a -
- // remove/drop first len elements.
- if (0 > len) a = this.scatterIndex(a, this.index(a.length + len));
- // Otherwise, get last a.length - len values of a - remove/drop last
- // len elements.
- else a = this.mirror(this.scatterIndex(
- this.mirror(a), this.index(a.length - len))
- );
- return a;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return a with an additional level of nesting (enclosure as an array) of
- // entire a.
- // APL: a ⊂ b produces a scalar - this actually produces a singleton vector
- // like: , a ⊂ b (ravel a enclose b)
- this.enclose = function(a) {return [a];};
- // Return Boolean indicators of whether a and b are equal on an element-wise
- // basis. Return is array of same structure as arguments with true for
- // matches, and false for mismatches. If shapes don't match, returns -1. apl
- // is (optional) Boolean indicator of whether to return JS or APL.
- // Booleans: true ==> return 0 for false and 1 for true - false (default) ==>
- // return JS true/false values
- // APL: a = b
- this.equals = function(a, b, apl) {var self = {name:'equals'};
- try {
- // If argument not given, set default.
- if ('undefined' == typeof apl) apl = false;
- // If both are scalars, exit immediately, returning simple equals
- // assessment in either JS or APL terms.
- if (this.isScalar(a) && this.isScalar(b))
- return apl?((a == b)?1:0):a == b;
- // (implicit else) If a is scalar, and b is Array, convert a to Array.
- if (this.isScalar(a) && this.isArray(b))
- a = this.ravel(this.reshape(a, this.shapeOf(b)));
- // If b is scalar, and a is Array, convert b to Array.
- if (this.isArray(a) && this.isScalar(b))
- b = this.ravel(this.reshape(b, this.shapeOf(a)));
- // Record shape of a.
- var shape = this.shapeOf(a);
- // If shapes don't match, exit immediately, returning -1.
- if (!this.match(shape, this.shapeOf(b))) return -1;
- // (implicit else) Convert a and b to vectors by value.
- var newA = this.ravel(this.clone(a));
- var newB = this.ravel(this.clone(b));
- // Initialize return.
- var isEqual = new Array();
- // For each element of a (and b), assess equality by value.
- for (var i = 0; i < newA.length; i++) {
- isEqual.push(newA[i] == newB[i]);
- }
- // If APL-style Booleans requested, convert.
- if (apl) isEqual = convertBooleans(isEqual, true);
- return this.reshape(isEqual, shape);
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return array flipped top-to-bottom (row-order reversed for rank-2 array)
- // on first axis.
- // APL: ⊖ a
- this.flip = function(a) {var self = {name:'flip'};
- // Only valid, if a has at least rows and columns (rank-2 minimum)
- if (2 > this.rankOf(a))
- throw AplArraysException(null, self.name, 'RankException', arguments);
- // Return elements reversed on indexes of first axis.
- try {return this.scatterIndex(a, this.mirror(this.index(a.length)));}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return frequencies of unique elements of a.
- this.freq = function(a) {var self = {name:'freq'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {
- // Get sorted vector of values of primitive elements of a.
- var r = (this.ravel(this.clone(a))).sort();
- // Get unique values of r in serial order of occurrence (see nub() and
- // kernel() for possible exceptions about order of elements).
- var n = this.nub(r);
- // Get number of unique elements.
- var last = n.length;
- // Initialize frequency counts Array.
- var f = new Array();
- // For each unique element...
- for (var i = 0; i < last; i++) {
- // ...get total number of elements of a that match current r.
- f.push(this.reduce(this.equals(n[i],r,true)));
- }
- return this.reshape(this.catenate(n,f),[2,last]);
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return integer vector of length n, starting at io (index origin -
- // hypothetically 0 or 1 in APL terms). returns [0,1,2,...,n-1] or
- // [1,2,3,...,n].
- // NOTE: current implementation can also (sort of mis)use io to generate
- // integer vectors of form [io, io+1, io+2,..., io+n-1] for io not 0 or 1.
- // APL: ⍳ n (with ⎕IO, index origin, sensitivity)
- this.index = function(n, io) {var self = {name:'index'};
- // If n is not an integer greater than zero, throw error.
- if (n < 0 || n != Math.floor(n))
- throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- try {
- // If index-origin not given, set default of 0.
- if ('undefined' == typeof io) io = 0;
- // Set lower bound for iteration.
- var last = io + n;
- // Initialize return.
- var idx = new Array();
- // Set value.
- for (var i = io; i < last; i++) {
- idx.push(i);
- }
- return idx;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return integer vector of index positions of first occurrences of elements
- // of vector b in vector a, starting at io (index origin - hypothetically 0
- // or 1 in APL terms). If element not found, value of it index is one greater
- // than greatest index of a.
- // APL: a ⍳ b (dyadic ⍳ with ⎕IO, index origin, sensitivity)
- this.indexOf = function(a, b, io) {var self = {name:'indexOf'};
- // If index origin not defined, set default value (same for APL and JS,
- // i.e., 0).
- if ('undefined' == typeof io) io = 0;
- // Extend b to singleton vector, if needed.
- if(this.isScalar(b)) b = this.ravel(b);
- // Verify a and b are arrays.
- if (!this.validateArgs(self, [a,b], ['array'])) return null;
- // If io not zero or one, throw error.
- if (0 != io && 1 != io)
- throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- try {
- // How many elements in b.
- var last = b.length;
- // Initialize return.
- var idxs = new Array();
- // For each element in b...
- for (var i = 0; i < last; i++) {
- // ...set current value of index to take io into account.
- var idx = a.length + io;
- // ...for each element of a...
- for (var j = 0; j < a.length; j++) {
- // ...if current element of b matches current a, set index and...
- // ...exit loop - limited search-&-destroy.
- if (this.match(b[i], a[j])) {
- idx = j + io;
- break;
- }
- }
- // ...capture value.
- idxs.push(idx);
- }
- return idxs;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Form inner product of rank-2 arrays a and b, using operations op and op2
- // (default to + and *) number of columns of a must be same that of of b.
- // Return is rank-2 array with number of rows of a and number of columns as
- // the number of rows b with all elements of rows of a combined element-wise
- // with corresponding (by index) elements of rows of b, using op2 - then
- // those compositions reduced by op.
- // APL: a op . op2 b
- // NOTE: in matrix-theory/linear-algebra, this is the standard inner product,
- // although conformability would require columns of a and rows of b to match
- // by length; here rows and rows must match by length, i.e., the current
- // implementation treats rows of b as columns in the linear algebraic sense.
- // If you know linear algebra, the transpose of b would be the right-hand
- // argument.
- // NOTE: a variety of interesting and possibly unexpected results are
- // available from this, e.g., || with != identifies whether any elements of
- // the rows of a differ from those of the elements of the rows of b, e.g.,
- // innerProduct([[1,2],[3,4]],[[1,2],[3,4]], '||','!=') ==>
- // [
- // [((1 != 1) || (2 != 2)),((1 != 3) || (2 != 4))],
- // [((3 != 1) || (4 != 2)),((3 != 3) || (4 != 4))]
- // ] ==>
- // [ [(false||false),(true||true)], [(true||true),(false||false)] ] ==>
- // [ [false, true], [true,false] ]
- this.innerProduct = function(a, b, op, op2) {
- // If either is vector, nest one level deeper.
- if (this.isVector(a)) a = this.enclose(a);
- if (this.isVector(b)) b = this.enclose(b);
- // Verify a and b are arrays.
- if (!this.validateArgs(self, [a,b], ['array'])) return null;
- // Column length of a must equal that of b - if not throw error.
- if (!this.validateArgs(self, [a[0],b[0]], ['length'])) return null;
- // If not given, set default operations to give arithmetic inner product.
- if ('undefined' == typeof op) op = '+';
- if ('undefined' == typeof op2) op2 = '*';
- // Calls first axis of a ("rows") and first of b ("cols").
- var rows = this.shapeOf(a)[0]; var cols = this.shapeOf(b)[0];
- // Initialize return.
- var p = new Array();
- // For each row-vector of a...
- for (var i = 0; i < rows; i++) {
- // ...for each column-vector (actually also row) of b...
- for (var j = 0; j < cols; j++) {
- // ...generate op2-product of elements of a and b - then op-reduce.
- p.push(this.reduce(this.op(a[i], b[j], op2), op));
- }
- }
- return this.reshape(p, [rows,cols]);
- };
- // Return Boolean indicator of whether a is an Array.
- this.isArray = function(a) {var self = {name:'isArray'};
- try {return -1 != a.constructor.toString().indexOf('Array');}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a an b are homogeneous and (shape-
- // wise) conformable.
- this.isConformable = function(a, b) {var self = {name:'isConformable'};
- try {
- return this.isHomogeneous(a) && this.isHomogeneous(b) &&
- this.equals(this.rankOf(a), this.rankOf(b)) &&
- this.reduce(this.equals(this.shapeOf(a), this.shapeOf(b)), '&&');
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a is homogeneous. If a is empty,
- // return true.
- this.isHomogeneous = function(a) {var self = {name:'isHomogeneous'};
- try {return -1 != this.shapeOf(a);}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether all elements at outermost level are of
- // same length.
- this.isLevelHomogeneous = function(a) {var self = {name:'isLevelHomogeneous'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {
- // Declare Boolean state of whether a homogeneous on current axis in
- // block scope
- var isLvlHomo;
- // For each element of a on first axis...
- for (var i = 0; i < a.length; i++) {
- // ...homogeneity obtains, if all are Strings.
- if (this.isString(a[0])) isLvlHomo = this.isString[i];
- // ...or homogeneity obtains, if all are of same length.
- else isLvlHomo = this.isScalar(a[i]) || a[0].length == a[i].length;
- // ...limited search-&-destroy...stop checking, if any fails.
- if (!isLvlHomo) break;
- }
- return isLvlHomo;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a is scalar.
- // APL: 0 = ⍴ ⍴ a
- this.isScalar = function(a) {var self = {name:'isScalar'};
- try {return (this.isArray(a))?false:a == (this.ravel(a))[0];}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a is a String.
- this.isString = function(a) {var self = {name:'isString'};
- try {return -1 != a.constructor.toString().indexOf('String');}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a is vector (rank-1, i.e.,
- // 1-dimensional array).
- // APL: 1 = ⍴ ⍴ a
- this.isVector = function(a) {var self = {name:'isVector'};
- try {return 1 == this.rankOf(a);}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return unique elements of a on first dimension. If scalar a, return a.
- // APL: extends idiom {(( a ⍳ a ) = ⍳ ⍴ a ) / a} to multi-dimensional arrays
- // NOTE: does not guarantee that elements of return will be in original
- // serial order (as is the case with the nub idiom), since toString()
- // operations on array elements may not go into a JS associative Array in an
- // order that preserves that serial order of a for later retrieval under the
- // {for (in)} enumeration applied here.
- this.kernel = function(a) {var self = {name:'kernel'};
- try {
- // Set hashtable style associative array working storage.
- var r = new Array();
- // For each element in a...
- for (var i = 0; i < a.length; i++) {
- // ...assign key/value pair - repeated values have same key and
- // replace one another, leaving only single instance of value.
- r[a[i].toString()] = a[i];
- }
- // Initialize return.
- var k = new Array();
- // For each key in r...
- for (var el in r) {
- // ...set value into index-oriented array.
- k.push(r[el]);
- }
- return k;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return row or column-wise concatenation of multi-dimension arrays a and b
- // in that order. axis specifies dimension on which lamination is performed
- // as: 0 ==> (default) a ON b (row lamination) - 1 ==> a BY b (column
- // lamination).
- // APL: a , [n] b
- this.laminate = function(a, b, axis) {var self = {name:'laminate'};
- // If axis argument not given, set default value to row(s).
- if ('undefined' == typeof axis) axis = 0;
- // Initialize return.
- var l = new Array();
- // If requested axis is invalid, throw error.
- if (0 != axis && 1 != axis)
- throw AplArraysException(null, self.name, 'AxisException', arguments);
- try {
- // If axis requested is "row"...
- if (0 == axis) {
- // Copy a and b by value to avoid side effects.
- var newA = this.clone(a);var newB = this.clone(b);
- // Extend a or b as needed to provide shape conformability.
- if (1 == this.rankOf(a))
- newA = this.reshape(newA, [1,this.shapeOf(a)]);
- if (1 == this.rankOf(b))
- newB = this.reshape(newB, [1,this.shapeOf(b)]);
- // Record shapes of a and b.
- var sA = this.shapeOf(newA); sB = this.shapeOf(newB);
- // If not shape-wise conformable below level of first axis, quit
- // immediately, returning -1.
- if (!this.match(this.drop(sA,1),this.drop(sB,1))) return -1;
- // (implicit else) Set new shape of intermediate result.
- var dim = this.catenate([sA[0] + sB[0]], this.drop(sA,1));
- // Reshape concatenated results.
- l = this.reshape(
- this.catenate(this.ravel(newA),this.ravel(newB)),dim
- );
- }
- // Otherwise, axis requested is "column"...
- else {
- // If a and b are vectors, combine into rank-2 array.
- if (1 == this.rankOf(a) && 1 == this.rankOf(b))
- for (var i = 0; i < a.length; i++ ) {
- l.push([a[i], b[i]]);
- }
- // Otherwise, transpose, recurse, and (un)transpose.
- else l = this.transpose(
- this.laminate(this.transpose(a),this.transpose(b))
- );
- }
- return l;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean indicator of whether a and b are equal structurally and
- // element-wise by values.
- // APL: a ≡ b
- this.match = function(a, b) {var self = {name:'match'};
- try {
- // If a and b are Arrays of same length on first axis...
- if (this.isArray(a) && this.isArray(b) && a.length == b.length) {
- // ...declare logical state of matching detection to block scope.
- var isMatch;
- // ...for each element pair of a and b...
- for (var i = 0; i < a.length; i++) {
- // ...recursively update state of match detection.
- isMatch = this.match(a[i], b[i]);
- // ...limited search-&-destroy - quit for detection of unmatched.
- if (!isMatch) break;
- }
- return isMatch;
- }
- else return a == b;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return maximum of elements of numerical array a.
- // APL: ⌈ / a
- this.max = function(a) {var self = {name:'max'};
- // Return first element of reversed items when sorted ascending.
- try {return (this.mirror((this.ravel(a)).sort()))[0];}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return arithmetic mean (average) of elements of numerical array a.
- // APL: ( + / a ) ÷ ⍴ a
- this.mean = function(a) {var self = {name:'mean'};
- // Plus-reduce elements and divide by number of elements,
- try {var r = this.ravel(a);return this.reduce(r)/r.length;}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return minimum of elements of numerical array a.
- // APL: ⌊ / a
- this.min = function(a) {var self = {name:'min'};
- // Return first element of items when sorted ascending.
- try {return ((this.ravel(a)).sort())[0];}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Reverse left-right order of elements of a on first axis. For scalar a,
- // simply return a.
- // APL: ⌽ a
- this.mirror = function(a) {var self = {name:'mirror'};
- try {
- // Return a immediately, if scalar.
- if (this.isScalar(a)) return a;
- // (implicit else) Initialize return.
- var rev = new Array();
- // Set elements of a into rev in reverse order on first axis.
- for (var i = 0; i < a.length; i++) {rev.unshift(a[i]);}
- return rev;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Remove depth levels of nesting from a.
- // APL: ⊃ a (applied depth times)
- this.mix = function(a, depth) {var self = {name:'mix'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- // If depth is given but beyond nesting levels of a, throw error.
- if ('undefined' != typeof depth && (depth < 0 || depth > this.rankOf(a)))
- throw AplArraysException(
- null, self.name, 'InvalidDepthException', arguments
- );
- try {
- // Set default value, if not given.
- if ('undefined' == typeof depth) depth = 0;
- // Set default value, if not given.
- if ('undefined' == typeof newA) var newA = new Array();
- // For each element of a, add to newA.
- for (var i = 0; i < a.length; i++) {newA = newA.concat(a[i]);}
- // If more level to be disclosed, recurse.
- if (0 < depth) return this.mix(newA, depth - 1);
- // Otherwise, return newA.
- else return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // For an homogeneous array of Booleans, return the negations of all values.
- // Values can be given as JS true/false or APL-style 1/0, and return will
- // contain negations of same style.
- // APL: ~ a
- this.not = function(a) {var self = {name:'not'};
- // Verify a is homogeneous (and) array.
- if (!this.validateArgs(self, [a], ['array', 'homogenous'])) return null;
- // Record original shape.
- var s = this.shapeOf(a);
- // Remove all internal nesting and clone.
- var notA = this.clone(this.ravel(a));
- // If elements are APL-style true/false, negate that way.
- if (allBooleans(notA, true)) {
- for (var i = 0; i < notA.length; i++) {notA[i] = (1 + notA[i])%2;}
- }
- // Otherwise, if elements are JS-style true/false, negate that way.
- else if (allBooleans(notA)) {
- for (var i = 0; i < notA.length; i++) {notA[i] = !notA[i];}
- }
- // Otherwise, throw error.
- else throw InvalidArgumentTypeException;
- return this.reshape(notA, s);
- };
- // Return unique elements of a in order encountered on first axis.
- // APL: (( a ⍳ a ) = ⍳ ⍴ a ) / a (for vectors)
- // NOTE: unlike APL nub idiom, if kernel() is called, the serial order of the
- // elements of the return is not guaranteed to be that of the original serial
- // order of those (nub) elements as they occur in a.
- this.nub = function(a) {var self = {name:'nub'};
- // If scalar a, exit immediately, returning a.
- if (this.isScalar(a)) return a;
- try {
- // If rank-1 Array, use classic idiom and return immediately.
- if (this.isVector(a))
- return this.compress(
- a, this.ravel(
- this.equals(this.indexOf(a, a), this.index(a.length))
- )
- );
- // Otherwise (higher-order Array), call alternate method.
- else return this.kernel(a);
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return result of applying op to element-pairs of a and b index-wise. op is
- // some JS operation enclosed in quotes, e.g., '*'.
- // APL: a op b (for shape-wise conformable arrays)
- this.op = function(a, b, op) {var self = {name:'op'};
- // Verify a and b are arrays of same shape.
- if (!this.validateArgs(self, [a,b], ['array', 'shape'])) return null;
- try {
- // If op not given, set default (add/concatenate).
- if ('undefined' == typeof op) op = '+';
- // Reshape both to simple, rank-1 vectors.
- var newA = this.ravel(a);var newB = this.ravel(b);
- // For each item pair...
- for (var i = 0; i < newA.length; i++) {
- // ...capture evaluation of operation on current element-pair.
- eval('newA[i] = newA[i] ' + op + ' newB[i]');
- }
- // Reshape to that of original and return.
- return this.reshape(newA, this.shapeOf(a));
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return Boolean OR of values of a with values of b.
- // APL: a ∨ b
- // apl is (optional) Boolean indicator of whether to return JS or APL
- // Booleans: true ==> return 0 for false and 1 for true - false (default) ==>
- // return JS true/false values
- this.or = function(a, b, apl) {var self = {name:'or'};
- // Verify a and b are arrays of same shape.
- if (!this.validateArgs(self, [a,b], ['array', 'shape'])) return null;
- try {
- // If argument not given, set default.
- if ('undefined' == typeof apl) apl = false;
- var newA = this.ravel(a); var newB = this.ravel(b);
- var all = true;
- // If 1/0 truth values requested, verify that both a and b have them
- if (apl) all = allBooleans(newA, apl) && allBooleans(newB, apl);
- // Otherwise, verify that both a and b have JS Booleans
- else all = allBooleans(newA, false) && allBooleans(newB, false);
- if (!all)
- throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- var c = new Array();
- for (var i = 0; i < newA.length; i++) {
- c.push(apl?(1 == newA[i] || 1 == newB[i]):(newA[i] || newB[i]));
- }
- return this.reshape(c, this.shapeOf(a));
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return rank (number dimensions) of homogeneous a. If a is not homogeneous,
- // return -1.
- // APL: ⍴ ⍴ a (literally, this is the shape of the shape)
- this.rankOf = function(a) {var self = {name:'rankOf'};
- try {
- // If scalar, exit immediately, returning 0.
- if (this.isScalar(a)) return 0;
- // Otherwise, return shape of shape.
- return (-1 != this.shapeOf(a))?this.shapeOf(this.shapeOf(a)):-1;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Remove nesting of multi-dimensionsional array and return vector of all
- // elements of a from top-to-bottom, left-to-right, etc. - also converts
- // scalar to singleton vector.
- // APL: , a
- this.ravel = function(arg, flat) {var self = {name:'ravel'};
- // If flat - accumulator array - not yet defined, create it.
- if ('undefined' == typeof flat) flat = new Array();
- try {
- // If arg is an array, iterate over the elements in index order.
- if (this.isArray(arg)) {
- // Call self recursively to get elements or process next, outermost
- // level of nesting.
- for (var i = 0; i < arg.length; i++) {this.ravel(arg[i],flat);}
- }
- // Otherwise, just add simple element to accumulator.
- else flat.push( arg );
- // Return accumulated values.
- return flat;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Insert operator op between each element of a and evaluate expression
- // (default is +). op is a JS operation enclosed in quotes, e.g., '=='.
- // APL: op / a
- this.reduce = function(a, op) {var self = {name:'reduce'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {
- // Set default op (add/concatenate), if not given.
- if ('undefined' == typeof op) op = '+';
- // Initialize command to evaluate.
- var cmd = '';
- // Compose command string - concatenate each element with op.
- for (var i = 0; i < a.length; i++) {cmd += a[i] + op;}
- // Remove, extraneous, trailing instance of op and return evaluation.
- return eval(cmd.substring(0, cmd.length - op.length));
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Reshape a according to the dimensions in dim. If number of elements
- // required is greater than the number of elements in a, repeat the elements
- // of a as often as needed to fill required shape.
- // APL: dim ⍴ a
- this.reshape = function(a, dim, isRecurse) {var self = {name:'reshape'};
- try {
- // If not recursing.
- if ('undefined' == typeof isRecurse) {
- // If dim is scalar, convert to singleton vector.
- if (this.isScalar(dim)) dim = this.ravel(dim);
- // Convert values of a to rank-1 array (vector).
- var newA = this.ravel(this.isScalar(a)?a:this.clone(a));
- // How many elements required in all to fill dimensions.
- var len = this.reduce(dim,'*');
- // Repeat values of a to at least number required.
- while (newA.length < len) {newA = this.catenate(newA, newA)};
- // Take as many as need to fit total number required.
- newA = this.take(newA, len);
- // Recurse to do actual work.
- return this.reshape(newA, dim, true);
- }
- // Otherwise, build the required structure.
- else {
- // Number of elements required on last dimension.
- var len = this.take(dim, -1);
- // Length of blocks of a to fill current level.
- var last = Math.round(a.length/len);
- // Copy values of a.
- var newA = this.clone(a);
- // Initialize raw return.
- var newB = new Array();
- // For each block of elements of a...
- for (var i = 0; i < last; i++) {
- // ...capture len elements from a for this block.
- newB.push(this.take(this.drop(newA,i*len), len));
- }
- // If not at last dimension, recurse - otherwise, remove outermost
- // level of nesting.
- return (1 < dim.length)?
- this.reshape(newB, this.drop(dim, -1),true):
- this.mix(newB);
- }
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Apply scalar s to each element of a using operation op (default is +). If
- // (optional) rev(erse) is true, apply op as {s op a[i]} for each i - if rev
- // is false (default), apply op as {a[i] op s}
- // APL: a op s
- this.scalarOp = function(a, s, op, rev) {var self = {name:'scalarOp'};
- // If arguments of wrong types, throw error.
- if (!this.isScalar(s) || !this.isArray(a))
- throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- try {
- // Set defaults, if not given.
- if ('undefined' == typeof op) op = '+'; if ('undefined' == typeof rev) rev = false;
- // Create conformable array with shape of a with s for all elements.
- var sa = this.reshape(this.ravel(s), this.shapeOf(a));
- // Call op() with argument order dependent on rev.
- return rev?this.op(sa,a,op):this.op(a,sa,op);
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Reduce each index-wise subset of a, according to operation op (default is
- // +), i.e., subsets of elements of a are drawn by indexes like
- // {0}, {0,1}, {0,1,2}...{0,1,2,...,n}. op is a JS operation enclosed in
- // quotes, e.g., '*'.
- // APL: op \ a
- this.scan = function(a, op) {var self = {name:'scan'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- try {
- // If not given, set to default (add/concatenate).
- if ('undefined' == typeof op) op = '+';
- // Initialize return.
- var newA = new Array();
- // For each element of a...
- for (var i = 1; i <= a.length; i++) {
- // ...capture reduction of subsets of elements from first (index 0)
- // to current index.
- newA.push(this.reduce(this.scatterIndex(a, this.index(i)), op));
- }
- return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return elements of a with index positions in (vector or scalar) idxs on
- // first axis of a in the order given by idxs.
- // APL: a[idxs] (for vectors)
- this.scatterIndex = function (a, idxs) {var self = {name:'scatterIndex'};
- // If idxs is scalar, convert to singleton vector.
- if (this.isScalar(idxs)) idxs = this.ravel(idxs);
- try {
- // Initialize return.
- var newA = new Array();
- // For each element of idxs, add corresponding element to return.
- for (var i = 0; i < idxs.length; i++) {newA.push(a[idxs[i]]);}
- return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return shape of homogeneous a - if not homogeneous, return -1. Shape is a
- // vector of the length on each axis in axis order, e.g., shape of
- // [1,2,3] is [3], shape of [[1,2,3],[4,5,6]] is [2,3].
- // APL: ⍴ A
- this.shapeOf = function(a, dim) {var self = {name:'shapeOf'};
- // If a has no length, exit immediately, returning [0].
- if ('undefined' != typeof a &&
- 'undefined' != typeof a.length &&
- 0 == a.length
- ) return [0];
- // If this is not array, exit immediately, returning empty.
- if (!this.isArray(a)) return '';
- // If not yet defined (not yet recursing), initialize return.
- if ('undefined' == typeof dim) dim = new Array();
- try {
- // If in first recursion.
- if (0 == dim.length) {
- // Add length of a on first axis.
- dim.push(a.length);
- // If String, return current dim.
- if (this.isString(a[0])) return dim;
- // Assess length of current element (could be undefined).
- var len = a[0].length;
- // If all elements homogeneous at this level.
- if (this.isLevelHomogeneous(a)) {
- // If length is defined, update dim with this length.
- if ('undefined' != typeof len) dim.push(len);
- // Recurse to next nesting level.
- return this.shapeOf(this.mix(a), dim);
- }
- // Otherwise, return -1 to indicate a is not homogeneous.
- else return -1;
- }
- else {
- // Get length of a on next axis.
- var len = a[0].length;
- // If length does not exist, exit immediately, returning dim.
- if ('undefined' == typeof len) return dim;
- // If element is String, exit immediately, returning dim.
- if (this.isString(a[0])) return dim;
- // (implicit else) Capture length on current axis.
- if (this.isLevelHomogeneous(a)) {dim.push(len);
- // Recurse to next level of nesting and exit.
- return this.shapeOf(this.mix(a), dim);}
- }
- return dim;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return elements in sub-array specified by index idx on second axis of a.
- this.sliceByIdx = function(a, idx) {var self = {name:'sliceByIdx'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- // If explicitly requested idx not valid, throw error.
- if (idx > this.rankOf(a))
- throw AplArraysException(
- null, self.name, 'IndexOutOfBoundsException', arguments
- );
- try {
- // If not defined, set default value.
- if ('undefined' == typeof idx) idx = 0;
- // Initialize return.
- var sliced = new Array();
- // For each element on first axis of of a, capture value.
- for (var i = 0; i < a.length; i++) {sliced.push(a[i][idx]);}
- return sliced;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Sort all elements of array a according to sort order of elements in sub-
- // array at index idx on second axis of a.
- this.sortByIdx = function(a, idx, descend) {var self = {name:'sortByIdx'};
- // Verify a is array.
- if (!this.validateArgs(self, [a], ['array'])) return null;
- // If idx points to axis not defined for a, throw error.
- if (idx > this.rankOf(a))
- throw AplArraysException(
- null, self.name, 'IndexOutOfBoundsException', arguments
- );
- try {
- // Set default values, if none given.
- if ('undefined' == typeof idx) idx = 0;
- if ('undefined' == typeof descend) descend = false;
- // Create new Array to hold String versions of contents of a.
- var sortable = new Array();
- // Load String versions with index values attached as least significant
- // substring for sorting process.
- for (var i = 0; i < a.length; i++) {
- sortable[i] = padLeft(a[i][idx],6) + '#' + padLeft(['' + i],6);
- }
- // Sort contents ascending by Unicode collating sequence.
- sortable.sort();
- // Initialize return.
- var newA = new Array();
- // For each element of a, add to return in calculated index position.
- for (var i = 0; i < a.length; i++) {
- var j = parseInt(
- sortable[i].substring(1 + sortable[i].indexOf('#'))
- );
- newA[i] = a[j];
- }
- // If requested order was descending, reverse elements on first axis.
- return (descend)?this.mirror(newA):newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return len sequential elements of a on first axis. len > 0 ==> take
- // elements from beginning...len < 0 ==> take element from end. If len
- // greater than a.length, pad a with type-specific elements, i.e., blanks for
- // Strings or zeros for Numbers.
- // APL: len ↑ a
- this.take = function(a, len) {var self = {name:'take'};
- try {
- // Need to dissociate a, since push() and unshift() would change a.
- var newA = this.isScalar(a)?[a]:this.clone(a);
- // If non-negative len...
- if (0 <= len) {
- // ...calculate difference between requested len and length of a.
- var last = len - newA.length;
- // ...if more requested than contained in a, need to extend a...
- if (0 < last) {
- // ...create filler element by type elements of a.
- var x = (this.isString(newA[0]))?' ':0;
- // ...add instances of x to end of a as needed to produce length.
- for (var i = 0; i < last; i++) {newA.push(x);}
- }
- // Otherwise, just keep first len elements.
- else newA = this.scatterIndex(newA, this.index(len));
- }
- // Otherwise (negative len) keep values from right-hand end.
- else {
- len = -len;var last = len - newA.length;
- // If, more requested than contained in a, need to extend a.
- if (0 < last) {
- // If a is String, extend with single, blank strings - else, 0's.
- var x = (this.isString(newA[0]))?' ':0;
- // Add instances of x to front of a as needed to produce length.
- for (var i = 0; i < last; i++) {newA.unshift(x);}
- }
- // Otherwise, just keep last len elements.
- else newA = this.scatterIndex(newA, this.index(len, -last));
- }
- return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Convert first element of array a (any shape) to scalar.
- // APL: '' ⍴ 1 ↑ , a
- this.toScalar = function(a) {var self = {name:'toScalar'};
- // Convert a to rank-1 Array (vector) and return value of 0th element.
- try {return (this.ravel(a))[0];}
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // Return a with rows and columns transposed (for rank-1 or -2 array a).
- // APL: ⍉ a
- // NOTE: rank-1 (row vector) array will be promoted to rank-2 (1 column
- // array).
- this.transpose = function(a) {var self = {name:'transpose'};
- // Verify a is homogeneous array.
- if (!this.validateArgs(self, [a], ['array', 'homogeneous'])) return null;
- // If not rank-1 or rank-2, throw error, unless rank-3 and all elements of
- // a are strings.
- if ( 2 < this.rankOf(a) &&
- !(3 == this.rankOf(a) &&
- allStrings(this.ravel(a)))
- )
- throw AplArraysException(null, self.name, 'RankException', arguments);
- try {
- // Initialize return.
- var newA = new Array();
- // Record shape and rank of a.
- var s = this.shapeOf(a);var r = this.rankOf(a);
- // If a has more than one axis...
- if (1 < r) {
- // ...number of columns is second element of shape of a.
- var cols = (this.shapeOf(a))[1];
- // ...add columns of a as rows to return.
- for (var i = 0; i < cols; i++) {
- newA.push(this.sliceByIdx(a, i));
- }
- }
- // Otherwise, for rank-1 row vector...
- else {
- // ...number of columns will be length of a - return will be a one
- // column, rank-2 array.
- var cols = a.length;
- // ...add each element of a with an additional level of nesting.
- for (var i = 0; i < cols; i++) {
- newA.push([a[i]]);
- }
- }
- return newA;
- }
- catch (e) {AplArraysException(e, self.name, null, arguments);}
- };
- // protected access to private variables
- this.getObjectName = function() {return objName;};
- this.getObjectType = function() {return objType;};
- // Validation for arguments and tests that can be expressed simply. Failures
- // throw corresponding AplArraysException types from here without actually
- // returning to caller (leaves calling stack pendent at point of exception).
- this.validateArgs = function(caller, args, tests) {
- var x = null;
- for (var j = 0; j < tests.length; j++) {
- switch (tests[j]) {
- // Each element of args must be Array.
- case 'array': {
- for (var i = 0; i < args.length; i++) {
- if (!this.isArray(args[i])) {
- x = 'NonArrayArgumentException';
- break;
- }
- }
- if (null != x) break;
- else continue;
- }
- // All elements of args must be Boolean.
- case 'boolean': {
- for (var i = 0; i < args.length; i++) {
- if (!allBooleans(this.ravel(args[i]))) {
- x = 'InvalidArgumentTypeException';
- break;
- }
- }
- if (null != x) break;
- else continue;
- }
- // All elements of args must be homogeneous.
- case 'homogeneous': {
- for (var i = 0; i < args.length; i++) {
- if (!this.isHomogeneous(args[i])) {
- x = 'NonHomogeneousArrayException';
- break;
- }
- }
- if (null != x) break;
- else continue;
- }
- // Lengths of args must the same.
- case 'length': {
- if (args[0].length != args[1].length) {
- x = 'MismatchedArgumentLengthsException';
- break;
- }
- else continue;
- }
- // Shapes of args must the same.
- case 'shape': {
- if (!this.isConformable(args[0], args[1])) {
- x = 'NonConformableArgumentsException';
- break;
- }
- else continue;
- }
- // args must be vectors.
- case 'vector': {
- for (var i = 0; i < args.length; i++) {
- if (!this.isVector(args[i])) {
- x = 'InvalidArgumentTypeException';
- break;
- }
- else continue;
- }
- if (null != x) break;
- else continue;
- }
- }
- }
- var validated = null == x;
- if (!validated) throw AplArraysException(null, caller.name, x, args);
- return validated;
- };
- // private functions - - - - - - - - - - - - - - -
- //
- // Return Boolean assessment of whether all elements of vector a are type
- // Boolean. apl is (optional) Boolean indicator of whether to verify as JS or
- // APL Booleans: true ==> treat as APL-style 1/0 Booleans - false ==> treat
- // as JS-style true/false Booleans
- function allBooleans(a, apl) {var self = {name:'allBooleans'};
- // Set default by examining first element, if not given.
- if ('undefined' == typeof apl) apl = 'boolean' != typeof a[0];
- // Initialize assessment for logical ANDing.
- var allAplBool = apl; var allJsBool = !apl;
- for (var i = 0; i < a.length; i++) {
- if (apl)
- allAplBool = allAplBool &&
- ('boolean' != typeof a[i]) && ((0 == a[i] || 1 == a[i]));
- else allJsBool = allJsBool && 'boolean' == typeof a[i];
- }
- return (apl)?allAplBool:allJsBool;
- };
- // Return Boolean assessment of whether all elements of vector a are type
- // String.
- function allStrings(a) {var self = {name:'allStrings'};
- for (var i = 0; i < a.length; i++)
- {if ('string' != typeof a[i]) return false;}
- return true;
- };
- // Return Boolean assessment of whether all elements of vector a are type
- // Number.
- function allNumbers(a) {var self = {name:'allNumbers'};
- for (var i = 0; i < a.length; i++)
- {if ('number' != typeof a[i]) return false;}
- return true;
- };
- // Convert Boolean vectors between APL and JS equivalents.
- // apl is (optional) Boolean indicator of whether to convert to APL Booleans:
- // true ==> convert JS-style to APL-style 1/0 Booleans - false ==> convert
- // APL-style to JS-style true/false Booleans
- function convertBooleans(b, apl) {var self = {name:'convertBooleans'};
- // Set default by examining first element, if not given.
- if ('undefined' == typeof apl) apl = 'boolean' != typeof b[0];
- // Initialize return.
- var newB = new Array();
- // If converting to APL-style...
- if (apl) {
- // If all values are JS-style, convert JS to APL.
- if (allBooleans(b),true)
- for (var i = 0; i < b.length; i++) {newB.push(b[i]?1:0);}
- // Otherwise, throw error.
- else throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- }
- // Otherwise, converting to JS-style...if all are APL-style.
- else {
- // If all values are APL-style, convert APL to JS.
- if (allBooleans(b, false))
- for (var i = 0; i < b.length; i++) {newB.push(1 == b[i]);}
- // Otherwise, throw error.
- else throw AplArraysException(
- null, self.name, 'InvalidArgumentTypeException', arguments
- );
- }
- return newB;
- };
- // Define object-specific exception handling for AplArrays.
- // NOTE: exceptions created this way are all Strings for the virtue of
- // compound composition across potentially multiple levels of
- // throw/catch among the function members of the AplArrays object.
- //
- // "checked" exception in the current context refers to the intentional
- // assessment of bad states of calls, due to incorrect argument types,
- // etc., i.e., member functions will assess invalid/inconsistent states
- // and intentionally invoke throw.
- //
- // To throw for checked conditions, usage will be like:
- // if (_condition_)
- // throw AplArraysException(null, self.name, _exception_, arguments);;
- // where:
- // _condition_ is some detectable flaw in the state of the system -
- // typically a flaw in the contents or structure of one of the
- // arguments to a function
- // self.name is a private property declared by the function on itself
- // _exception_ is a String name that describes the particular
- // exception, e.g., 'RankException'
- // arguments is the arguments property of the function that threw the
- // exception
- //
- // To throw for unchecked, runtime conditions, usage will be like:
- // try {
- // <function block>
- // }
- // catch (e) {AplArraysException(e, self.name, null, arguments);}
- // where:
- // e is the runtime exception caught and passed to this handler
- // self.name is a private property declared by the function object on
- // itself
- // arguments is the arguments property of the function that threw the
- // exception
- //
- // Checked (predefined, checkable) exception types specific to the
- // AplArrays object are:
- //
- // AxisException
- // an indicated dimension (axis) is undefined for given Array object
- // IndexOutOfBoundsException
- // value of an index exceeds the extent of Array object on an
- // indicated axis
- // InvalidArgumentTypeException
- // argument passed to function is an invalid type for the signature
- // InvalidDepthException
- // number of levels of nesting of Array is not sufficient to satisfy
- // request
- // MismatchedArgumentLengthsException
- // Arrays that are required to have same length do not
- // NonArrayArgumentException
- // object required to be an Array is not
- // NonConformableArgumentsException
- // Arrays required to have conformable shape structure
- // (conformability can vary by type of operation requested) do not
- // NonHomogeneousArrayException
- // Array required to be homogeneous is not
- // RankException
- // Array fails to meet rank (number of dimensions/axes) requirement
- //
- // NOTE: there's a useful side-effect of the mixture of checked and
- // unchecked exception handling in the APL-style base functions; if a
- // checked exception is thrown from one of the base functions as a
- // result of a call from some other base function or one of higher
- // order functions, the exception reported to the console reveals the
- // call stack; the checked type is reported as such, and the
- // remaining stack is revealed as a series of reports of unchecked
- // exceptions at all remaining stack frames; this can speed debugging
- // by providing a set of candidates for watch- and break-points.
- //
- // Arguments are:
- // e........JavaScript native exception type for unchecked calls
- // callee...name of function throwing exception
- // msg......text of named exception type for checked exceptions - could
- // be more as described in the note above
- // args.....arguments array of function that threw the exception
- function AplArraysException(e, callee, msg, args) {
- var self = {name:'AplArraysException'};
- // If called with a null value for message (msg), this is a runtime error
- // or possibly a chain of exceptions from multiple frames that began with
- // a checked exception in some base function.
- if ('undefined' == typeof msg || null == msg) msg = 'runtime exception';
- // Name of function had the problem.
- var fn = '\nfunction ' + objName + '.' + callee;
- // Report this object's type.
- var type = ' (from object of type ' + objType + ')';
- // Initialize other possible reporting variables.
- var rArgs = '';var ex = '';var caller = '';
- // If this was a runtime exception, there's a JS value to report.
- if (null != e) ex = '\nerror reported was:\n' + e.toString();
- // Collect any arguments that were available to the caller.
- for (var i = 0; i < args.length; i++)
- {rArgs += '\nargs[' + i + '] = ' + args[i];}
- // Deprecated, but, if available, may help in analysis of stack.
- if (
- 'undefined' != typeof args &&
- 'undefined' != typeof args.callee &&
- 'undefined' != typeof args.callee.caller &&
- null != args.callee.caller &&
- 'undefined' != typeof args.callee.caller.name &&
- '' != args.callee.caller.name
- ) caller = '\nwhen called by ' + args.callee.caller.name;
- // Compose the collected information and rethrow.
- throw (fn + type + ' threw ' + msg + rArgs + caller + ex);
- };
- // private variables - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- //
- // mainly useful for exceptions handling, testing and debugging
- // These values are set once - at object construction; they are accessible
- // after construction via public "getters" but have no "setter" (mutators).
- // name of an instance:
- // - can be set at construction like: var myArrays = new Arrays('myArrays');
- // - defaults to short, convenience name "axApl"
- // (for a[rray e]x[tender]APL) in which case, construction should be:
- // var aau = new Arrays() (if accurate error reporting required)
- var objName = ('undefined' != typeof objectName)?objectName:'axApl';
- // type of this object
- var objType = 'AplArrays';
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement