86.19%
Search · Index

V.3 List Operations

A Tcl list holds a sequence of elements, each of which can be a number, a string, or another list. Let's look at the commands for constructing a list:
% # create an empty list using the list command  
% set user_preferences [list]
% # verify that we've created a 0-item list
% llength $user_preferences
0
% lappend user_preferences "hiking"
hiking
% lappend user_preferences "biking"
hiking biking
% lappend user_preferences "whale watching"
hiking biking {whale watching}
% llength $user_preferences
3
At this point, the variable user_preferences is a three-element list. We can pull individual items out with lindex:
% lindex $user_preferences 0
hiking
% lindex $user_preferences 1
biking
% lindex $user_preferences 2
whale watching
% lindex $user_preferences 3
% lindex $user_preferences 5

Note, that  lindex list 0  gives the first element of the list! (Indexing is 0-based and lindex will return the empty string rather than an error if you supply an out-of-range index.)

When producing a page for a user, we'd be more likely to be interested in searching the list. The command lsearch returns the index of the list element matching a query argument or -1 if unsuccessful:

if { [lsearch -exact $user_preferences "hiking"] != -1 } {
# look for new articles related to hiking
}

 

Concat

Suppose that User A marries User B. You want to combine their preferences into a household_preferences variable using the concat command:

% # use the multiple-argument form of list to create an N-element
% # list with one procedure call
% set spouse_preferences [list "programming" "computer games" "slashdot"]
programming {computer games} slashdot
% set household_preferences [concat $user_preferences $spouse_preferences]
hiking biking {whale watching} programming {computer games} slashdot
% llength $household_preferences
6

 

Split and Join

Suppose we have a file called addressees.txt with information about people, one person to a line. Suppose each of these lines contains, among other information, an email address which we assume we can recognize by the presence of an at-sign (@). The following program extracts all the email addresses and joins them together, separated by commas and spaces, to form a string called spam_address that we can use as the Bcc: field of an email message, to spam them all:

# open the file for reading
set addressees_stream [open "~/addressees.txt" r]

# read entire file into a variable
set contents_of_file [read $addressees_stream]

close $addressees_stream

# split the contents on newlines
set list_of_lines [split $contents_of_file "\n"]

# loop through the lines
foreach line $list_of_lines {
if { [regexp {([^ ]*@[^ ]*)} $line one_address] } {
lappend all_addresses $one_address
}
}

# use the join command to mush the list together
set bcc_line_for_mailer [join $all_addresses ", "]
Some things to observe here:
  • We've used the foreach operator (see the chapter on control structure) to iterate over the list formed by splitting the file at newline characters.
  • We use pattern matching to extract the email address from each line. The pattern here specifies "stuff that doesn't contain a space, followed by at-sign, followed by stuff that doesn't contain a space." (See the explanation of regexp in the chapter on pattern matching.)
  • The iteration keeps lappending to all_addresses, but this variable is never initialized. lappend treats an unbound list variable the same as an empty list.



 

