Is there a simple way to count the number of matching elements in a list? The answer is "Yes. Several ways."
RWT: And just to keep things interesting, we can compare them in the context of a little test harness that times how quickly they run. There are many times when a Tcl programmer might want to compare a couple of different techniques to see which is fastest. The [time] command can often help. (See the Tcl Performance page for more information on speed improvements.)
#!/bin/sh # restart on the next line using tclsh \ exec tclsh "$0" "$@" #---------------------------------------- # Define procs to test each method for # counting identical list items. This # enables the byte-code compiler to # optimize the code. #---------------------------------------- proc count_members1 list { foreach member $list { if {[info exists count($member)]} { incr count($member) } else { set count($member) 1 } } } proc count_members2 list { foreach x $list { if {[catch {incr count($x)}]} {set count($x) 1} } } proc count_members3 list { foreach x $list { expr {[catch {incr count($x)}] && [set count($x) 1]} } } proc count_members4 list { foreach x $list { lappend ulist($x) {} } foreach name [array names ulist] { set count($name) [llength $ulist($name)] } } proc count_members5 list { foreach x $list { append ulist($x) . } foreach name [array names ulist] { set count($name) [string length $ulist($name)] } } #---------------------------------------- # Create some test data. In this case, # build a list of 10,000 items #---------------------------------------- set items [list john paul jones mary] for {set i 0} {$i<10000} {incr i} { lappend data [lindex $items [expr {int(rand()*[llength $items])}]] } #---------------------------------------- # Print some information about our # environment. This is very useful # when consulting comp.lang.tcl. #---------------------------------------- puts "[info patchlevel] over $tcl_platform(os) $tcl_platform(osVersion)." #---------------------------------------- # Run the tests. # Note that we have cleverly named # the test procs so that [info] can # easily find and execute them. #---------------------------------------- foreach proc [info proc count_members*] { puts "" puts "$proc" puts [time {$proc $data} 10] }
RS 2004-02-20: Note however that the above procs don't really yield their count - the local array is discarded on return. For practical use, I modified count_members4 which was among the fastest in my tests on WinXP, to return a list of {element count} pairs:
proc lcount list { foreach x $list {lappend arr($x) {}} set res {} foreach name [array names arr] { lappend res [list $name [llength $arr($name)]] } return $res } % lcount {yes no no present yes yes no no yes present yes no no yes yes} {no 6} {yes 7} {present 2}
The list is in hash (i.e., apparently no) order, but you can post-process it to
I don't have 8.5 at hand yet, but I expect this dict version to be a good solution too:
proc lcount list { set count {} foreach element $list {dict incr count $element} set count }
gold Here's counting through a list of thrown dice combos for probability. The worker bee is lsearch -all $lister $facen $facen is the sum of a 2-dice throw like 7. Subroutine is invoked by a foreach procedure for probability, which would be number of thrown 7's over all possible throws ($lister). Drop (-all) and lose all, returning only one position of "7" in list. See Binomial Probability Slot Calculator Example.
console show proc calculation { facen } { # prob. subroutines for two 6-sided dice set lister {2 3 4 5 6 7 3 4 5 6 7 8 4 5 6 / 7 8 9 5 6 7 8 9 10 6 7 8 9 10 11 7 8 9 10 11 12} set ee [llength $lister ] set kk [ llength [ lsearch -all $lister $facen ] ] set prob [ expr { ($kk*1.) / $ee } ] return $prob } set limit 12 for { set i 1 } { $i <= $limit } { incr i } { lappend listxxx $i lappend listxxx [ calculation $i ] puts " $i [ calculation $i ] " } #end
See Chart of proposed list functionality too.