Tcl: How can I search a nested list in Tcl 8.4?

I have a nested list as follows:

set myList {
   {first apples dollars}
   {second bananas euros}
   {third pears pesos}
}

In Tcl 8.7, I can use lsearch -index 2 (e.g.) if I want to find the type of fruit I'm selling in euros:

set myFruit [lindex $myList [lsearch -index 2 $myList "euros"] 1]
puts $myFruit    
# returns bananas

But how do I do that in Tcl 8.4 where the -index switch doesn't exist? Is there a shortcut, or will I have to use a foreach?

I would like to do this with TCl's native functionality, if possible, and not via any additional packages (which I unfortunately cannot use for my particular project).

3 answers

  • answered 2019-03-13 18:30 B. Shefter

    According to 589332:

    package require Tclx
    
    set somelist {{aaa 1} {bbb 2} {ccc 1}}
    set searchTerm "bbb"
    if {[keylget somelist $searchTerm myvar]} {
        puts "Found instance of $searchTerm, value is: $myvar"
    }
    

    The keylget command in this syntax returns 1 if found, 0 if not. The value next to bbb (2) is then placed in the variable myvar. Note that there should be no dollar sign ($) in front of somelist in the keylget command.

    So for your example that would be:

    set searchTerm "euros"
    if {[keylget myList $searchTerm myFruit]} {
        puts $myFruit
    }
    

    (Not marking this as a duplicate because 589332 didn't deal with v8.4 specifically; someone just happened to mention it in one of the answers.)

  • answered 2019-03-14 05:31 Jerry

    Using the wildcard option of lsearch might be what you are looking for?

    set myList {
       {first apples dollars}
       {second bananas euros}
       {third pears pesos}
    }
    
    set myFruit [lindex $myList [lsearch $myList "*euros*"] 1]
    puts $myFruit
    

    Though the above would also match the word 'neuroscience' for example, so you could use something like the below if that's a possibility:

    lsearch -regexp $myList "\beuros\b"
    

    If it gets more complex, like, if you want to match the entire sub-element (and therefore not want to match euros in the element {forth kiwi "0.5 euros each"} then instead of trying to make the search more flexible, I believe it would be much easier to use a loop:

    for {set i 0} {$i < [llength $myList]} {incr i} {
        if {"euros" in [split [lindex $myList $i]]} {
            set myFruit [lindex $myList $i]
            puts $myFruit
        }
    }
    

  • answered 2019-03-14 14:14 mrcalvin

    This is more an extension to Jerry's answer:

    If going down the regexp route, and you can guarantee that the input list of lists is regular in the sense of a sanitized Tcl-list string representation, then you can have regexp do all the work:

     lindex [regexp -all -inline {([^\s]+)\s+euros\}} $myList] 1
    

    This also covers for the case of multiple sub-lists having euros as elements.

     foreach {_ v}  [regexp -all -inline {([^\s]+)\s+euros\}} $myList] {puts $v}