|
Arrays |
Arrays are fundamental data structures that let you store a list of items in a single variable. Think of them as a set of numbered slots, each holding a value. In Sheerpower, arrays are a simple way to manage lists, but it's also important to know when a more powerful tool, like a Cluster Array, is a better choice.
If you know exactly how many items you need to store, you can
create a fixed-size array using the dim statement.
This creates an array that looks like this:
1. However,
you can specify a different range, such as dim myarray(0 to 5)
or even dim myarray(-10 to 30).
redim
What if you create an array and later realize you need more
slots? You can use the redim statement to expand it.
Importantly, redim preserves all the existing
data in the array.
So "fixed" is worth defining: an array declared with a
size, such as dim names$(5) or dim grid(2, 3),
is fixed in its number of dimensions, not in its size.
redim names$(200) and redim grid(4, 5) change
the bounds; redim grid(6) is a compile error, because that
would change how many subscripts the array takes. Only an expandable
array (dim x(*)) can change its number of dimensions.
An expandable array (the next section) can go further:
redim can give it a different number of dimensions,
keeping the data in order. That is covered on the Array Math page.
See Array Math Functions, Index Lists & Slices, Solve(), Sort(), and More.
Visually, the redim operation does this:
What if you don't know the size at all and want to add items one by one? Declare the array with an open bound, written as an asterisk:
To add an item, assign to (*). This
appends the item to the end of the array, making it
grow automatically. Reading (*) gives you the
last element, and size() tells you how
many items there are.
The first element is number 1, as usual. If you want the numbering to
start somewhere else, give the lower bound and leave the upper bound
open: dim scores(0 to *) starts at 0, so the first
append lands in element 0 and size() still counts the
items.
To empty an expandable array and start over, re-declare
it the same way: redim shopping_list$(*) (older code:
redim shopping_list$(0)).
The most common thing to do with any array is to visit every element,
and the for loop from the previous page is built for it.
size() supplies the upper bound, so the loop is right
however many items the list holds:
dim list$(0) and append with
list$(0) = value. Both still work exactly as before, and
list$(*) = value may be used on such an array too. For
new code, write dim list$(*) and list$(*) = value
— it says what it means.
(*) works on every array. A fixed array
such as dim buf(3) or dim grid(2, 3) has an
append cursor too: buf(*) = value puts the value in the next
unused slot (a 2D array fills row by row, the order print
shows) and buf(*) reads the last one appended. The
difference is room: a fixed array never grows, so once every slot is
used the next append raises SUBOUTBND ("no room to append").
Only dim list$(*) keeps growing. A fill or an
assignment to an element moves the cursor past what it wrote, so
fill w with 1, 2, 3 followed by w(*) = 4 lands
on w(4). The cursor survives a reshape in storage order:
redim keeps it when the array grows and caps it when the
array shrinks, so four appended items followed by redim x(2,
3) leave the cursor at 4 and the next append fills the fifth
slot. A whole-array assignment (x = y * 2) fills every slot,
so afterwards the cursor is at the end and an append on a
two-dimensional result raises SUBOUTBND until the array is
one-dimensional again.
fill
You have 100 scores to zero out before a new round, a month of days
to number, or a week of names to load. Element by element works, but
these most common jobs — give every element a starting value,
number the elements, or load a small known list — deserve one
line each. The fill statement does exactly that, in three
forms.
And fill is not a one-time load — run it as often as you
like. A plain fill reloads from the start,
fill x(*) adds to the end, and fill x(pos) changes
elements in place. So fill is also your append:
Sheerpower needs no separate append statement, and the same
statement grows an expandable array or edits a fixed one.
fill x with ... starts at element 1
(the array's lower bound).fill x(*) with ... starts at the slot after
the last one used — the same slot x(*) = v
writes.fill x(expr) with ... starts at element
expr (fill grid(r, c) at that cell).with. Only the plain form discards what an expandable
array holds; (*) and (expr) always work
inside or onto what is there.
The value is evaluated for every element — so
fill dice with rnd(6) rolls every die, and
fill ids$ with _gid$ gives every slot its own id. A
literal, a const or a plain variable is simply copied;
an expression is computed again for each slot. After any fill,
_integer holds how many elements were filled.
On an expandable array a one-value fill covers whatever the array
currently holds — fill nines with 0 zeroes all
hundred, and nines(*) = 1 still appends a 101st
afterwards. To give an expandable array a shape first, use
redim: redim grid(2, 3) then fill grid
with 0.
seq()
seq(a, b [, step]) builds the numbers from a to
b as an array, and fill lays them into yours. The
step defaults to 1; a descending range needs a negative step, so without one
seq(10, 1) is empty — the same rule as
for i = 10 to 1. And it is not only seq(): ANY
array expression fills this way — filter(),
sort(), arithmetic like src * 2, or another array
— its elements become the values. An array is really just a
list item that splices in its elements, so you can mix arrays and
plain values freely: fill x with seq(1, 5), 99, seq(100, 110).
Values are full expressions, assigned left to right. Giving
more values than the array has elements raises the
catchable exception FILLOVER — and for a value
list the check happens before anything is written, so a
caught overflow leaves the array untouched. Giving
fewer fills just those elements and leaves the rest
alone.
fill x(*)
A plain fill writes from element 1 — which is why a
second fill menu$ with ... replaces the menu rather than
lengthening it. To add values after what is already there, put the
append cursor in the parentheses: fill x(*) with ...
writes from the slot after the last used one, the same place
x(*) = v appends a single value. All three forms work:
On a fixed array fill x(*) continues from the cursor and
raises FILLOVER when there is no room; on an expandable
one it grows. The cursor is moved by every write — a
fill, an assignment, or an indexed write — so
q(2) = 5 followed by fill q(*) with 7, 8
writes q(3) and q(4). On a fresh array the
cursor is at the start, so fill x(*) with 1, 2, 3 and
fill x with 1, 2, 3 give the same result; the difference
shows only once the array holds something.
fill x(20)
The third place a fill can start is any element you name. The rule
has one shape: fill x starts at element 1,
fill x(*) at the append cursor, and fill x(20)
at element 20. From there the values run on in storage order, so on a
two-dimensional array fill grid(2, 1) with 1, 2, 3 fills
row 2.
The start must be an element of a fixed array — naming one past
the end raises SUBOUTBND, the same as x(11) = 1
would — and a run that would pass the end raises
FILLOVER before anything is written. On an expandable array
the start may lie past the end, and the gap is filled with zeros,
exactly as x(9) = 8 does. A positioned fill never discards
what the array holds; only a plain fill x with ... does
that.
n of value
Fifty 9s on the end of an array is not a range and not a list you
want to type. Say how many: a list item may be n of value,
and it mixes with ordinary items. To repeat a whole group, put the
group in parentheses.
of binds to the one value beside it —
3 of 1, 2, 3 is 1, 1, 1, 2, 3 — so
parentheses are what make a group. A count of 0 contributes nothing.
Every value inside an n of is evaluated for each element
it fills: 50 of rnd(6) is fifty rolls, 5 of
_gid$ five different ids. A plain list item is evaluated once,
before anything is written, so fill x with x(2), x(1) swaps. Too many values for a fixed
array is still FILLOVER, decided before anything is
written, and _integer is the total number written.
fill checks sizes at runtime
against the array's current extent, so it plays correctly
with redim.fill x(*) with ... is the form that adds to the end.
A one-value fill on an expandable array covers whatever it currently
holds.fill grid with
1, 2, 3, 4, 5, 6 on dim grid(2, 3) fills row 1
with 1, 2, 3 and row 2 with 4, 5, 6.fill is: three places to start (x /
x(*) / x(pos)) times a value that can be a
broadcast, a seq(), a list, a spliced array, or
n of repeats, over a fixed or an expandable array.
That one primitive does the work most languages split across
fill, append, extend, insert,
range, and a repeat operator — and because the value is
evaluated per element, it also generates (fill dice with
rnd(6)). Learn the three start points and the value forms once, and
you have covered nearly all of array mutation.
print and print array
Once an array is loaded you want to see it, and writing a loop just to
look is tedious. You can hand a whole array straight to
print. The elements come out lined up in columns, sized to the
widest one; a two-dimensional array prints one row per line, like a
table:
When you want control over the layout, use the print array
statement — the same shape as print cluster, with a
colon and a comma-separated option list:
The options are list (one element per line),
index (show each element's subscript — a
2D array shows (row,col)), csv
(strings quoted, commas with no spaces), and quoted
(the aligned display, but each string element wrapped in
quotes). Expandable arrays print what has been appended so far; an
empty one prints an empty line.
quoted earns its place when a string might be empty or
carry stray spaces — the plain display cannot show either, but
quotes make both plain. An empty element appears as "",
a trailing space sits inside the quotes, and an embedded quote is
doubled (as in csv). Numbers are never quoted, so
quoted changes nothing for a numeric array. It combines
with the others: : list, quoted quotes one element per
line, : index, quoted labels and quotes each.
sorted
A leaderboard, a list of names for a menu, the readings from a sensor
that you want to scan from lowest to highest — some arrays are
only ever wanted in order, and sorting them yourself after every
change is a chore that is easy to forget. Say so once, on the
dim, and the array keeps itself in order:
Every write keeps the order: an append with (*) lands at
its place, an element write moves the new value to its place, and a
fill sorts once. Numbers order numerically; strings order
case-blind. Because the array is always in order, a position is a
rank: scores(1) is the lowest score and
scores(*) the highest, and scores(1) = 99
replaces the lowest score with 99, which then takes its own rank.
Everything you have read on this page — print,
size(), the statistics functions, filter()
— sees the sorted order.
sorted arrays are best for simple tasks. Because the elements are kept in
sorted order, only two positions have a guaranteed meaning:
arrayname[1] always holds the lowest value, and
arrayname[size(arrayname)] always holds the highest. Any other position
may hold a different element after new data is added, so never save an index and
expect it to refer to the same value later.
Arrays can also be computed with as a whole — z = x * 100
scales every element at once, matmul(a, b) multiplies matrices,
and solve(a, b) finds the unknowns behind known totals.
String arrays take part: every string function maps over one
(ucase$(names$)), + joins element by element,
a comparison such as names$ = "bob" is a mask, and
sort(), unique() and isin() work
on strings as on numbers.
That is a different level of work from this page; when you need it,
it has a discussion of its own: Array Math Functions, Index Lists & Slices, Solve(), Sort(), and More. Extensive statistics
are also available. See Cluster and Traditional Array Statistics.
Traditional arrays are great for simple lists of single values. However, most business data is structured. For example, instead of just a list of product names, you usually need to store the product's name, its price, and its quantity together.
For this kind of structured data, the Cluster Array is the preferred and more powerful tool in Sheerpower. Think of it as a spreadsheet in memory, where each row is a complete record.
dim array(10)redim array(20)dim array(*)array(*) = value
(dim array(0) / array(0) = value is the
older spelling, still supported).fill array with ...print array / print array x: list, index, csv, quotedprint for a
one-line view, or use print array for a layout.z = x * 100, matmul(a, b), solve()All told, Sheerpower gives you 17 core array operations, made open-ended by array-mapping over the whole function library (built-in and user-defined), with slicing/indexing and the fill/print/structure machinery around them. The list below shows every one, all in one place.
(Show/Hide the Full List of Array Features)Everything here returns (or reduces) an array, and every one is exact.
matmul(a, b) — matrix multiply. R×K by
K×C gives R×C, with exact sums. A vector stands for a
column on the right and a row on the left, so
matmul(m, v) and matmul(v, m) give vectors;
two vectors raise ARRAYSHAPE (say which is the row:
for the dot product use dot()).dot(a, b) — the dot product, a single exact
number: the sum of a(i) * b(i) over two arrays of the same
count (dot(v, v) is the squared length).reshape(a, d1 [, d2 ...] [, recycle: true]) — pour
a's elements into a new shape; reshape(a) with no sizes
flattens to a 1-D vector.transpose(a) — swap rows and columns
(r×c becomes c×r).solve(a, b) — solve the square linear system
matmul(a, x) = b exactly.lstsq(a, b) — the least-squares best fit of an
overdetermined (full-rank) system.determinant(a) — the determinant of a square
matrix; 0 means there is no unique solution.norm(a [, kind: manhattan|max] [, axis: n] [, keep: true]) —
vector length (the Frobenius norm of a matrix); kind gives
the Manhattan (sum of |x|) or max norm; axis gives a
vector of per-row / per-column norms.inverse() — solve,
don't invert (solve(a, b); solve(a, identity)
if you truly need the inverse) — and eigenvalues, SVD, QR and
Cholesky, which are iterative floating-point results with no exact
answer: reach for a numerical library for those.outer(a, b, op) — every element of a paired with
every element of b through op (the times / addition
table); op is a bare operator, a built-in, or your own
routine.reduce(a, op [, axis: n] [, keep: true]) — fold an
array down to a scalar, or one dimension smaller along an axis: on a
grid reduce(g, +) is the row sums (the
last axis), reduce(g, +, axis: 1) the column
sums; the same with *, max(),
min() or your own routine. keep: true
keeps the folded dimension as size 1, so the result broadcasts
straight back against the source.stats$sum(a, axis: n [, keep: true]) — and
stats$mean, min, max,
median, stddev, var,
percentile: one statistic per row or column
(axis: 1 = per column, axis: 2 = per row),
the same exact answer the whole-array call gives each lane.
g - stats$mean(g, axis: 1, keep: true) centres every
column in one line. Without axis: a
stats$*() call takes the whole array as one population.scan(a, op [, axis: n]) — the running (cumulative)
fold, in a's shape: running totals per row with axis.filter(values, mask [, axis: n] [, partial: true])
— the values whose mask element is 1, as a 1-D array (storage
order); with axis: n the mask picks whole rows or columns:
filter(g, g[*, 1] > 100, axis: 1) keeps the rows whose
first value exceeds 100, all their columns, and axis: 2
keeps columns.sort(a [, descending: true] [, nocase: false]) —
the sorted array.sortindex(a [, descending: true] [, nocase: false])
— the permutation vector that sorts a (sort one array by
another: names$[sortindex(ages)]).unique(a [, nocase: false]) — the distinct
elements in first-occurrence order (sort(unique(a))
sorted; size(unique(a)) the count).stats$argmax(a [, axis: n]) / stats$argmin(a)
— the POSITION of the maximum / minimum (first on ties);
names$[stats$argmax(scores)].stack(a, b, ... [, axis: 2]) — the arrays one
under the other as rows (a vector is a row), or side by side.isin(a, set [, nocase: false]) — a 0/1 mask of
which elements of a occur in set: filter(a, isin(a, b))
is the intersection, filter(a, not isin(a, b)) the
difference, unique(stack(a, b, axis: 2)) the union.split(text$ [, delim$]) — a string into a string
array.join$(a [, delim$]) — an array into one string.seq(lo, hi [, step]) — the numbers lo..hi as a REAL
array (APL's iota).You are never limited to the 17. Any function applies element-wise to a whole array, so the entire function library — and anything you write yourself — works array-wide.
abs(v),
round(v, 2), sqr(v), sin(v),
mod(v, 3), clamp(v, lo, hi),
int(v), exp(v), log10(v), ...
— any built-in whose first argument is a REAL, giving a same-shape
REAL array.ucase$(names$),
trim$(v$), mid$(v$, 1, 2),
replace$(v$, "a=A"), len(v$),
pos(v$, "x"), val(v$), str$(v),
chr$(codes), ... — string built-ins over a string
array (a string→number function gives a REAL array). The
operators work element by element too: + concatenates
(names$ + "!", first$ + " " + last$,
first$ + " (" + str$(ages) + ")") and the six
comparisons give a 0/1 mask with the scalar rules
(names$ = "bob", names$ < "m";
same(names$, "bob") for case-blind) — so
filter(names$, names$ = "bob") filters a string column
and names$(names$ = "old") = "new" replaces a value.
sort(), unique(), isin() and
filter() take string arrays; join the whole array into
one string with join$(v$).calc_tax(amount = prices) — a function-form routine
whose first parameter is a scalar maps element-wise; pipes too:
prices |> calc_tax().max(a, b), clamp(v, lo, hi).v[a:b] — a slice (inclusive both ends);
v[a:*] to the end, v[:b] from the start,
v[a:b:step] every step-th (v[1:*:2] every
other; a negative step walks down, v[*:1:-1] reverses);
* is the last index and *-n counts back from
it, so v[*-1:*] is the last two and v[*-2:*]
the last three (there is no v[-1]: a negative subscript
is a real index on dim a(-10 to 30)). On a grid
g[2, *] is a row, g[*, 3] a column,
g[1:2, 2:3] a tile.x[y] — gather: the elements at the positions in
index array y, shaped like y.x[y] = w / x[y] = value — scatter:
write w's elements (or one value) to those positions.v(mask) — masked read (the same as
filter); vals(mask) = value — masked
store (a 1 selects each element to change).+ - * / ^ and unary
- — whole arrays, element by element. A single
value broadcasts (v * 2, g - stats$mean(g)),
and so do shapes, by one rule: shapes pair by
position from the last dimension, and a dimension of 1 stretches. So
a 1×c row repeats down the rows of an r×c matrix and an
r×1 column repeats across the columns — per-column
centering is g - reshape(colmeans, 1, c), a per-row
offset g + reshape(rowoffs, r, 1). A bare vector
against a matrix is refused (say which way it lies:
reshape(v, 1, n) for a row, reshape(v, n, 1)
for a column); any other mismatch raises ARRAYSHAPE
naming both shapes.= <> < <= >= > —
passes = scores >= 60. A mask is a boolean
array, kept one bit a flag (a REAL or integer array of 0s and 1s is
accepted as a mask too); a mask element that is not 0 or 1 raises
NOT0OR1, so an index list mistaken for a mask is loud.
stats$sum(mask) counts the 1s — the way to count
the elements that meet a condition (Array Math, section 2).and, or,
not.The whole stats$*() family (48 aggregates —
stats$sum, stats$mean,
stats$median, stats$stddev,
stats$percentile, stats$pcorr,
stats$min, stats$max, ...) works on any
array.
dim x(n) / dim g(r, c) /
dim a(lo to hi) — fixed arrays (1-origin; custom or
negative bounds).dim x(*) — expandable; x(*) = v
appends, x(*) reads the last, and (*) is the
append cursor of any array.redim x(...) — resize (keeps data) or reshape an
expandable array.fill x with ... — load in one statement: broadcast,
a list, n of v, seq(a, b), positioned
x(pos), or append x(*).print x / print array x: list, index, csv,
quoted — print an array (quoted wraps each
string element in quotes, revealing empty / whitespace-bearing
elements).dim x(n) sorted — a self-maintaining sorted
array.freeze x / permafreeze x — make a
whole array immutable.routine r with values(*), returning sq(*) — pass
arrays by reference to routines.x = y — whole-array copy; typeof$(x)
— an array's shape and state.|
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. |