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:25]
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 32: Line 32:
 | ''​table''​ | Table | Yes | The table to read from. | | ''​table''​ | Table | Yes | The table to read from. |
 | ''​keys''​ | Tuple | Yes | The key identifying the row. | | ''​keys''​ | Tuple | Yes | The key identifying the row. |
-| ''​column''​ | name or index | Yes | Which value column to read. An index counts every column left to right, including key columns — so the first value column'​s index is one past the number of key columns, not 1. |+| ''​column''​ | name or index | Yes | Which value column to read. An index counts every column left to right, starting at 1, including key columns — so the first value column'​s index is one past the number of key columns, not 1 (see [[table_type|Table Type]]). |
 | ''​valueIfNotFound''​ | matches the column | No | Returned instead of failing if the key isn't present. | | ''​valueIfNotFound''​ | matches the column | No | Returned instead of failing if the key isn't present. |
  
Line 67: Line 67:
     3 30     3 30
 ]; ];
 +// lookedUpValue will be 20.
 lookedUpValue := GetLookupTableValue myLookupTable 2; lookedUpValue := GetLookupTableValue myLookupTable 2;
-// lookedUpValue is 20 
  
 myTable := Table [ myTable := Table [
Line 75: Line 75:
     2, 39398, 6     2, 39398, 6
 ]; ];
 +// population will be 667137.
 population := GetTableValue myTable 1 "​Population";​ population := GetTableValue myTable 1 "​Population";​
-// population is 667137 
  
 +// wholeRow will be the two-element Tuple (39398, 6) — Population then Area.
 wholeRow := GetTableRow 2 myTable; wholeRow := GetTableRow 2 myTable;
-// wholeRow is the two-element Tuple (39398, 6) — Population then Area 
 </​code>​ </​code>​
  
 ===== 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 234: Line 260:
 // instead be retrieved by City first. // instead be retrieved by City first.
 priceTableByCity := ReorderTableColumn priceTable2 "​City"​ 1; priceTableByCity := ReorderTableColumn priceTable2 "​City"​ 1;
 +// citySubTable will be the Year*/Price pairs for city 4534272.
 citySubTable := GetTableFromKey priceTableByCity 4534272; citySubTable := GetTableFromKey priceTableByCity 4534272;
-// citySubTable is now the Year*/Price pairs for city 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>​