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.

User Contributed Notes
0 notes + add a note