4. Arrays

Introduction



set presidents [list Reagan Bush Clinton]
# => Reagan Bush Clinton


set nested [list this that [list the other]]
llength $nested
# => 3


set tune [list The Star-spangled Banner]
list #0 = [lindex $tune 0] #1 = [lindex $tune 1]
# => #0 = The #1 = Star-spangled

Specifying a List In Your Program



set a [list quick brown fox]
# => quick brown fox

set a "Why are you teasing me?"
# => Why are you teasing me?

set lines [list]
foreach {l} [split {
    The boy stood on the burning deck,
    It was as hot as glass.
} \n ] {
    set line [string trimleft $l]
    if {[string length $line]} {
        lappend lines $line
    }
}
puts $lines
# => {The boy stood on the burning deck,} {It was as hot as glass.}

set f [open $mydatafile]          ;# Automatically raises error on failure
set biglist [split [read $f] \n]

lappend banner1 Costs only \$4.95
set banner2 [list Costs only \$4.95]
set banner3 [split {Costs only $4.95}]
expr {"$banner1" == "$banner2" && "$banner2" == "$banner3"}
# => 1

set ships [list Niña Pinta Santa María]   ;# WRONG (4 ships)
llength $ships
# => 4

set ships [list Niña Pinta {Santa María}] ;# right (3 ships)
llength $ships
# => 3

Printing a List with Commas



set list [list red yellow green]
puts [list I have $list marbles.]
# => I have {red yellow green} marbles.

set list [list red yellow green]
puts "I have $list marbles."
# => I have red yellow green marbles.


set lists {
    {{just one thing}}
    {Mutt Jeff}
    {Peter Paul Mary}
    {{to our parents} {Mother Theresa} God}
    {{pastrami} {ham and cheese} {peanut butter and jelly} {tuna}}
    {{recycle tired, old phrases} {ponder big, happy thoughts}}
    {{recycle tired, old phrases}  {ponder big, happy thoughts}  {sleep and dream peacefully}  }
}
proc commifySeries {args} {
    if {[regexp , $args]} {
        set sepchar ";"
    } else {
        set sepchar ,
    }
    # Tcl has a switch command, nyah nyah nyah
    switch [llength $args] {
        0 { return {} }
        1 { eval return $args }
        2 { return [join $args { and }] }
        default {
            set args [lreplace $args end end [concat and [lindex $args end]]]
            return [join $args "$sepchar "]
        }
    }
}

# => just one thing

# => Mutt and Jeff

# => Peter, Paul, and Mary

# => to our parents, Mother Theresa, and God

# => pastrami, ham and cheese, peanut butter and jelly, and tuna

# => recycle tired, old phrases and ponder big, happy thoughts

# => recycle tired, old phrases; ponder big, happy thoughts; and sleep and dream peacefully

Changing Array Size



# There is no equivalent to $#ARRAY in Tcl.



proc whatAboutThatList args {
    variable people
    append res "The list now has [set len [llength $people]] elements.\n"
    append res "The index of the last element is [incr len -1].\n"
    append res "Element #3 is `[lindex $people 3]'."
}

set people [list Crosby Stills Nash Young]
whatAboutThatList
# => The list now has 4 elements.
# => The index of the last element is 3.
# => Element #3 is `Young'.

set people [lrange $people 0 end-1]
whatAboutThatList
# => The list now has 3 elements.
# => The index of the last element is 2.
# => Element #3 is `'.

# append 10001-(length of list) null elements to the list:
for {set i [llength $people]} {$i <= 10000} {incr i} {
    lappend people {}
}
whatAboutThatList
# => The list now has 10001 elements.
# => The index of the last element is 10000.
# => Element #3 is `'.

Doing Something with Every Element in a List



foreach user $badUsers {
    complain $user
}

foreach key [lsort [array names env]] {
    puts $key=$env($key)
}

foreach {user} $allUsers {
    set diskSpace [getUsage $user]
    if {$diskSpace > $MAXQUOTA} {
        complain $user
    }
}

# Tcl has no implicit variables like Perl's $_.
foreach _ [exec who] {
    if [regexp tchrist $_] {
        puts $_
    }
}


# Tcl does not sneak in references unexpectedly.
# If you need to mutate a list, this is the preferred
# idiom:

#     set mylist [mutate $mylist args]

# You *can* 'simulate' manipulation by reference by
# using call-by-name and connecting a local variable
# to a variable with that name in the outer scope:



proc timesSeven {listname} {
    upvar $listname listref
    for {set i 0} {$i < [llength $listref]} {incr i} {
        set listref [lreplace $listref $i $i [expr {[lindex $listref $i] * 7}]]
    }
}

Iterating Over an Array by Reference



variable res {}
set fruits [list Apple Blackberry]
set fruitRef fruits
# the variable fruitRef is now set to the name of the fruit list,
# which makes it a kind of reference variable
foreach fruit [set $fruitRef] {
    append res "$fruit tastes good in a pie.\n"
}
puts $res
# => Apple tastes good in a pie.
# => Blackberry tastes good in a pie.

Extracting Unique Elements from a List



