[Richard Suchenwirth] 2002-12-06 - Tables are understood here as rectangular (matrix) arrangements of data in rows (one row per "item"/"record") and columns (one column per "field"/"element"). They are for instance the building blocks of relational databases and spreadsheets. In Tcl, a sensible implementation for [compact data storage] would be as a list of lists. This way, they are "pure values" and can be passed e.g. through functions that take a table and return a table. No con-/destructors are needed, in contrast to the heavierweight [matrix] in [Tcllib]. I know there are many table implementations in Tcl, but like so often I wanted to build one "with my bare hands" and as simple as possible. As you see below, many functionalities can be "implemented" by just using Tcl's list functions. A nice table also has a ''header line'', that specifies the field names. So to create such a table with a defined field structure, but no contents yet, one just assigns the header list: set tbl {{firstname lastname phone}} Note the double bracing, which makes sure ''tbl'' is a 1-element list. Adding "records" to the table is as easy as lappend tbl {John Smith (123)456-7890} Make sure the fields (cells) match those in the header. Here single bracing is correct. If a field content contains spaces, it must be quoted or braced too: lappend tbl {{George W} Bush 234-5678} Sorting a table can be done with lsort -index, taking care that the header line stays on top: proc tsort args { set table [lindex $args end] set header [lindex $table 0] set res [eval lsort [lrange $args 0 end-1] [list [lrange $table 1 end]]] linsert $res 0 $header } Removing a row (or contiguous sequence of rows) by numeric index is a job for [lreplace]: set tbl [lreplace $tbl $from $to] Simple printing of such a table, a row per line, is easy with puts [join $tbl \n] Accessing fields in a table is more fun with the field names than the numeric indexes, which is made easy by the fact that the field names are in the first row: proc t@ {tbl field} {lsearch [lindex $tbl 0] $field} % t@ $tbl phone 2 You can then access cells: puts [lindex $tbl $rownumber [t@ $tbl lastname]] and replace cell contents like this: lset tbl $rownumber [t@ $tbl phone] (222)333-4567 Here is how to filter a table by giving pairs of field name and glob-style expression - in addition to the header line, all rows that satisfy at least one of those come through (you can force AND behavior by just nesting such calls): proc trows {tbl args} { set conditions {} foreach {field condition} $args { lappend conditions [t@ $tbl $field] $condition } set res [list [lindex $tbl 0]] foreach row [lrange $tbl 1 end] { foreach {index condition} $conditions { if [string match $condition [lindex $row $index]] { lappend res $row break; # one hit is sufficient } } } set res } % trows $tbl lastname Sm* {firstname lastname} phone {John Smith (123)456-7890} This filters (and, if wanted, rearranges) columns, sort of what is called a "view": proc tcols {tbl args} { set indices {} foreach field $args {lappend indices [t@ $tbl $field]} set res {} foreach row $tbl { set newrow {} foreach index $indices {lappend newrow [lindex $row $index]} lappend res $newrow } set res } ---- See also [matrix], [Tkcon], "[Displaying Tables]", ... See also [starbase] ---- [Category Concept] | [Arts and crafts of Tcl-Tk programming]