|
reduce() and scan(): Folding an Array Down to One Value, or Running It Up |
reduce(x, op) folds an array down to one value by applying an
operation between the elements, left to right; scan(x, op)
does the same but keeps every intermediate result — the running
values. They take the same third-slot op as
outer() (outer(): Every Element of One Array Against Every Element of Another): an operator written bare, or
a built-in or one of your own routines with its parentheses. Read the
Arrays page (Arrays) first.
reduce(v, +) is what stats$sum(v) gives you; the
point of reduce is everything stats$ does not
have — any operator, any built-in, any routine. The result is an
ordinary value: assign it, test it, use it inside an expression:
The fold runs left to right, which is the reading order
and what scan shows step by step. (APL, where these functions
come from, folds the other way — its -/3 1 6 2 5 is 11,
not -11.)
A real fold: compound growth. An investment of 1000 grows
by a different amount each year — up 5%, up 3%, down 2%, up 8%. Each
year multiplies what came before, so chaining the yearly factors with a
* fold gives the total growth in one line — and in exact
decimal it holds every cent across the whole chain, where a run of
floating-point multiplications would drift:
A running product like that is exactly what stats$ has no name
for — the reason reduce takes any operator you like.
scan keeps the shape: as many elements as the input, element
k being the reduce of the first k.
On a string array the one operator is + (the strings are
joined); anything else takes a string function or routine. A mask of ones
and zeros folds with and (all?) and or (any?).
Any routine with two with parameters and one
returning parameter is a fold. The running value fills its
first parameter, the next element its second; extras inside the
parentheses fill parameter three onward, by position or by name, or come
from defaults:
On a multi-dimensional array, reduce with no axis folds
along the last dimension — on a matrix, one value
per row — and that dimension disappears. That is APL's rule, and
the natural reading of "reduce a table". axis: n picks a
different dimension; all: true folds every cell, in storage
order, down to one value.
axis: takes one dimension; fold twice to collapse two. The
shape that governs is the one the array has at run time
— a dim z(*) holding a 2 by 3 result folds exactly as
a 2 by 3 array does. What the surrounding code asks for sets the result's
kind: in an array spot (an array assignment, a print) you get the array;
in a scalar spot (total = reduce(z, +)) a result that is
still an array raises a catchable error telling you to assign it to an
expandable array instead.
Sheerpower's REAL is exact decimal up to 54 integer digits and floats with 64 significant digits beyond that, out to exponents of a billion. So this is one statement:
That is 100000 factorial: 456,574 digits long. The exponent is exact and so are the first 60 of the 64 digits shown; past 1054 every one of the hundred thousand multiplications rounds at the 64th digit, and the accumulated rounding shows in the last four. (A double overflows at 170!.) Through 43! the product stays in the exact range, and every digit of it is real.
The rules, in one place. The fold runs left to right.
With no axis, a declared multi-dimensional array folds its last axis; a
1-D array folds to a scalar; all: true folds every cell. An
operator is bare (+ - * / ^, the six comparisons,
and, or); a function or routine carries its
parentheses. The op must give back the same type it takes — numbers
to a number, strings to a string — because the running value feeds
back in; numeric arrays fold as REAL. An empty array reduces to the
operator's identity where it has one (+ 0, * 1,
and 1, or 0) and otherwise raises the catchable
exception EMPTYFOLD; scan of an empty array is empty.
_integer is the number of pairs folded. The old
scan(string, substring) search is untouched — the fold
reading applies only when the first argument is an array.
The bigger picture. An expandable array is reshaped by
whatever is assigned to it, and every array function honors the shape it
holds right now. So a pair of declarations like dim z(*), r(*)
gives you variables that behave like APL's: r can receive the
result of any function that returns a 0-to-n dimensional array —
outer() raising the rank, reduce() lowering it,
scan(), reshape(), filter() keeping
or reshaping it — with the shape travelling with the value, not the
declaration. (Rank 0 lands in an array variable as a one-element array;
a plain scalar variable is what you use in a scalar spot.)
|
Hide Description
|
|
|
Enter or modify the code below, and then click on RUN |
|
Looking for the full power of Sheerpower?
Check out the Sheerpower website. Free to download. Free to use. |