|
outer(): Every Element of One Array Against Every Element of Another |
outer(x, y, op) pairs every element of one array with every
element of another and applies an operation to each pair. The result is a
grid: one row per element of x, one column per element of
y. Read the Arrays page (Arrays) first; everything
below assumes dim, fill and
print of an array.
Multiply every number in the first list by every number in the second and lay the answers out in a grid — the times table you learned in school:
Four elements of x down the rows, three of y
across, twelve cells. Nothing is skipped and nothing is repeated.
result(i, j) holds x(i) * y(j): x
labels the rows, y the columns. Swap the two arrays and you
get the transposed grid — which for a symmetric operation such as
* looks right until it isn't.
Assign it to an expandable array (dim table(*), reshaped to
fit), print it, or hand it to any function that takes an array:
stats$sum(outer(x, y, *)) is 60.
outer is not a multiplication tool that happens to allow
substitutions; it is a pairing tool, and multiplication is just the most
familiar thing to do with a pair. Change the operator and you change the
table. An operator is written bare: + - * / ^ and the six
comparisons = <> < <= > >=.
A comparison gives a 1/0 mask, one cell per pair, which you can count
with stats$sum. Which x values are bigger than
which y values?
Pair a sorted array against itself with > and you get a
strictly lower-triangular mask — a quick way to see that a list is
sorted, or to count inversions in one that is not.
A function goes in the third slot with its parentheses:
outer(x, y, max()). The parentheses are part of the name
here. You are telling outer which function to use,
not calling it yourself — outer does the calling, once
per pair, and the pair always fills the function's first two parameters.
If the function takes more than two parameters, the rest go inside the
parentheses and fill parameter three onward. clamp(value, low,
high) takes three, so clamp(3) means
clamp(x(i), y(j), 3):
Positional arguments are therefore offset by two on the page: in
outer(x, y, f(5)) the 5 is f's third
argument. An expression inside the parentheses is evaluated once per
pair, not once per call — outer(x, y, clamp(rnd(9)))
re-rolls the bound for every cell. To share one value across the grid,
compute it into a variable first and pass the variable.
A rounding table: one value against three precisions, with
round(value, decimals) taking the pair as its two parameters.
Any routine with two with parameters and one
returning parameter works the same way. The pair fills its
first two parameters; the rest come from inside the parentheses, by
position or by name, or from defaults.
Once you can write the routine yourself, the built-in operators stop
looking special: they are just the two-in, one-out functions that come
pre-written. A routine returning a string gives a string array; a bare
returning gives a REAL array.
The two arrays need not be the same type, and the result follows the
function. left$() takes a string and a number and returns a
string, so a string array paired against a numeric array yields a string
array — every word against every length:
One row per word, one column per length — and the columns come in
the order the lengths were given (3, 1, 6, 2, 5), not sorted: the grid
follows y exactly. A length past the end of a word simply
gives the whole word, as left$() always does.
A function that takes strings and returns a number gives a REAL array.
compare() scores two strings 0 to 100, so a list of names
against itself is a similarity matrix — 100 down the diagonal, a
typo scoring high, unrelated words scoring 0:
The types must line up: x against the function's first
parameter, y against its second. A string where a number is
expected (or the reverse) is a compile-time error; numeric kinds convert
as they do everywhere else.
Two lists give a grid, whatever their lengths: a one-element
x against a five-element y is a 1 by 5 grid,
not a flat list. When either array has more than one dimension, the
result's dimensions are x's followed by y's:
a 4 by 2 against a 3 gives 4 by 2 by 3. Every element is still paired
with every element; the dimensions only describe how the pairs are
arranged. Cells are filled in row-major order — the last index
varies fastest — and that order is guaranteed, so an argument that
counts or generates per pair follows it. Sheerpower allows up to 16
dimensions, so the two ranks must sum to 16 or less.
Mind the element count: pairing every element against every element
multiplies the two totals. A 100 by 100 by 100 array against a 100 by 100
array is ten billion cells — work it out before running it. An empty
operand gives an empty result of the right shape (size() 0).
The rules, in one place. The op is required —
outer(x, y) is a compile error; nothing is implied. An
operator is bare; a function or routine carries its parentheses, and a
bare name such as outer(x, y, min) is refused. The pair
always fills parameters one and two: a named argument may not refer to
them, a default on them never fires, and a routine with fewer than two
with parameters cannot be the op. Extras are single values,
not arrays. Too many values for the routine's parameter list, or a type
that does not line up, is caught at compile time. Operators are symbols
only: mod() is the function.
|
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. |