Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
manipulating_tables_and_lookup_tables [2026/07/25 03:32]
hermann
manipulating_tables_and_lookup_tables [2026/07/25 04:29] (current)
hermann
Line 9: Line 9:
 A **Tuple** is a sequence of table cells, most often used to represent the key (or full set of keys) identifying a table row. Tuple elements are ordered by column index and have no names of their own — a Tuple by itself doesn'​t say which column each element belongs to; that mapping only exists in the context of the table it's being used against. A **Tuple** is a sequence of table cells, most often used to represent the key (or full set of keys) identifying a table row. Tuple elements are ordered by column index and have no names of their own — a Tuple by itself doesn'​t say which column each element belongs to; that mapping only exists in the context of the table it's being used against.
  
-A single Real or String value connects directly to any port expecting a Tuple — Dinamica EGO converts it automatically,​ which is why a plain key value can be wired straight into a functor like ''​GetTableValue'' ​without explicitly constructing a Tuple first. A multi-column key has to be built explicitly, one element at a time, by chaining ​''​AddTupleValue'' ​calls — each call appends one value to the Tuple produced by the previous call:+A single Real or String value connects directly to any port expecting a Tuple — Dinamica EGO converts it automatically,​ which is why a plain key value can be wired straight into a functor like [[Get Table Value]] ​without explicitly constructing a Tuple first. A multi-column key has to be built explicitly, one element at a time, by chaining ​[[Add Tuple Value]] ​calls — each call appends one value to the Tuple produced by the previous call:
  
 <​code>​ <​code>​
Line 84: Line 84:
 ===== Iterating over Lookup Table values ===== ===== Iterating over Lookup Table values =====
  
