Popup YouTube Video
Sheerpower Logo

outer(): Every Element of One Array Against Every Element of Another


outer(): The Pairing Function

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.

1. The Times Table

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:

dim x(4), y(3) fill x with seq(1, 4) fill y with seq(1, 3) print outer(x, y, *)
1 2 3 2 4 6 3 6 9 4 8 12

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.

2. Operators

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 = <> < <= > >=.

print outer(x, y, +) ! the addition table: 2 3 4 / 3 4 5 / ... print outer(x, y, ^) ! powers: x(i) ^ y(j)

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?

print outer(x, y, >) print "pairs where x > y: "; stats$sum(outer(x, y, >))
0 0 0 1 0 0 1 1 0 1 1 1 pairs where x > y: 6

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.

3. Built-in Functions

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.

print outer(x, y, max()) ! the larger of each pair print outer(x, y, min()) ! the smaller print outer(x, y, mod()) ! mod(x(i), y(j))
1 2 3 ! max() 2 2 3 3 3 3 4 4 4

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):

print outer(x, y, clamp(3)) ! 1 2 3 / 2 2 3 / 3 3 3 / 3 3 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.

dim v(1), decimals(3) fill v with 1.2345 fill decimals with 0, 1, 2 print outer(v, decimals, round()) ! 1 1.2 1.23

4. Your Own Routines

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.

print outer(x, y, half_sum()) ! 1 1.5 2 / 1.5 2 2.5 / ... print outer(x, y, scale_pair(10)) ! factor = 10, by position print outer(x, y, scale_pair(factor = 100)) routine half_sum with a, b, returning h h = (a + b) / 2 end routine routine scale_pair with a, b, factor = 1, returning c c = a * b * factor end routine

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.

5. Strings

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:

dim x(*) fill x with 3, 1, 6, 2, 5 dim names$(*) fill names$ with 'apples', 'pears', 'blueberrys', 'oranges' print x print outer(names$, x, left$())
3, 1, 6, 2, 5 app, a, apples, ap, apple pea, p, pears, pe, pears blu, b, bluebe, bl, blueb ora, o, orange, or, orang

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:

dim names$(3) fill names$ with "hello", "jello", "world" print outer(names$, names$, compare())
100, 92, 0 92, 100, 0 0, 0, 100

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.

6. Shapes

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.