Reference: List operations

  • list arg1 arg2 ...
    Construct and return a list the arguments. Akin to (list arg1 arg2...) in Scheme.
    set foo [list 1 2 [list 3 4 5]] ==> 1 2 {3 4 5}
    or   
    set foo {1 2 {3 4 5}}==> 1 2 {3 4 5}
  • lset varName ?index...? newValue
    Gives you the opportunity to insert elements at a given position and work with positions within a list in general.

    .
     

  • lindex list i
    Returns the ith element from list; starts at index 0.

    llength $foo ==> 1

  • llength list
    Returns the number of elements in list.
    llength $foo ==> 3

  • lrange list i j
    Returns the ith through jth elements from list.
    lrange $foo 1 2 ==> 2 {3 4 5}

  • lappend listVar arg arg...
    Append elements to the value of listVar and reset listVar to the new list. Please note that listVar is the name of a variable and not the value, i.e., you should not put a $ in front of it (the same way that set works.
    lappend foo [list 6 7] ==> 1 2 {3 4 5} {6 7}
    set foo ==> 1 2 {3 4 5} {6 7}

  • linsert list index arg arg...
    Insert elements into list before the element at position index. Returns a new list.
    linsert $foo 0 0  ==> 0 1 2 {3 4 5} {6 7}
    
  • lreplace list i j arg arg...
    Replace elements i through j of list with the args. Returns a new list and leaves the original list unmodified.
    lreplace $foo 3 4 3 4 5 6 7 ==> 0 1 2 3 4 5 6 7
    set foo ==> 1 2 {3 4 5} {6 7}
    
  • lsearch mode list value
    Return the index of the element in list that matches the value according to the mode, which is -exact, -glob, or -regexp. -glob is the default. Return -1 if not found.
    set community_colleges [list "caltech" "cmu" "rpi"]
    lsearch -exact $community_colleges "caltech" ==> 0 lsearch -exact $community_colleges "harvard" ==> -1

  • lsort switches list
    Sort elements of the list according to the switches: -ascii, -integer, -real, -increasing, -decreasing, -command command. Returns a new list.
    set my_friends [list "herschel" "schlomo" "mendel"]
    set my_sorted_friends [lsort -decreasing $my_friends] ==> schlomo mendel herschel
  • concat arg arg...
    Join multiple lists together into one list.
    set my_wifes_friends [list "biff" "christine" "clarissa"]
    concat $my_wifes_friends $my_friends ==> biff christine clarissa herschel schlomo mendel

  • join list joinString
    Merge the elements of list to produce a string, where the original list elements are separated by joinString. Returns the resulting string. Note:  if you want to merge without separating the elements, just put "" . 
    set foo_string [join $foo ":"] ==> 0:1:2:3:4:5:6:7

  • split string splitChars
    Split a string to produce a list, using (and discarding) the characters in splitChars as the places where to split. Returns the resulting list.
    set my_ip_address 18.1.2.3 ==> 18.1.2.3
    set ip_elements [split $my_ip_address "."] ==> four element list,
    with values 18 1 2 3

 


 Exercises

 

 1. Write the iota function, which takes a numeric argument n and returns a list of numbers of length n which are the numbers from 0 to n-1.

# iota :: num -> [num]
proc iota {n} {...}

Examples:

iota 3
=> 0 1 2
iota 20
=> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Answer

 

 2. Write the function incrlist, which takes one argument, a list of numbers, and returns a list of equal length as a result, in which each element is the successor to the corresponding element of the argument list.

# incrlist :: [num] -> [num]
proc incrlist {L} {...}
incrlist {34 987 1 567 -23 8}
=> 35 988 2 568 -22 9
incrlist [iota 12]
=> 1 2 3 4 5 6 7 8 9 10 11 12

Hint: Arithmetic is not done directly by the Tcl interpreter. It is done by calling the C library using the expr command on arthmetic expressions.

Does your function work for the empty list?

Answer

 

 3. Write the function strlenlist, which takes one argument, a list of strings, and returns a list of equal length as a result, in which each element is the string length of the corresponding element of the argument list.

# strlenlist :: [str] -> [num]
proc strlenlist {L} {...}
strlenlist {34 987 1 567 -23 8}
=> 2 3 1 3 3 1
strlenlist {foo bar antidisestablishmentarianism}
=> 3 3 28

Does your function work for the empty list? 

Answer

 

 4. Write sumlist, which takes one argument, a list of numbers, and returns, as result, a single number: the sum of the numbers in the argument list.

# sumlist :: [num] -> num
proc sumlist {L} {...}
sumlist [iota 3]
=> 3
sumlist {34 987 1 567 -23 8}
=> 1574

Answer

 

 5. Write multlist, which takes one argument, a list of numbers, and returns, as result, a single number: the product of the numbers in the argument list.

# multlist :: [num] -> num
proc multlist {L} {...}
multlist [iota 3]
=> 0
multlist [incrlist [iota 3]]
=> 3
multlist {34 987 1 567 -23 8}
=> 793928272

Answer

 

 6. Write catlist, which takes one argument, a list of strings, and returns, as result, a single string: the concatenation of the strings in the argument list.

# catlist :: [str] -> str
proc catlist {L} {...}
catlist [iota 3]
=> 012
catlist [incrlist [iota 3]]
=> 123
list {foo bar antidisestablishmentarianism}
=> foobarantidisestablishmentarianism

Answer

 


 

 

 

 

 

Data abstraction with lists

The Tcl shell's output is giving you an ugly insight into the internal representation of lists as strings, with elements being separated by spaces and grouped with braces. There is no reason to rely on how Tcl represents lists or even think about it. Practice data abstraction by using Tcl lists as your underlying storage mechanism but define constructor and accessor procedures that the rest of your source code invokes. You won't find a huge amount of this being done in Web development because the really important data structures tend to be RDBMS tables, but here's an example of how it might work, taken from http://photo.net/philg/careers/four-random-people.tcl. We're building a list of lists. Each sublist contains all the information on a single historical figure. Method A is quick and dirty:

set einstein [list "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]

set mill [list "John Stuart Mill" "English Youth" "Was able to read Greek and Latin at age 3."]

# let's build the big list of lists
set average_folks [list $einstein $mill ...]

# let's pull out Einstein's title
set einsteins_title [lindex $einstein 1]

Method B uses data abstraction:

proc define_person {name title accomplishment} {
return [list $name $title $accomplishment]
}

proc person_name {person} {
return [lindex $person 0]
}

proc person_title {person} {
return [lindex $person 1]
}

proc person_accomplishment {person} {
return [lindex $person 2]
}

% set einstein [define_person "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]
{A. Einstein} {Patent Office Clerk} {Formulated Theory of Relativity.}
% set einsteins_title [person_title $einstein]
Patent Office Clerk

Data abstraction will make building and maintaining a large system much easier. As noted above, however, the stateless nature of HTTP means that any information you want kept around from page to page must be kept by the RDBMS. SQL already forces you to refer to data by table name and column name rather than positionally.

---

based on Tcl for Web Nerds