Gert Gottschalk
Gert Gottschalk

Reputation: 1716

TCL foreach to keep track of index

When using foreach in TCL to loop through a list it is desired to have a running number of the index of the current object. A way I have done this before is to maintain an extra counter variable.

set ct 0 
foreach x $list {
    puts "Items is $x index is $ct"
    incr ct
}

One could use lsreach to retrieve the index but that's compute intensive and could be problematic with double occurrences.

Wondering if there is streamlined sleek-looking way of maintaining index information during a loop.

Pseudocode :

foreach x $list {
    puts "Items is $x index is [foreach_index $x]"
}

Your feedback is appreciated.

UPDATE:

Run time tests with the provided answers:

  1. Peter Lewerin : 86098.8 microseconds per iteration
  2. Gert : 91057.4 microseconds per iteration
  3. David B : 115860.0 microseconds per iteration

Loop through list with 100k random strings 80char long. The loop proc is fastest, but hard to read.

Upvotes: 2

Views: 8234

Answers (2)

Peter Lewerin
Peter Lewerin

Reputation: 13252

While the control structure definitions in other languages are the law, in Tcl they're more like a set of guidelines.

proc foreachWithIndex {indexVar args} {
    upvar 1 $indexVar var
    set var 0
    uplevel 1 [list foreach {*}[lrange $args 0 end-1] "[lindex $args end];incr $indexVar"]
}

foreachWithIndex x v {a b c} {puts "$x: $v"}

But I suggest using for instead. Radical language modifications are fun and occasionally useful, but if I had an Imperial credit for every such clever construct I ended up throwing away later I could build my own Death Star and still have money to put a grating over the exhaust port.

Upvotes: 4

David B
David B

Reputation: 71

Your method of using incr with a counter works ok. This also works:

for { set i 0 } { $i < [llength $list] } { incr i } {
    set x [lindex $list $i]
    puts "Items is $x index is $i"
}

Another advantage of doing it this way is that you can modify the list while you are iterating. Let's say you want to remove all items with the value "bad" from a list.

set values { 1 2 3 bad 4 bad bad 5 }
for { set i 0 } { $i < [llength $values] } { incr i } {
    if { [lindex $values $i] eq "bad" } {
        set values [lreplace $values $i $i]
        incr i -1
    }
}
puts $values

Upvotes: 3

Related Questions