Version 23 of lsearch

Updated 2005-12-11 09:56:10

http://www.purl.org/tcl/home/man/tcl8.4/TclCmd/lsearch.htm

The simplest invocation form for lsearch is:

 lsearch list search_term

lsearch searches the first argument list for an element that matches the search_term argument.

caspian: lsearch returns -1 if search_term was not found.

RS: Note that in recent versions lsearch has gained many additional options and almost turned into a powerful "search engine":

  • -sorted requires the list to be sorted, and does faster binary search
  • -inline returns the element(s) found (or {} if nothing found, not -1!)
  • -all returns all matches (by index, or with -inline, by value)
  • -regexp matches with a regular expression, not a glob pattern as is the default

So the following call has grep-like functionality, if you search a list of lines:

 lsearch -regexp -inline -all $lines $regexp

A cute little wrapper for the most frequent use case (for me at least) is:

   proc in {list element} {expr [lsearch -exact $list $element] >= 0}

Suppose you have this list:

    set yourlist {RedHat SUSE Debian Knoppix Peanut Mandrake Slackware}

...and you run lsearch to check if a given Linux distribution is present in the list. If it is, it will return the index, i.e. its position in the list.

    lsearch $yourlist RedHat

...would return 0 (zero) because the count starts from zero and "RedHat" is the first item in the list. "SUSE" would return 1 (one) and "Slackware" would return 6 (six).

If the item is not present in the list, the search returns -1 (minus one), so in order to check the presence of an item in the list, we can just check if the return value is a positive number, i.e. greater than zero or zero:

    if  { [ lsearch $yourlist Debian ] >= 0 }   {
         puts "Debian is in the list"
    }  else  { puts "There is no Debian in the list" }

"RedHat" can be a tricky string to search because it is mixed case, so scroll down this page to see how we can run a case-insensitive search within a list. It involves using regular expressions in the search string. (Matthias Hoffmann: But wouldn't it be better to implement a -nocase-operator for those cases to avoid using the 'battleship' regexp for such simple daily cases...????)

If you get to use regular expressions, -inline becomes a very useful switch. It makes lsearch return the element found instead of its index. Or, instead of regular expressions, we can use a simple glob pattern. Run...

    lsearch -inline $yourlist *ware

... and get Slackware instead of a relatively meaningless integer, i.e. the list index.

But note that all searches above will return the first element found. If you want to find all matches, use the -all switch:

    lsearch -all $yourlist *an*

...returns the list {2 4 5}: Debian, Peanut and Mandrake.

Combine -all with -inline and get all names instead of all indices:

    lsearch -all -inline $yourlist *an*

... returns the list {Debian Peanut Mandrake}


The default for lsearch is -glob . However, be certain the glob behavior is what you expect.

For instance, check out this code.

 set a [list field1:val1=field3 field2:val2=2 field3:val3=3 field3]
 set b [lsearch $a field3]
 puts $b

Would you expect glob to mean that element 0, 2, or 3 would be returned? 3 is the answer you get. On the other hand,

 set b [lsearch -regexp $a field3]
 puts $b

returns 0.


Case-insensitive lsearch: Use (?i) in the -regexp for case-insensitive comparison:

 % lsearch -regexp {Foo Bar Grill} (?i)^BAR$
 1

From Tcl 8.4, fancy new modes have been added: -all gives you all instances (instead of the first only); -inline gives the elements themselves instead of their index. So a very easy filter that removes empty sublists from a list is

 lsearch -all -inline $list ?* ;# RS - example:

 % lsearch -all -inline {foo {} bar {} grill} ?*
 foo bar grill

From 8.5 on there will be even more switches, for example TIP 127 [L1 ] -index option.

JMN 2005-12-11 Along with new commands like lrepeat.. this makes Tcl pretty neat for manipulating matrices (nested lists). e.g

 set m [lrepeat 3 [lrepeat 3 0]
 % {0 0 0} {0 0 0} {0 0 0}
 lset $m 1 0 1 ; lset $m 2 0 2
 % {0 0 0} {1 0 0} {2 0 0}

Now you can retrieve in columnwise fashion like this:

 lsearch -all -inline -subindices -index 0 $m *
 %0 1 2

But it seems a bit funny to be using lsearch in 'glob' mode when we are really wanting to do positional access. Without resorting to extensions.. is there a better way?


See also list, lappend, lindex, linsert, llength, lrange, lreplace, lsort . Recursive list searching gives you an index vector which can be used with lindex/lset.


Tcl syntax help - Arts and crafts of Tcl-Tk programming - Category Command