-A [[For Each]] loop run over a connected Lookup Table executes once per key. Inside the loop, a ''​Step'' ​functor retrieves the current key — its input auto-binds to the container'​s current iteration value, the same mechanism described in [[ego_script#​internal_output_ports|Internal output ports]], so it needs no explicit wiring. From there, the key can be used directly, or passed to ''​GetLookupTableValue'' ​to retrieve the corresponding value.+A [[For Each]] loop run over a connected Lookup Table executes once per key. Inside the loop, a [[Step]] functor retrieves the current key — its input auto-binds to the container'​s current iteration value, the same mechanism described in [[ego_script#​internal_output_ports|Internal output ports]], so it needs no explicit wiring. From there, the key can be used directly, or passed to [[Get Lookup Table Value]] ​to retrieve the corresponding value.
  
 A loop like this can often run its iterations in parallel — see [[basic_data_flow|Basic Data Flow]] for the conditions that allow it. A loop like this can often run its iterations in parallel — see [[basic_data_flow|Basic Data Flow]] for the conditions that allow it.
  
-**Example** — printing every entry of a Lookup Table. ​''​Print'' ​is itself a container — its ''​initialMessage''​ is printed before whatever it contains runs, so an empty ''<​nowiki>​{{ }}</​nowiki>''​ block is enough when the only goal is to print something. ​''​CreateString'' ​builds a message from a format string and the values connected inside it, referenced the same way a Code expression references a hook — by a numbered, type-prefixed tag (''<​v1>''​ for the first connected value, and so on). This example also uses ''​{ initialMessage = message }''​ [[ego_script#​nominal_syntax|nominal syntax]] for ''​Print'',​ and the ''​_''​ [[ego_script#​positional_syntax|discard convention]] for outputs that aren't needed:+**Example** — printing every entry of a Lookup Table. ​[[Print]] is itself a container — its ''​initialMessage''​ is printed before whatever it contains runs, so an empty ''<​nowiki>​{{ }}</​nowiki>''​ block is enough when the only goal is to print something. ​[[Create String]] ​builds a message from a format string and the values connected inside it, referenced the same way a Code expression references a hook (see [[ego_script#​verbose_form|Verbose form]]) ​— by a numbered, type-prefixed tag (''<​v1>''​ for the first connected value, and so on). This example also uses ''​{ initialMessage = message }''​ [[ego_script#​nominal_syntax|nominal syntax]] for ''​Print'',​ and the ''​_''​ [[ego_script#​positional_syntax|discard convention]] for outputs that aren't needed:
  
 <​code>​ <​code>​
Line 113: Line 113:
 ===== Iterating over Table rows ===== ===== Iterating over Table rows =====
  
-Table rows are iterated the same way, but need one extra functor: ​''​GetTableKeys'' ​returns a table mapping unique indices to the keys of the input table'​s first key column. When the key column is already Real-typed, those indices and the keys are the same numbers, so the mapping is trivial. When the key column is String-typed,​ the indices are distinct numeric placeholders,​ and a ''​GetTableValue''​ call inside the loop is needed to map the current index back to its actual String key — this indirection is //why// String-typed key columns are usually avoided when a table will be iterated (see [[table_type|Table Type]]).+Table rows are iterated the same way, but need one extra functor: ​[[Get Table Keys]] ​returns a table mapping unique indices to the keys of the input table'​s first key column. When the key column is already Real-typed, those indices and the keys are the same numbers, so the mapping is trivial. When the key column is String-typed,​ the indices are distinct numeric placeholders,​ and a ''​GetTableValue''​ call inside the loop is needed to map the current index back to its actual String key — this indirection is //why// String-typed key columns are usually avoided when a table will be iterated (see [[table_type|Table Type]]).
  
-A table with more than one key column can only be iterated one key column at a time this way. To examine every key column, nest loops — see Sub-Tables below.+A table with more than one key column can only be iterated one key column at a time this way. To examine every key column, nest loops — see [[#​sub_tables|Sub-Tables]] below.
  
-**Example** — printing every entry of a Table with a Real-typed key column. General tables are wrapped in ''​Table'' ​rather than ''​LookupTable''​:+**Example** — printing every entry of a Table with a Real-typed key column. General tables are wrapped in [[Table]] rather than [[Lookup Table]]:
  
 <​code>​ <​code>​
Line 162: Line 162:
         NumberString key 1;         NumberString key 1;
         NumberValue value 1;         NumberValue value 1;
 +    }};
 +
 +    _ := Print { initialMessage = message } {{ }};
 +}};
 +</​code>​
 +
 +**Example** — a table with more than one value column. Nothing new is needed: call ''​GetTableValue''​ once per value column and combine the results:
 +
 +<​code>​
 +myTable := Table [
 +    "​CityId*",​ "​Population",​ "​Area",​
 +    1, 667137, 125,
 +    2, 39398, 6
 +];
 +
 +allKeys := GetTableKeys myTable;
 +
 +_ := ForEach allKeys {{
 +    key := Step;
 +    population := GetTableValue myTable key "​Population";​
 +    area := GetTableValue myTable key "​Area";​
 +
 +    message := CreateString "​(CityId:​ <v1>, Population: <v2>, Area: <​v3>​)"​ {{
 +        NumberValue key 1;
 +        NumberValue population 2;
 +        NumberValue area 3;
     }};     }};
  
Line 193: Line 219:
 Output: the updated table. Output: the updated table.
  
-Both functors ​only operate on the table'​s **leftmost** key column(s) — they can't reach into the middle of a composite key. If the key you need isn't leftmost, reorder the columns first with ''​ReorderTableColumn''​, which moves one column (key or value) to a new index; key and value columns can't be moved across each other.+Both [[Get Table From Key]] and [[Set Table By Key]] only operate on the table'​s **leftmost** key column(s) — they can't reach into the middle of a composite key. If the key you need isn't leftmost, reorder the columns first with [[Reorder Table Column]], which moves one column (key or value) to a new index; key and value columns can't be moved across each other.
  
 **Worked example.** Take a table of commodity prices, keyed by ''​Year''​ and ''​City'':​ **Worked example.** Take a table of commodity prices, keyed by ''​Year''​ and ''​City'':​
Line 211: Line 237:
 To instead retrieve by ''​City''​ first, ''​Year''​ would need to be reordered ahead of it, since ''​GetTableFromKey''​ can only key off the leftmost column(s). With three key columns, the same idea nests: peel off the leftmost key to get a sub-table, then peel off the next leftmost key of //that// sub-table, and so on — which is exactly how nested ''​ForEach''​ loops iterate a multi-key table one column at a time; see [[ego_script#​container_functors|Container functors]] for how nesting containers this way affects execution order. To instead retrieve by ''​City''​ first, ''​Year''​ would need to be reordered ahead of it, since ''​GetTableFromKey''​ can only key off the leftmost column(s). With three key columns, the same idea nests: peel off the leftmost key to get a sub-table, then peel off the next leftmost key of //that// sub-table, and so on — which is exactly how nested ''​ForEach''​ loops iterate a multi-key table one column at a time; see [[ego_script#​container_functors|Container functors]] for how nesting containers this way affects execution order.
  
-**Example** — building the table above, retrieving the 2007 sub-table, updating one of its prices, writing it back, and finally reordering ''​City''​ ahead of ''​Year''​ to key by ''​City''​ instead:+**Example** — building the table above, retrieving the 2007 sub-table, updating one of its prices ​with [[Set Table Cell Value]], writing it back, and finally reordering ''​City''​ ahead of ''​Year''​ to key by ''​City''​ instead:
  
 <​code>​ <​code>​
Line 236: Line 262:
 // citySubTable will be the Year*/Price pairs for city 4534272. // citySubTable will be the Year*/Price pairs for city 4534272.
 citySubTable := GetTableFromKey priceTableByCity 4534272; citySubTable := GetTableFromKey priceTableByCity 4534272;
 +</​code>​
 +
 +**Example** — printing every entry of a table with more than one key column. [[Get Table Keys]] only ever iterates the //first// key column, so a table with several needs one nested loop per extra key: peel off a sub-table for each ''​Year'',​ then iterate the ''​City''​ key within it:
 +
 +<​code>​
 +priceTable := Table [
 +    "​Year*",​ "​City*",​ "​Price",​
 +    2004, 4534272, 1200,
 +    2004, 5483552, 1453,
 +    2007, 4534272, 4332,
 +    2007, 5483552, 233
 +];
 +
 +yearKeys := GetTableKeys priceTable;
 +
 +_ := ForEach yearKeys {{
 +    year := Step;
 +    pricesInYear := GetTableFromKey priceTable year;
 +
 +    cityKeys := GetTableKeys pricesInYear;​
 +
 +    _ := ForEach cityKeys {{
 +        city := Step;
 +        price := GetTableValue pricesInYear city "​Price";​
 +
 +        message := CreateString "​(Year:​ <v1>, City: <v2>, Price: <​v3>​)"​ {{
 +            NumberValue year 1;
 +            NumberValue city 2;
 +            NumberValue price 3;
 +        }};
 +
 +        _ := Print { initialMessage = message } {{ }};
 +    }};
 +}};
 +</​code>​
 +
 +===== Storing results across a loop =====
 +
 +A [[Mux Table]] or [[Mux Lookup Table]] can carry a Table or Lookup Table across the iterations of a loop, the same way [[Mux Value]] carries a single value — see [[ego_script#​carrying_and_selecting_values_across_iterations|Carrying and selecting values across iterations]]. Each iteration reads the mux's current output, adds a row to it with ''​AddTableRow'',​ and feeds the result back in as the mux's ''​feedback''​ input for the next iteration, building up a result table one row at a time.
 +
 +The accumulated table is read after the loop the same way any container'​s internal result is read from outside it: a functor after the loop simply takes the accumulator'​s feedback variable as an input. There'​s nothing special about this — the loop is guaranteed to finish before anything depending on it runs, per [[basic_data_flow|Basic Data Flow]].
 +
 +<​code>​
 +sourceLookup := LookupTable [
 +    "​Key"​ "​Value",​
 +    1 667137,
 +    2 39398,
 +    3 181045
 +];
 +
 +emptyResults := Table [
 +    "​CityId*#​real",​ "​DoubledPopulation#​real"​
 +];
 +
 +_ := ForEach sourceLookup {{
 +    accumulated := MuxTable emptyResults nextAccumulated;​
 +
 +    key := Step;
 +    population := GetLookupTableValue sourceLookup key;
 +    doubled := $ [ $population * 2 ];
 +
 +    newRow := AddTupleValue key doubled;
 +    nextAccumulated := AddTableRow accumulated newRow;
 +}};
 +
 +// finalResults holds every row added across all iterations. Use nextAccumulated,​
 +// not accumulated (the mux's own output), which lags one iteration behind.
 +// The Table carrier passes it through, since := always binds a functor call.
 +finalResults := Table nextAccumulated;​
 +</​code>​
 +
 +> **Note:** This is also why ''​accumulated''​ should never be read again after ''​AddTableRow''​ consumes it in the same iteration. When exactly one reference to a table'​s current version remains, Dinamica can update it destructively — modifying the existing storage in place. A second live reference to ''​accumulated''​ (reading it directly somewhere else, instead of only through ''​nextAccumulated''​) forces Dinamica to copy the table instead, since the old version now has to stay intact for that other reference. That copy cost repeats every iteration — see [[basic_data_flow|Basic Data Flow]] for this same rule stated generally, beyond just tables.
 +
 +**Why this isn't the best approach.** [[basic_data_flow|Basic Data Flow]] documents three conditions under which a loop's iterations can run simultaneously:​ no mux directly inside the loop, nothing produced inside the loop consumed outside it, and no loop member used as a submodel'​s output port. A mux-based accumulator like the one above breaks the first condition outright — the mux ties every iteration to the one before it, so the loop can never run as anything but sequential, even when the per-iteration work is otherwise completely independent,​ as it is above (each row's value depends only on that row's own key).
 +
 +When the transformation is this simple — one output row per input row, computed independently — the calculator shorthand already covered in [[calculate_functors#​5_lookup_table_operators|Lookup Table Operators]] produces the same result without a loop, a mux, or the sequential cost that comes with one:
 +
 +<​code>​
 +doubledResults := % [ %sourceLookup[line] * 2 ] "​CityId"​ "​DoubledPopulation"​ sourceLookup;​
 </​code>​ </​code>​