At this point, the variable% # 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
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
}
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
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:
Some things to observe here:# 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 ", "]
foreach operator (see the chapter on control structure) to iterate over the list formed by splitting the file at newline characters. regexp in the chapter on pattern matching.) lappending to all_addresses, but this variable is never initialized. lappend treats an unbound list variable the same as an empty list.
(list arg1 arg2...) in Scheme. set foo [list 1 2 [list 3 4 5]] ==> 1 2 {3 4 5}orset 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 illength $foo ==> 1
llength listllength $foo ==> 3
lrange list i jlrange $foo 1 2 ==> 2 {3 4 5}
lappend listVar arg arg... 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... linsert $foo 0 0 ==> 0 1 2 {3 4 5} {6 7}
lreplace list i j arg arg... 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 valueset community_colleges [list "caltech" "cmu" "rpi"]
lsearch -exact $community_colleges "caltech" ==> 0 lsearch -exact $community_colleges "harvard" ==> -1
lsort switches listset my_friends [list "herschel" "schlomo" "mendel"]
set my_sorted_friends [lsort -decreasing $my_friends] ==> schlomo mendel herschel
concat arg arg...set my_wifes_friends [list "biff" "christine" "clarissa"]
concat $my_wifes_friends $my_friends ==> biff christine clarissa herschel schlomo mendel
join list joinStringset foo_string [join $foo ":"] ==> 0:1:2:3:4:5:6:7
split string splitCharsset 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
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?
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?
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
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
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
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