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 02:49]
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>​ 
 +// A single value auto-converts to a one-element Tuple; no AddTupleValue needed. 
 +yearOnly := 2007; 
 + 
 +// A composite key needs one AddTupleValue call per extra element: the first 
 +// argument (2007) auto-converts to a one-element Tuple, and the call appends 
 +// 4534272 to it, producing a two-element Tuple. 
 +yearAndCity := AddTupleValue 2007 4534272; 
 +</​code>​
  
 Every functor below that identifies a row or sub-table by key takes that key as a Tuple. Every functor below that identifies a row or sub-table by key takes that key as a Tuple.
Line 22: 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 35: Line 45:
 | ''​table''​ | Table | Yes | The table to read from. | | ''​table''​ | Table | Yes | The table to read from. |
  
-Output: the full row, as a Tuple.+Output: the row's value columns, as a Tuple — not the key columns, since those are already known from the ''​keys''​ input.
  
 === GetLookupTableValue === === GetLookupTableValue ===
Line 47: Line 57:
  
 Output: the value for that key. Output: the value for that key.
 +
 +**Example** — a small constant table and lookup table, each read with the functor above that matches its shape:
 +
 +<​code>​
 +myLookupTable := LookupTable [
 +    "​Key"​ "​Value",​
 +    1 10,
 +    2 20,
 +    3 30
 +];
 +// lookedUpValue will be 20.
 +lookedUpValue := GetLookupTableValue myLookupTable 2;
 +
 +myTable := Table [
 +    "​CityId*",​ "​Population",​ "​Area",​
 +    1, 667137, 125,
 +    2, 39398, 6
 +];
 +// population will be 667137.
 +population := GetTableValue myTable 1 "​Population";​
 +
 +// wholeRow will be the two-element Tuple (39398, 6) — Population then Area.
 +wholeRow := GetTableRow 2 myTable;
 +</​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. [[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>​
 +myLookupTable := LookupTable [
 +    "​Key"​ "​Value",​
 +    1 10,
 +    2 20,
 +    3 30
 +];
 +
 +_ := ForEach myLookupTable {{
 +    key := Step;
 +    value := GetLookupTableValue myLookupTable key;
 +
 +    message := CreateString "(Key: <v1>, Value: <​v2>​)"​ {{
 +        NumberValue key 1;
 +        NumberValue value 2;
 +    }};
 +
 +    _ := Print { initialMessage = message } {{ }};
 +}};
 +</​code>​
  
 ===== 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 [[Lookup Table]]: 
 + 
 +<​code>​ 
 +myTable := Table [ 
 +    "​CityId*",​ "​Population",​ 
 +    1, 667137, 
 +    2, 39398 
 +]; 
 + 
 +allKeys := GetTableKeys myTable; 
 + 
 +_ := ForEach allKeys {{ 
 +    key := Step; 
 +    value := GetTableValue myTable key "​Population";​ 
 + 
 +    message := CreateString "(Key: <v1>, Value: <​v2>​)"​ {{ 
 +        NumberValue key 1; 
 +        NumberValue value 2; 
 +    }}; 
 + 
 +    _ := Print { initialMessage = message } {{ }}; 
 +}}; 
 +</​code>​ 
 + 
 +**Example** — the same, but with a String-typed key column. This is the extra-indirection case described above: ''​GetTableKeys''​ returns indices, not the original keys, so an extra ''​GetTableValue''​ inside the loop maps each index back to its String key before it can be used: 
 + 
 +<​code>​ 
 +// #string is redundant here — quoted values like "​Boston"​ already infer as String — 
 +// it's included only to make the key column'​s type explicit at a glance. 
 +myStringKeyedTable := Table [ 
 +    "​CityName*#​string",​ "​Population",​ 
 +    "​Boston",​ 667137, 
 +    "​Chelsea",​ 39398 
 +]; 
 + 
 +indexToKey := GetTableKeys myStringKeyedTable;​ 
 + 
 +_ := ForEach indexToKey {{ 
 +    index := Step; 
 +    key := GetTableValue indexToKey index 2; 
 +    value := GetTableValue myStringKeyedTable key "​Population";​ 
 + 
 +    message := CreateString "(Key: <s1>, Value: <​v1>​)"​ {{ 
 +        NumberString key 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; 
 +    }}; 
 + 
 +    _ := Print { initialMessage = message } {{ }}; 
 +}}; 
 +</​code>​
  
 ===== Sub-Tables ===== ===== Sub-Tables =====
Line 85: 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 103: 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.
  
-===== Choosing between Tables ​and Lookup Tables =====+**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:
  
-A Lookup ​Table's fixed shape — one Real keyone Real value — makes it faster to query and iterate than a general Tableand it supports proximity-based lookups (nearest keylinear interpolation) that Tables don't. Prefer a Lookup Table whenever the data actually fits that shapeincluding inside map/value expressionswhere Lookup Table operands add less evaluation overhead than Table operands — see [[calculate_functors#​5_lookup_table_operators|Lookup Table Operators]] and [[calculate_functors#​6_multi-column_table_operators|Multi-Column Table Operators]for how each is queried from within an expression.+<​code>​ 
 +priceTable := Table 
 +    "​Year*"​"​City*"​"​Price"​, 
 +    200445342721200, 
 +    2004, 5483552, 1453, 
 +    2007, 4534272, 4332, 
 +    2007, 5483552, 233 
 +];
  
-Reach for a general Table instead when a single value per key isn't enough — when a row needs more than one associated value, or a String value, or when rows need to be indexed by more than one key at once.+// Retrieve the City*/Price sub-table ​for Year 2007. 
 +pricesIn2007 := GetTableFromKey priceTable 2007;
  
-===== Examples =====+// Replace the price for city 5483552 within that sub-table. 
 +updatedPricesIn2007 :SetTableCellValue pricesIn2007 "​Price"​ 5483552 999;
  
-Both examples below use ''​Print'',​ ''​Step'',​ and ''​CreateString''​ to log each entry to the message console. ''​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)Both examples also use ''​{ initialMessage ​message }''​ [[ego_script#​nominal_syntax|nominal syntax]] for ''​Print'',​ and the ''​_''​ [[ego_script#​positional_syntax|discard convention]] for outputs that aren't needed.+// Write the modified sub-table back under the same key. 
 +priceTable2 :SetTableByKey priceTable 2007 updatedPricesIn2007;​
  
-**Printing ​every entry of a Lookup Table.** The literal below is small constant lookup ​table, built with ''​LookupTable'' ​— see [[ego_script#​constants|Constants]] for the bracket syntax:+// Move City (currently index 2) ahead of Year (index 1), so sub-tables can 
 +// instead be retrieved by City first. 
 +priceTableByCity := ReorderTableColumn priceTable2 "​City"​ 1; 
 +// citySubTable will be the Year*/Price pairs for city 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>​ <​code>​
-myLookupTable ​:= LookupTable ​+priceTable ​:= Table 
-    "Key" "Value", +    "Year*""City*",​ "Price", 
-    ​1 10+    ​2004, 4534272, 1200
-    ​2 20+    ​2004, 5483552, 1453
-    ​3 30+    ​2007, 4534272, 4332, 
 +    2007, 5483552, 233
 ]; ];
  
-_ := ForEach ​myLookupTable ​{{ +yearKeys := GetTableKeys priceTable;​ 
-    ​key := Step; + 
-    ​value := GetLookupTableValue myLookupTable key;+_ := ForEach ​yearKeys ​{{ 
 +    ​year := Step; 
 +    ​pricesInYear ​:= GetTableFromKey priceTable year;
  
-    message := CreateString "(Key: <​v1>, ​Value: <​v2>​)"​ {{ +    ​cityKeys := GetTableKeys pricesInYear;​ 
-        NumberValue ​key 1; + 
-        NumberValue ​value 2;+    _ := 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 } {{ }};
     }};     }};
- 
-    _ := Print { initialMessage = message } {{ }}; 
 }}; }};
 </​code>​ </​code>​
  
-**Printing every entry of a Table** with Real-typed key columnGeneral tables are wrapped in ''​Table'' ​rather than ''​LookupTable'':​+===== Storing results across ​loop ===== 
 + 
 +A [[Mux Table]] or [[Mux Lookup Table]] can carry 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'''​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 ita 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>​ <​code>​
-myTable ​:= Table +sourceLookup ​:= LookupTable ​
-    "CityId*""Population", +    "Key" "Value", 
-    1667137, +    1 667137, 
-    239398+    2 39398
 +    3 181045
 ]; ];
  
-allKeys ​:= GetTableKeys myTable;+emptyResults ​:= Table [ 
 +    "​CityId*#​real",​ "​DoubledPopulation#​real"​ 
 +]; 
 + 
 +_ := ForEach sourceLookup {{ 
 +    accumulated := MuxTable emptyResults nextAccumulated;
  
-_ := ForEach allKeys {{ 
     key := Step;     key := Step;
-    ​value := GetTableValue myTable ​key "​Population"​;+    ​population ​:= GetLookupTableValue sourceLookup ​key
 +    doubled := $ [ $population * 2 ];
  
-    ​message ​:= CreateString "(Key: <v1>, Value: <​v2>​)"​ {{ +    ​newRow ​:= AddTupleValue ​key doubled
-        NumberValue ​key 1+    ​nextAccumulated := AddTableRow accumulated newRow
-        ​NumberValue value 2+}};
-    }};+
  
-    _ := Print { initialMessage = message } {{ }}; +// 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>​ </​code>​
  
-**Printing every entry of a Table with a String-typed key column.** This is the case flagged ​in [[#​iterating_over_table_rows|Iterating over Table rows]] above: ​''​GetTableKeys'' ​returns indicesnot the original keys, so an extra ''​GetTableValue'' ​inside ​the loop maps each index back to its String key before it can be used:+**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 elseinstead 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.
  
-<​code>​ +**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 loopnothing produced inside the loop consumed outside itand 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 itso the loop can never run as anything but sequentialeven when the per-iteration work is otherwise completely independentas it is above (each row's value depends only on that row's own key).
-myStringKeyedTable := Table [ +
-    "​CityName*#​string"​"​Population"​, +
-    "​Boston"​667137, +
-    "​Chelsea"​39398 +
-];+
  
-indexToKey ​:= GetTableKeys myStringKeyedTable;​+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:
  
-_ := ForEach indexToKey {{ +<​code>​ 
-    ​index ​:= Step; +doubledResults ​:= % [ %sourceLookup[line] * "CityId" ​"​DoubledPopulation"​ sourceLookup; 
-    key := GetTableValue indexToKey index 2+</​code>​
-    value := GetTableValue myStringKeyedTable key "Population";+
  
-    message :CreateString "(Key: <s1>, Value: <​v1>​)"​ {{ +===== Choosing between Tables and Lookup Tables =====
-        NumberString key 1; +
-        NumberValue value 1; +
-    }};+
  
-    _ := Print { initialMessage = message } {{ }}; +A Lookup Table'​s fixed shape — one Real key, one Real value — makes it faster to query and iterate than a general Table, and it supports proximity-based lookups (nearest key, linear interpolation) that Tables don't. Prefer a Lookup Table whenever the data actually fits that shape, including inside map/value expressions,​ where Lookup Table operands add less evaluation overhead than Table operands — see [[calculate_functors#​5_lookup_table_operators|Lookup Table Operators]] and [[calculate_functors#​6_multi-column_table_operators|Multi-Column Table Operators]] for how each is queried from within an expression. 
-}}; + 
-</​code>​+Reach for a general Table instead when a single value per key isn't enough — when a row needs more than one associated value, or a String value, or when rows need to be indexed by more than one key at once.