each
each, keyword
The each keyword offers an iteration mechanism against an observation table.
Iterations are not ordered. The table named after each is always the
iteration table.
table Obs = with
[| as N |]
[| 1 |]
[| 2 |]
[| 3 |]
Obs.Cpy = each Obs
cpy = Obs.N + 1
return cpy
show table "" with Obs.N, Obs.Cpy
each .. scan , keyword
The each .. scan keywords offer an iteration mechanism against an observation
table. Iterations are ordered based on the argument passed to scan.
The scan expression must depend only on data available before the loop
starts. Expressions computed inside the loop are rejected to avoid rebuilding
indices per iteration.
table Obs = with
[| date(2021, 1, 1) as Date, 13 as Quantity |]
[| date(2021, 2, 1) , 11 |]
[| date(2021, 3, 1) , 17 |]
[| date(2021, 4, 1) , 18 |]
[| date(2021, 5, 1) , 16 |]
Best = 0
Obs.BestSoFar = each Obs scan Obs.Date
keep Best
NewBest = max(Best, Obs.Quantity)
Best = NewBest
return NewBest
show table "" with Obs.Date, Obs.BestSoFar
Table diagram
each uses the table diagram to decide how tables are accessed in the loop:
- Iteration table: the table after
each. - Upstream table: broadcasts into the iteration table.
- Downstream table: receives broadcasts from the iteration table (not allowed).
- Upstream-cross table: a cross table whose left side is the iteration table or upstream; only allowed when the right side is a full table and either small or equal to the iteration table.
- Full table: any other table; allowed only if small.
Reads from upstream tables return the scalar value matching the current
iteration line. Writes to the iteration table or upstream tables are forbidden;
use keep Upstream.X as X to update upstream values through a scalar alias.
Writing to upstream-cross tables is forbidden.
Broadcasting into the iteration table or upstream tables is forbidden.
The keep statements must appear first in the block and refer to variables
defined before the loop. each .. scan requires at least one keep.
desc reverses the scan order.
Upstream vectors must be kept as scalars and updated through an alias:
table Items[id] = with
[| as Id, as X, as Cat |]
[| 1, 1, "A" |]
[| 2, 2, "B" |]
table Categories[cat] = by Items.Cat
Categories.X = 0
Items.B = each Items scan Items.Cat
keep Categories.X as X
X = Items.X + X
return X
Cross broadcasts
Inside each, a vector from an upstream-cross table may be broadcast onto the
full-table component of that cross table.
table Items[id] = with
[| as id, as X, as Cat |]
[| "1", 1, "A" |]
[| "2", 2, "B" |]
[| "3", 3, "A" |]
[| "4", 4, "B" |]
table T = with
[| as Y |]
[| 1 |]
[| 2 |]
[| 3 |]
[| 4 |]
expect table T max 4
table Cat[c] max 2 = by Items.Cat
table TCat = cross(T, Cat)
TCat.Z = match TCat.c with
"A" -> T.Y
"B" -> (1 + T.Y) ^ 2
Items.B = each Items
x = Items.X
T.Z = TCat.Z
y = sum(T.Z) when (T.Y < x)
return y
show table "Items" with
Items.id
Items.Cat
Items.B
If the projected table is the right component of the cross table, then that
cross table must be small.
Assignments to full tables
Inside each, a process may assign its result to a small full table or to a
grouped table derived from that full table. The produced vectors can then be
read later in the same iteration.
table Items[id] = with
[| as id, as X |]
[| "1", 1 |]
[| "2", 2 |]
[| "3", 3 |]
[| "4", 4 |]
table T = with
[| as Y, as Cat |]
[| 1, "A" |]
[| 3, "B" |]
[| 4, "B" |]
[| 2, "A" |]
expect table T max 4
table C[cat] max 4 = by T.Cat
Items.B = each Items
x = Items.X
C.X = sum(T.Y) when (T.Y > x)
return join(text(C.X); "-") sort cat
As usual, a table that is aggregated as a whole inside the loop must be small.
Nested blocks
Nested each blocks are allowed. The inner block gets its own iteration table,
while keep aliases from the outer block remain available as scalars. The
usual table-diagram restrictions still apply inside the inner block.
table Items[id] = with
[| as id, as X |]
[| "1", 1 |]
[| "2", 2 |]
table T = with
[| as Y |]
[| 1 |]
[| 2 |]
expect table T max 2
Items.Trace = each Items
X = Items.X
T.Step = each T scan T.Y
keep X
X = X + T.Y
return X
return join(text(T.Step); "-") sort T.Y
show table "Nested each" with
Items.id
Items.Trace
The inner block does not relax the usual restrictions: table statements are
still forbidden, and assigning a vector from a non-scalar full table is still
rejected.
Cross-table returns
An each block may return both a vector on the iteration table and a vector in
a cross table built from the iteration table and another table.
table Items[id] = with
[| as id, as X |]
[| "1", 1 |]
[| "2", 2 |]
[| "3", 3 |]
table T = with
[| as Y |]
[| 1 |]
[| 2 |]
[| 3 |]
expect table T max 3
table ItemsT = cross(Items, T)
Items.Y, ItemsT.Pair = each Items
x = Items.X
return (x, "\{x}:\{T.Y}")
show table "Items" with
Items.id
Items.Y
show table "Pairs" with
ItemsT.id
T.Y
ItemsT.Pair
The same rule applies inside nested loops: return always targets the current
loop, and one of its outputs may live in a cross table tied to that loop.
Return-less blocks
If only the final keep values matter, return can be omitted:
table Currencies = with
[| as Code |]
[| "EUR" |]
[| "JPY" |]
[| "USD" |]
Sep = ""
List = ""
each Currencies scan Currencies.Code
keep Sep
keep List
List = "\{List}\{Sep}\{Currencies.Code}"
Sep = ", "
show scalar "" with List
auto ordering
scan auto follows the primary dimension ordering:
table T = extend.range(3)
x = 0
T.X = each T scan auto
keep x
x = T.N - x
return x
Any-order blocks
scan T.* allows arbitrary ordering:
table Obs = with
[| as X |]
[| 42 |]
[| 41 |]
[| 45 |]
myMin = 1B
myMax = -(1B)
each Obs scan Obs.*
keep myMin
keep myMax
myMin = min(myMin, Obs.X)
myMax = max(myMax, Obs.X)
each .. when
when filters iterations and is only allowed on return-less blocks:
table T = extend.range(5)
s = 0
each T scan auto when T.N mod 2 == 1
keep s
s = s + T.N
Comparison with for
each and for overlap, but they are not identical.
Prefer each when the loop body is naturally row-oriented.
eachanchors the loop on one table and exposes the current row through the table diagram. The body can read columns from that row directly, for exampleItems.KeyorItems.X, without listing them in the loop header.eachcan also use that row context to broadcast a selected slice of an upstream-cross table inside the body, as inT.Z = TCat.Z.eachaccepts some row-dependent operations thatforrejects. In particular, a lookup with an iteration-dependenttextkey such asSeek.X[Items.Key]is accepted ineachbut rejected infor.forsupports capabilities thateachdoes not: integer ranges such asfor n = 1..10and an explicit list of loop inputs in the header.