lsort -unique [list how much wood would a wood chuck chuck]
# => a chuck how much wood would


# This is an order of magnitude slower than the previous solution.
foreach e $list {
    array set unique [list $e {}]
}
array names unique
# => a wood much chuck how would

Finding Elements in One Array but Not Another



# Use the TclX standard package (contained in
# the ActiveTcl distribution).
package require Tclx
set listA [list 1 1 2 2 3 3 3 4 5]
set listB [list 1 2 4]
set res [intersect3 $listA $listB]
# [intersect3] yields three result lists;
# we want the first one:
lindex $res 0
# => 3 5

Computing Union, Intersection, or Difference of Unique Lists


# Use the TclX standard package (contained in
# the ActiveTcl distribution).
package require Tclx
set listA [list 1 1 2 2 3 3 3 4 5]
set listB [list 1 2 4 4 6 7]
foreach {difference intersection -} [intersect3 $listA $listB] break
set union [union $listA $listB]
list $difference $intersection $union
# => {3 5} {1 2 4} {1 2 3 4 5 6 7}

Appending One Array to Another



set members [list Time Flies]
lappend members An Arrow
# => Time Flies An Arrow


set members [list Time Flies]
set initiates [list An Arrow]
set members [concat $members $initiates]
# => Time Flies An Arrow


set members [list Time Flies An Arrow]
set members [linsert $members 2 Like]
# => Time Flies Like An Arrow


set members [list Time Flies Like An Arrow]
set members [lreplace $members 0 0 Fruit]
set members [lreplace $members end-1 end A Banana]
# => Fruit Flies Like A Banana

Reversing an Array



set list [list 0 1 2 3 4 5 6 7 8 9]
set rlist [list]
for {set i [expr {[llength $list]-1}]} {$i >= 0} {incr i -1} {
    lappend rlist [lindex $list $i]
}
puts $rlist
# => 9 8 7 6 5 4 3 2 1 0


set list [list 0 1 2 3 4 5 6 7 8 9]
lsort -decreasing $list
# => 9 8 7 6 5 4 3 2 1 0

Processing Multiple Elements of an Array



proc splice-ish {listname number} {
    upvar $listname list
    set length [llength $list]
    if {$number < 0} {
        set number [expr {abs($number)}]
        set res [lrange $list end-[expr {$number-1}] end]
        set list [lrange $list 0 end-$number]
    } else {
        set res [lrange $list 0 [expr {$number-1}]]
        set list [lrange $list $number end]
    }
    return $res
}


proc shift2 {listname} {
    upvar $listname list
    return [splice-ish list 2]
}

set friends [list Peter Paul Mary Jim Tim]
foreach {this that} [shift2 friends] break
list $this $that $friends
# => Peter Paul {Mary Jim Tim}


proc pop2 {listname} {
    upvar $listname list
    return [splice-ish list -2]
}

set beverages [list Dew Jolt Cola Sprite Fresca]
set pair [pop2 beverages]
list $beverages $pair
# => {Dew Jolt Cola} {Sprite Fresca}

Finding the First List Element That Passes a Test



set matchIdx [lsearch $list $criterion]
if {$matchIdx >= 0} {
    set match [lindex $list $matchIdx]
    ## do something with $match
} else {
    ## unfound
}


set matchIdx [lsearch $list $criterion]
if {$matchIdx >= 0} {
    ## found in [lindex $list $matchIdx]
} else {
    ## unfound
}


Employee is an [incr Tcl] class with the members category,
name, salary, ssn, and age.


