Updated 2011-06-01 14:17:59 by AMG

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" "[email protected]"

    #----------------------------------------
    #  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

  • alphabetic: lsort [lcount $list]
  • numeric: lsort -integer -index 1 -decr [lcount $list]

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.