Differences
This shows you the differences between two versions of the page.
| 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> | ||