lappend employees [Employee #auto {manager John 120000 {}}]
lappend employees [Employee #auto {engineer Susie 100000 {}}]
lappend employees [Employee #auto {programmer Harold 90000 {}}]
foreach employee $employees {
    if {[$employee category] eq "engineer"} {
        set highestEngineer $employee
        break
    }
}
$highestEngineer name
# => Susie

Finding All Elements in an Array Matching Certain Criteria



# If the test is matching an element's value against
# an exact string, a wildcard pattern, or a regular
# expression, use the standard package TclX (contained
# in the ActiveTcl distribution).



package require Tclx
set matching [lmatch [list ab ac bc dk ab] a*]
# => ab ac ab


# If another type of test is necessary, or TclX is
# unavailable, a foreach loop is useful:



# TEST could have been a regular proc, of course
interp alias {} TEST {} string match a*
set matching [list]
foreach e [list ab ac bc dk ab] {
    if {[TEST $e]} {
        lappend matching $e
    }
}
set matching
# => ab ac ab

Sorting an Array Numerically



set numsorted [lsort -real [list 38 388.7 1.56 279 1e7]]
# => 1.56 38 279 388.7 1e7


set descending [lsort -decreasing -real [list 38 388.7 1.56 279 1e7]]
# => 1e7 388.7 279 38 1.56

Sorting a List by Computable Field



# Generic code for using a custom comparison in a list sort:

#     set ordered [lsort -command compare $unordered]

# Tcl doesn't have a standard map command as used by the following
# examples.

# Pool (<URL: http://www.purl.org/NET/akupries/soft/pool/index.htm>)
# includes a command, ::pool::list::apply, which is similar to Perl's
# map.



package require Pool_Base
namespace import ::pool::list::apply
set unordered [list 1+7 5-2 3+4]
proc compute e {list [expr $e] $e}
proc second args {lindex $args 1}
set precomputed [apply compute $unordered]
set orderedPrecomputed [lsort -integer -index 0 $precomputed]
set ordered [apply second $orderedPrecomputed]
# => 5-2 3+4 1+7


Employee is an [incr Tcl] class with the members category,
name, salary, ssn, and age.



apply names $employees
# => Betsy Ewan Fran Andy Carl Diane
set ordered [lsort -command Employee::compare-name $employees]
apply names $ordered
# => Andy Betsy Carl Diane Ewan Fran

foreach employee [lsort -command Employee::compare-name $employees] {
    puts "[$employee name] earns \$[$employee salary]"
}
# => Andy earns $110000
# => Betsy earns $120000
# => Carl earns $90000
# => Diane earns $80000
# => Ewan earns $115000
# => Fran earns $110000

set sortedEmployees [lsort -command Employee::compare-name $employees]
foreach employee $sortedEmployees {
    puts "[$employee name] earns \$[$employee salary]"
}
# load bonus array
foreach employee $sortedEmployees {
    if {[info exists bonus([$employee ssn])]} {
        puts "[$employee name] got a bonus!"
    }
}
# => Andy earns $110000
# => Betsy earns $120000
# => Carl earns $90000
# => Diane earns $80000
# => Ewan earns $115000
# => Fran earns $110000
# => Ewan got a bonus!
# => Fran got a bonus!


# The class procedure Employee::compare-name-or-age looks
# like this:

#     proc compare-name-or-age {a b} {
#         set cmp [string compare [[namespace parent]::$a name] [[namespace parent]::$b name]]
#         if {$cmp != 0} {
#             return $cmp
#         } else {
#             return [expr {[[namespace parent]::$a age]-[[namespace parent]::$b age]}]
#         }
#     }



lappend employees [Employee #auto {{} Andy  95000 28}] ;# add another Andy
set sorted [lsort -command Employee::compare-name-or-age $employees]
apply names-and-ages $sorted
# => {Andy 28} {Andy 30} {Betsy 43} {Carl 30} {Diane 27} {Ewan 37} {Fran 35}

Implementing a Circular List



set circular [concat [lrange $list 1 end] [lindex $list 0]]
set circular [concat [lindex $list end] [lrange $list 0 end-1]]


proc grabAndRotate {listname} {
    upvar $listname list
    set first [lindex $list 0]
    set list [concat [lrange $list 1 end] $first]
    return $first
}
while 1 {
    set process [grabAndRotate processes]
    puts "Handling process $process"
    after 1000
}

Randomizing an Array



proc FisherYatesShuffle {listname} {
    upvar $listname list
    for {set i [expr {[llength $list]-1}]} {$i >= 0} {incr i -1} {
        set j [expr {int(rand()*$i+1)}]
        if {$i != $j} {
            set temp [lindex $list $i]
            set list [lreplace $list $i $i [lindex $list $j]]
            set list [lreplace $list $j $j $temp]
        }
    }
}


#   Several shuffle algorithms in Tcl are compared for performance
#   here: <URL: http://mini.net/cgi-bin/nph-wikit/941.html>.
#   This is a very efficient algorithm for small lists:


proc K {x y} {return $x}
 
proc shuffle5a { list } {
    set n 1
    set slist {}
    foreach item $list {
        set index [expr {int(rand()*$n)}]
        set slist [linsert [K $slist [set slist {}]] $index $item]
        incr n
    }
    return $slist
} ;# Christoph Bauer

Program: words

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Program: permute



package require math 1.2
 
# n2pat N len : produce the $N-th pattern of length $len
proc n2pat {N len} {
    set i 1
    set pat [list]
    while {$i <= $len + 1} {
        lappend pat [expr {$N % $i}]
        set N [expr {int($N/$i)}]
        incr i
    }
    return $pat
}
 
# pat2perm pat : turn pattern returned by n2pat into
# permutation of integers.
proc pat2perm {args} {
    if {[llength $args] == 1} {
        set pat [lindex $args 0]
    } else {
        set pat $args
    }
    set source [list]
    for {set i 0} {$i < [llength $pat]} {incr i} {
        lappend source $i
    }
    set perm [list]
    while {[llength $pat]} {
        set i [lindex $pat end]
        set pat [lrange $pat 0 end-1]
        lappend perm [lindex $source $i]
        set source [lreplace $source $i $i]
    }
    return $perm;
}
 
# n2perm N len : generate the $Nth permutation of $len objects
proc n2perm {N len} {
    return [pat2perm [n2pat $N $len]]
}
 
proc main {} {
    while {[gets stdin _] >= 0} {
        set data [split $_]
        set len [llength $data]
        set numPermutations [::math::factorial $len]
        for {set i 0} {$i < $numPermutations} {incr i} {
            set permutation [list]
            foreach {p} [n2perm $i [expr {$len - 1}]] {
                lappend permutation [lindex $data $p]
            }
            puts $permutation
        }
    }
}
 
main