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/08/03 01:57]
hermann
manipulating_tables_and_lookup_tables [2026/08/18 03:09] (current)
hermann [Printing a table with any number of key columns]
Line 500: Line 500:
 ===== Table Manager ===== ===== Table Manager =====
  
-The accumulator pattern in [[#​storing_results_across_a_loop|Storing results across a loop]] ​builds ​a table one row at a time with a Mux, which forces the loop to run sequentially. **Table Manager** solves ​a related but different ​problembuilding a table with an extra key column from sub-tables produced inside ​a loopwithout ​threading every sub-table ​through a Mux.+The accumulator pattern in [[#​storing_results_across_a_loop|Storing results across a loop]] ​uses Mux to carry a growing ​table from one iteration to the next -- which, as that section'​s closing note explains, ​forces the loop to run sequentially, one iteration at a time. **Table Manager** solves ​the same class of problem ​-- building a table with an extra key column from results computed across ​a loop -- without ​that cost: each iteration registers its own sub-table ​independently,​ with no Mux and nothing threaded between iterations, so the loop producing those partial results stays eligible to run in parallel, per [[basic_data_flow|Basic Data Flow]]. A single [[#​mergesubtables|MergeSubTables]] call combines everything afterward.
  
-Three functors work together:+A container and two functors ​do the core work; a fourth covers a narrower case:
  
 ==== TableManager ==== ==== TableManager ====
Line 540: Line 540:
 Each sub-table'​s ''​keys''​ tuple can hold more than one element; every element becomes its own leading key column on ''​baseTable''​. A ''​tableName''​ can only be merged once -- the merge removes its sub-tables from the manager as it runs. Each sub-table'​s ''​keys''​ tuple can hold more than one element; every element becomes its own leading key column on ''​baseTable''​. A ''​tableName''​ can only be merged once -- the merge removes its sub-tables from the manager as it runs.
  
-> **Warning:​** ''​MergeSubTables''​ has no data dependency on the ''​SubTable''​ calls that feed it -- it reads ''​emptyPriceTable'', ​the literal ''​tableName'',​ and the auto-bound manager, none of which depend on the loop that registered the sub-tables. Placing it after a loop in the script, even inside the same ''​TableManager''​ container, does **not** guarantee it runs after that loop finishes; the two have no connection, so the engine is free to run them in either order or at the same time. Force the ordering explicitly with a [[Group]] gated by the loop's ''​sequenceOutput'',​ as in the example below -- see [[basic_data_flow#​ordering_without_data_sequence_connections|Ordering without data: sequence connections]] and [[ego_script#​sequence_ports|Sequence ports]].+> **Warning:​** ''​MergeSubTables''​ has no data dependency on the ''​SubTable''​ calls that feed it -- its ''​baseTable'' ​inputits literal ''​tableName'',​ and its auto-bound manager ​are all available from the moment the container startsand none of them depend on the loop that registered the sub-tables. Placing it after a loop in the script, even inside the same ''​TableManager''​ container, does **not** guarantee it runs after that loop finishes; the two have no connection, so the engine is free to run them in either order or at the same time. Force the ordering explicitly with a [[Group]] gated by the loop's ''​sequenceOutput'',​ as in the example below -- see [[basic_data_flow#​ordering_without_data_sequence_connections|Ordering without data: sequence connections]] and [[ego_script#​sequence_ports|Sequence ports]].
  
 ==== TableManagerValue ==== ==== TableManagerValue ====
Line 565: Line 565:
         // Stand-in for a per-year computation;​ in practice this might come         // Stand-in for a per-year computation;​ in practice this might come
         // from a database query, a Calculate Table Values call, or another         // from a database query, a Calculate Table Values call, or another
-        // model entirely.+        // model entirely. A Table literal only accepts literal cell values, 
 +        // so a real per-year computation would build this row by row with 
 +        // AddTupleValue and AddTableRow instead, the same as any table 
 +        // whose contents aren't known until the model runs.
         yearPrices := Table [         yearPrices := Table [
             "​City*",​ "​Price",​             "​City*",​ "​Price",​
-            "​Boston", ​$ [ 4000 + $year ]+            "​Boston",​ 4000, 
-            "​Chelsea", ​$ [ 200 + $year ]+            "​Chelsea",​ 200
         ];         ];
  
Line 582: Line 585:
     }};     }};
 }}; }};
