for
for .. in .. , keyword
The for X in T.X offers a simple iteration mechanism over the values of a
specified vector. Unlike each blocks, there is no diagram, no observation
table, etc.
All iteration constructs in Envision have a bounded number of iterations;
while loops are not supported.
This mechanism allows to cross a table with itself, as illustrated by:
table T = extend.range(5)
T.S = for N in T.N
return N * sum(T.N) when (N < T.N)
show table "" with T.N, T.S
Several tables can be used as long as Envision can infer one unique iteration table:
table T = extend.range(5)
table U[u] = by T.N mod 2
T.S = for tn in T.N, un in u
return un + tn * sum(T.N) when (tn < T.N)
show table "" with T.N, T.S
An input may also be projected from a cross table. In the example below, the
loop iterates on Items, and T.Z in CatT.Z exposes one slice of CatT.Z as
the loop input T.Z.
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 |]
table Cat[c] max 2 = by Items.Cat
table CatT max 8 = cross(Cat, T)
CatT.Z = match CatT.c with
"A" -> T.Y
"B" -> (1 + T.Y) ^ 2
Items.B = for X in Items.X, T.Z in CatT.Z
Y = sum(T.Z) when (T.Y < X)
return Y
show table "Items" with
Items.id
Items.Cat
Items.B
This works because CatT links one table upstream of Items (Cat) with the
named input table (T). If the named input table is the left component of the
cross table, then that cross table must be small.
You can also force the iteration table with for _ in T, .... Here _ is
only a placeholder, not a loop variable; T is the explicit iteration table,
and this form must appear immediately after for.
table T = extend.range(5)
table U[u] = by (T.N mod 3)
U.M = sum(T.N)
s = 0
T.Cum = for _ in T, M in U.M scan T.N
keep s
s = s + M
return s
show table "" with
T.N
T.Cum
Inside for, keep also supports as. Use keep ... as ... for upstream
vectors, for example keep Colors.N as N. The same form can expose the
selected slice of a kept cross vector, for example keep PC.N as Cities.N.
Iteration order with scan
The scan option defines the iteration order. It is required when using keep
and forbidden otherwise.
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 T = extend.range(3)
acc = 0
T.Cum = for n in T.N scan T.N
keep acc
acc = acc + n
return acc
Filtering with when
when filters iterations and can only be used on loops without return.
table T = extend.range(5)
s = 0
for n in T.N scan T.N when n mod 2 == 1
keep s
s = s + n
show scalar "odd sum" with s
Assignments to full tables
Inside a for body, 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, as Cat |]
[| "1", 1, "A" |]
[| "2", 2, "B" |]
[| "3", 3, "A" |]
[| "4", 4, "B" |]
expect table Items small 4
table Cats = by Items.Cat
Items.B = for X in Items.X
Cats.N = count(X < Items.X)
return sum(Cats.N)
As usual, a table that is aggregated as a whole inside the loop must be small.
Cross-table return
Returning vectors from other tables requires a matching cross table:
table T = extend.range(3)
table U = extend.range(2)
table TU = cross(T, U)
T.A, TU.B = for n in T.N
a = n
U.B = n * U.N
return (a, U.B)
If the returned cross table uses the iteration table as its right component,
then that cross table must be small.
table T = extend.range(3)
table U = extend.range(2)
table UT small 6 = cross(U, T)
UT.B = for n in T.N
return U.N + n
show table "UT" with
U.N
T.N
UT.B
return ... by ... at ... can also be used when the loop consumes additional
scalars from a related table.
table enum Regions[rg] = "North", "South"
table St = with
[| as City, as PopA, as PopB, as Region |]
[| "Paris", 0.9, 0.1, "North" |]
[| "Lyon", 0.7, 0.3, "South" |]
[| "Tours", 0.5, 0.5, "North" |]
[| "Lille", 0.6, 0.4, "North" |]
[| "Cannes", 0.8, 0.2, "South" |]
expect St.rg = enum<<Regions>>(St.Region)
St.Twin = for PopA in St.PopA, PopB in St.PopB, City in St.City, Region in Regions.Label
return argmin(abs(PopA - St.PopA) + abs(PopB - St.PopB), St.City)
when (City != St.City)
by St.Region at Region
show table "Twins" with
St.City
St.Twin
Range iteration
for n = low..high iterates over integer ranges. scan is not allowed and
return is forbidden.
a = 0
for n = 1..3
keep a
a = a + n
show scalar "a" with a
Allowed statements
The for body allows assignments, keep, return, when, and nested loops.
Statements like read, write, show, def, and where are not allowed.
keep supports as aliases.
Comparison with each
for and each overlap, but they are not identical.
Prefer for when the loop is simpler to express through explicit inputs in the
header, without relying on the table diagram.
formakes the loop inputs explicit in the header. This includes loops over several vectors from related tables and integer ranges.fordoes not expose iteration-row columns implicitly. If the body needs a scalar from the iteration table, that value must appear in the header, for exampleKey in Items.Key.eachanchors the loop on one table and exposes the current row through the table diagram. This is often more direct when the body uses several columns from that row or nested blocks that should keep the same row-oriented context.- Both constructs can make the iteration table explicit:
each Tdoes so directly, whilefor _ in T, ...does so withinforsyntax. - A cross-table projection that
eachcan broadcast inside the body, such asT.Z = TCat.Z, must instead be turned into an explicit loop input infor, such asT.Z in TCat.Z. - Some row-dependent operations remain specific to
each. In particular, a lookup with an iteration-dependenttextkey such asSeek.X[Items.Key]is accepted ineachbut rejected infor.