Version 12 of list map and list grep

Updated 2005-06-22 21:42:52 by dkf

Perhaps I'm just having a bout of Perl envy, but I keep wanting to implement an lmap and lgrep set of procs that do map and grep on lists. Sample usage:

  % set l [list a b c d]
  a b c d

  % lmap l { format %s%s $_ $_ }
  aa bb cc dd

  % lgrep l { string match *d* $_ }
  d

These are easy enough to implement in Tcl:

  proc lmap {listName expr} {
    upvar $listName list
    set res [list]
    foreach _ $list {
      lappend res [eval $expr]
    }
    return $res
  }

  proc lgrep {listName expr} {
    upvar $listName list
    set res [list]
    foreach _ $list {
      if [eval $expr] {
        lappend res $_
      }
    }
    return $res
  }

Does anyone else wish these were part of standard Tcl? Would someone want to write up the TIP to try and get this in?

-- Dossy 13aug2003


RS What you want (and have implemented) are the classic map (function mapping) and filter functions. The term lgrep seems not so good to me, as grep is etymologized as "general regular expression parser", and filter allows much more than that.

However, use of an implicit variable _ is no good Tcl style. I'm afraid one would have to call these with named or anonymous functions (Lambda in Tcl):

 lmap2   [lambda x {format %s%s $x $x}]   $list    ;# pass values, not names!
 lfilter [lambda x {string match *d* $x}] $list

And I think, as easily these are implemented, there's no need to TIP a core change... List comprehension has a fancy, and lambda-less, wrapper for filters:

 % all  i in {1 0 3 -2 4 -4} where {$i>1}
 3 4

Dossy - The reason I called it lgrep is because it's counterpart in Perl is grep -- yes, perhaps a misnomer. Also, $_ is the magic var in Perl that indicates the current element -- not good Tcl style, but I was trying to keep the similarities in place to illustrate my idea for folks who already have Perl background.

Also, after having done Vignette development for a long time, I've gotten really spoiled by the FOREACH proc. Perhaps we could have a similar counterpart, say, lforeach, in the Tcl core:

  proc lforeach {argList list expr} {
    set res [list]
    foreach $argList $list {
      lappend res [eval $expr]
    }
    return $res
  }

Used something like so:

  % join [lforeach x [list 1 2 3 4] {
    expr $x * $x
  }] ,
  1,4,9,16

Hmm, looks a lot like lmap, except it takes multiple args ...


RS Yes, and different arg order. Also, Tcllib since 1.4 has a ::struct::list map command, so standards are just being set.


In Tcl 8.4 you can do:

  interp alias {} lfilter {} lsearch -inline -all
  set l [list qwe ert tyu]
  lfilter $l *e*

Mrb OK, I'm going crazy with all this list stuff. I just want to read in a file (it's always going to have 4 fields per line), and be able to grab a group of lines (a list?), based on the first field (grep field1pat myfile > foo). Then I'd really like to be able to parse through that list, and set a variable to the value of the 4th field. The particular variable I want to set is based on the value of the 2nd field. I have a kluge that does this:

   set searchVar1 [open "|grep field2String foo" r]
   scan [read $searchVar1] "%s %s %s %s" field1 field2 field3 Field4
   close foo

This isn't too bad if you do it once, but I need to do it a bunch of times, for lots of different values. I'd really like to understand how to do it somewhat like a simple shell combination of grep and awk would do it (I looked at the "awk-like tcl" pages, and couldn't make sense of it - it's me I know):

   grep unique_file1 myfile > part_i_need
   for string in (val1 val2 val3 val4... )
   do
   spField1=grep $string part_i_need | awk '{ print $4 }'
   spField2=grep $string part_i_need | awk '{ print $4 }'
   spField3=grep $string part_i_need | awk '{ print $4 }'
   spField4=grep $string part_i_need | awk '{ print $4 }'
   ...

I realize that this is even an overly verbose and klugey way to express the concept, but the fundamental mechanics are there, and it's pretty much what I want to do. I have tried reading the file into an array, but I can't figure out how to read data out of the array. It seems awkward to call out to the shell environment each time I want a value from inside a file. I don't know if this idea even belongs on this page, but "list grep" seems the closest to what I want to do out of all the pages I have looked at. Can anybody help me?

NEM You may be better asking general questions like this on the newsgroup comp.lang.tcl. From what you've said, an array should be fine:

 # Read file into array:
 # Assumes that the first field is unique for each line...
 set fid [open $myfile]
 set contents [split [read $fid] \n]
 foreach line $contents {
     foreach {first second third fourth} [split $line] { break }
     set lookup($first) [list $second $third $fourth]
 }

Now you can lookup lines by the first item key:

 puts "item $tolookfor = $lookup($tolookfor)"

You can use lindex to get the individual parts of each record.


DKF: Did you know that in 8.4, lsearch can act rather much like Perl's grep with only a little encouragement?

  set theList {a b c alpha beta gamma aleph beth}
  set hasAnA [lsearch -all -inline -regexp $theList {a.*a|^b$}]

[ Category Suggestions ]