- 
-// priceTable is usable here, once the Group it was produced inside -- and 
-// through its sequenceInput,​ the loop that Group was forced to wait for -- 
-// has finished. 
 </​code>​ </​code>​
 +
 +''​priceTable''​ is usable here, once the Group it was produced inside — and through its ''​sequenceInput'',​ the loop that Group was forced to wait for — has finished.
  
 The loop's own iterations are still eligible to run in parallel with each other: nothing inside it is a Mux, nothing inside it is consumed from outside it, and it feeds no submodel output port -- see [[basic_data_flow|Basic Data Flow]]. Only the loop as a whole is sequenced against the merge, which is why this avoids the cost the Mux-accumulator pattern above pays on every single iteration. The loop's own iterations are still eligible to run in parallel with each other: nothing inside it is a Mux, nothing inside it is consumed from outside it, and it feeds no submodel output port -- see [[basic_data_flow|Basic Data Flow]]. Only the loop as a whole is sequenced against the merge, which is why this avoids the cost the Mux-accumulator pattern above pays on every single iteration.
Line 592: Line 593:
 > **Note:** ''​year''​ is passed to ''​SubTable''​ directly, not wrapped in brackets as ''​[year]''​. Bracket syntax is for Tuple **literals**;​ a connected Real value converts to a one-element Tuple automatically on connection, the same pattern used by ''​GetTableFromKey''​ in [[#​sub-tables|Sub-Tables]] above. > **Note:** ''​year''​ is passed to ''​SubTable''​ directly, not wrapped in brackets as ''​[year]''​. Bracket syntax is for Tuple **literals**;​ a connected Real value converts to a one-element Tuple automatically on connection, the same pattern used by ''​GetTableFromKey''​ in [[#​sub-tables|Sub-Tables]] above.
  
-There is no functor exposing ''​TableManager'''​s ''​eraseSubTables''​ or ''​getTableCount''​ methods -- those are internal bookkeeping,​ not part of the functor catalog. 
  
 ===== Printing a table with a generic format ===== ===== Printing a table with a generic format =====
Line 600: Line 600:
 [[Get Table Row]], [[Get Tuple Size]], and [[Get Tuple Value]] make the column-count side of this possible: a row read as a Tuple can be measured with ''​GetTupleSize'',​ and each of its elements retrieved by position, rather than by column name, with ''​GetTupleValue''​. Each retrieved element has type ''​TableValue''​ — a generic type able to hold either a Real or a String. [[Get Table Row]], [[Get Tuple Size]], and [[Get Tuple Value]] make the column-count side of this possible: a row read as a Tuple can be measured with ''​GetTupleSize'',​ and each of its elements retrieved by position, rather than by column name, with ''​GetTupleValue''​. Each retrieved element has type ''​TableValue''​ — a generic type able to hold either a Real or a String.
  
-[[String]] and [[Real Value]] each convert a ''​TableValue''​ the same conditional way: the conversion only succeeds if the ''​TableValue''​ actually holds that type, and raises an error otherwise — converting an already-typed,​ known ''​RealValue''​ or String, by contrast, never fails. ​Attempting the String conversion wrapped ​in [[Skip On Error]] turns that failure into a type test: ''​SkipOnError''​ catches the error rather than aborting the model, ​and its ''​executionCompletedSucessfully''​ output reports whether ​the ''​TableValue'' ​really was String. [[If Then]] and [[If Not Then]] branch on that result and take the matching path. The first uses the converted String directly. The second has already ruled String outso it extracts a ''​RealValue'' ​instead — safe nowsince only Real remains possible — and converts //that// known value to a stringA [[String Junction]] then recombines ​the two branches into one resultsince only one of them ever actually runs.+[[String]] and [[Real Value]] each convert a ''​TableValue''​ the same conditional way: the conversion only succeeds if the ''​TableValue''​ actually holds that type, and raises an error otherwise — converting an already-typed,​ known ''​RealValue''​ or String, by contrast, never fails. ​Wrapping each attempt ​in its own [[Skip On Error]] turns that failure into "​produced nothing"​ instead of aborting the model, ​so only the attempt that actually matches ​the ''​TableValue''​'s real type ever produces ​display string; the other attempt simply fails silently and produces nothing[[String Junction]] then picks whichever one exists. 
 + 
 +> **Note:** A junction always tries its first port before its second — see [[ego_script#​carrying_and_selecting_values_across_iterations|Carrying and selecting values across iterations]] for this rule stated generallysince it applies identically to every Junction functor (''​String'', ​''​Table'',​ ''​Value'',​ ''​Lookup Table'',​ ''​Map'',​ ''​Categorical Map'',​ ''​Folder''​)It makes no practical difference here, though: ​the two attempts below are mutually exclusive by constructionso only one of ''​stringDisplay''/''​realDisplay''​ is ever available for the junction to pick regardless of port order.
  
 <​code>​ <​code>​
Line 626: Line 628:
             displayColumn := $ [ $columnIndex - 1 ];             displayColumn := $ [ $columnIndex - 1 ];
  
-            // Attempt to read the cell as a String; SkipOnError ​catches the +            // Try the cell as a String; SkipOnError ​leaves stringDisplay 
-            // failure ​if it'​s ​actually a Real, rather than aborting. +            // unproduced ​if cellValue is actually a Real, rather than 
-            ​attempt ​:= SkipOnError .yes {{ +            // aborting ​the model
-                ​asString ​:= String cellValue+            ​:= SkipOnError .yes {{ 
-            }}; +                ​stringDisplay ​:= String cellValue;
- +
-            _ := IfThen attempt {{ +
-                stringDisplay := String asString;+
             }};             }};
  
-            ​_ := IfNotThen attempt {{ +            // Try the cell as a Real instead; ​SkipOnError leaves 
-                ​// cellValue was a Real instead; ​RealValue extracts it, and +            // realDisplay unproduced if cellValue is actually a String. 
-                // String ​— safe here, since converting a known Real never +            _ := SkipOnError ​.yes {{
-                // fails — turns it into a display string.+
                 asReal := RealValue cellValue;                 asReal := RealValue cellValue;
                 realDisplay := String asReal;                 realDisplay := String asReal;
             }};             }};
  
-            // Only one of stringDisplay/​realDisplay was actually produced+            // Exactly ​one of the two attempts above ever succeeds
-            // StringJunction picks whichever one has a value.+            // StringJunction picks whichever one was produced.
             cellDisplay := StringJunction stringDisplay realDisplay;​             cellDisplay := StringJunction stringDisplay realDisplay;​
  
Line 1047: Line 1045:
 ]; ];
  
-result := CalculatePythonExpression ​(String ​$"(+result := CalculatePythonExpression $"(
 inputTable = dinamica.inputs['​t1'​] inputTable = dinamica.inputs['​t1'​]
 header = inputTable[0] header = inputTable[0]
Line 1060: Line 1058:
  
 dinamica.outputs['​reshapedTable'​] = dinamica.prepareTable(newTable,​ 1) dinamica.outputs['​reshapedTable'​] = dinamica.prepareTable(newTable,​ 1)
-)"{{+)" {{
     NumberTable myTable 1;     NumberTable myTable 1;
 }}; }};
Line 1082: Line 1080:
  
             // Same generic Real/String printing technique as the earlier             // Same generic Real/String printing technique as the earlier
-            // example: ​attempt String first, SkipOnError ​catches the +            // example: ​try both conversions independentlyeach guarded by 
-            // failure if it'​s ​actually ​a Real, RealValue then converts it+            // its own SkipOnError, and let StringJunction pick whichever 
-            ​attempt ​:= SkipOnError .yes {{ +            // one was actually ​produced
-                ​asString ​:= String cellValue;+            ​:= SkipOnError .yes {{ 
 +                ​stringDisplay ​:= String cellValue;
             }};             }};
  
-            _ := IfThen attempt {{ +            _ := SkipOnError .yes {{
-                stringDisplay := String asString; +
-            }}; +
- +
-            _ := IfNotThen attempt ​{{+
                 asReal := RealValue cellValue;                 asReal := RealValue cellValue;
                 realDisplay := String asReal;                 realDisplay := String asReal;
Line 1118: Line 1113:
  
 Reach for a general Table instead when a single Real value per Real key isn't enough — when a row needs more than one associated value, or a String value, or a String key, or when rows need to be indexed by more than one key at once. A String key column costs more than the type change alone: iterating one means mapping each placeholder index back to its actual key inside the loop, so prefer a Real key column wherever the data allows. Reach for a general Table instead when a single Real value per Real key isn't enough — when a row needs more than one associated value, or a String value, or a String key, or when rows need to be indexed by more than one key at once. A String key column costs more than the type change alone: iterating one means mapping each placeholder index back to its actual key inside the loop, so prefer a Real key column wherever the data allows.
-