McMahon has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks...

This of course is easy:

foreach my $item(@array) { next unless ($item =~ "good stuff"); }


Is there an equivalent for map()? This works:

map { die "Bogus" unless m["good stuff"]; #DO STUFF } @array


But this doesn't work, and I understand why it doesn't work-- but I really want it to work =) :
map { next "Bogus" unless m["good stuff"]; #DO STUFF } @array


So: is there a way to exclude elements of an array from map() based on a regex?

Replies are listed 'Best First'.
Re: "foreach" is to "next" as "map" is to ???
by sacked (Hermit) on May 26, 2004 at 16:27 UTC
    You can use the empty list () to prevent a return value from an interation of a map block:
    my @nums= ( 0 .. 10 ); + my @odds= map { $_ % 2 ? $_ : () } @nums; + print "@odds"; __END__ 1 3 5 7 9

    Here is (equivalent) code that uses foreach:
    my @nums= ( 0 .. 10 ); + my @odds; + foreach( @nums ) { next unless $_ % 2; push @odds => $_; } + print "@odds";

    Update: As others have stated, if you are not modifying the list, then you should use grep rather than map. Continuing with the code example from above, a better solution would be:
    my @nums= ( 0 .. 10 ); + my @odds= grep { $_ % 2 } @nums; + print "@odds";

    --sacked
      sacked,
      The empty list trick, while very cool, is not equivalent to next. Your equivalent for code should look like the following instead:
      my @odd; for ( 0 .. 10 ) { push @odd unless $_ % 2; }
      The reason I say this is that a map block is like a modifed subroutine that is applied to each item successively in the input list. Like regular subroutines, the last line of evaluated code is the return value. Unlike regular subroutines, you can not force it to return early by using return.

      The empty list trick is just preventing the map block from returning a value for that input item - it still has to execut every step in the block first. In the case of next in a for loop, it actually jumps to the next iteration without executing the body of the block.

      Cheers - L~R

      Update: While I maintain that your example is not equivalent, it is possible to get the same effect. Placing a conditional where the two branches are an empty list and a block of code at every place you mean next gets the job done.

        The empty list trick, while very cool, is not equivalent to next.
        So, there is a difference? Let's see what that should be.
        The empty list trick is just preventing the map block from returning a value for that input item - it still has to execut every step in the block first.
        Really? Let's see, we take a list of numbers, and for all odd numbers, we print the square of the number, and return the number. The even numbers are skipped. According to your theory, the following code would print the squares of all the numbers:
        my @odds = map {$_ % 2 ? do {print $_ * $_; $_} : ()} 1 .. 10;

        However, if I run it, it only prints the numbers 1, 9, 25, 49 and 81. I guess either my perl is broken, or your theory is false.

        Abigail

        I meant "equivalent" in the sense that @odds is the same after both the map and the foreach.

        As far as equivalent code, I'd argue that this is closer to the map than your for loop using unless:
        my @odds; for ( 0 .. 10 ) { push @odds => $_ % 2 ? $_ : (); }
        In the same way that the original foreach using next does not evaluate the push for every iteration, your code using push/unless does not evaluate the push every iteration.

        --sacked
Re: "foreach" is to "next" as "map" is to ???
by hardburn (Abbot) on May 26, 2004 at 16:20 UTC

    This is exactly what grep is for.

    ----
    send money to your kernel via the boot loader.. This and more wisdom available from Markov Hardburn.

      This is also what returning an empty list is for. grep is just a specialized form of map. Use map when you want to do other work as well.

      grep { TEST } map { TEST ? $_ : () }
        grep is just a specialized form of map

        Not entirely true.

        The difference between grep { TEST } and map { TEST ? $_ : () } is that grep returns a list of aliases to the elements that match TEST. map doesn't do this for obvious reasons - its return can be anything, it's in no way bound to $_.

        For example, if you have an array of numbers and you want to set any negative elements to zero, you can do it with the following code (of course this is not the most common way):

        $ perl @ary = (1, -5, -12, 7, 3); $_ = 0 for grep {$_ < 0} @ary; print "@ary\n"; ^D 1 0 0 7 3

Re: "foreach" is to "next" as "map" is to ???
by Belgarion (Chaplain) on May 26, 2004 at 16:22 UTC

    Hmm, I don't think you can exclude something from inside a map, but you could place a grep before the map call. Basically, the grep would remove anything you didn't want before it's passed to the map function. So, you would have something like:

    @array = map { # do stuff } grep { m/good stuff/ } @array;

    This would pass anything item that matched "good stuff" to the map function for further processing.

    Update After reading through the other comments posted, I guess I should not be surprised that map is more powerful than I had initially expected. I, personally, happen to think the map/grep combo is clearer code in this example, but the additional power of map is certainly nice to know.

Re: "foreach" is to "next" as "map" is to ???
by Abigail-II (Bishop) on May 26, 2004 at 16:40 UTC
    map {m ["good stuff"] ? do { DO STUFF } : ()} @array;

    Abigail

Re: "foreach" is to "next" as "map" is to ???
by blokhead (Monsignor) on May 26, 2004 at 16:29 UTC
    Remember, you can return a list of any size from map's block:
    my @results = map { m/good stuff/ ? do { ... } : () } @array;
    So return an empty list to produce a no-op in map's output. If you don't want the whole thing wrapped in a conditional like this, you can also use return () somewhere in the block as a guard condition in place of next, but you have to mess with the syntax a little:
    my @results = map sub { return () unless m/good stuff/; ... }, @array;
    Note the sub keyword, and the comma after the sub. All in all, it's probably clearest to tack on a grep before the map:
    my @results = map { ... } grep { m/good stuff/ } @array;

    blokhead

Re: "foreach" is to "next" as "map" is to ???
by BrowserUk (Patriarch) on May 26, 2004 at 18:42 UTC

    Reading between the lines (or rather between the nodes) and assuming that you are trying to combine the two loops in this node, then the empty list technique is the most applicable.

    my @sorted = map{ substr $_, 5; }sort { $b cmp $a } map{ m[,\s+(\d+)] ? sprintf '%05d%s', $1, $_ : () } @msgs;

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
      Wow. You make that look so *easy*... =)

      Thanks for jumping in. I've been breaking apart the GRT into little sections, asking questions about various parts. I guess you figured that out.

      There's still lots of stuff here for me to study, but this is definitely what I was trying to get to.

        It's Perl, it's original design and many years of refinement that make this sort of thing easy. A language developed to solve problems rather than comply to some academic or commiteed theory.

        It just takes a while to encounter all the ways to use it, and a bit longer to be able to remember them all. I'm still a relative novice and still learn something new here almost every day.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        You make that look so *easy*...

        Think about map, grep, and sort as operating on lists as a whole. That's what made the functional parts of Perl really click for me. for-loops operate on items that just happen to be bundled in lists. map and grep operate on lists that just happen to have items in them.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: "foreach" is to "next" as "map" is to ???
by Enlil (Parson) on May 26, 2004 at 16:28 UTC
    grep is a good way to do it another way would be:
    @modified_list = map { $_ =~ /good stuff/ : #do stuff here ? () } @array;
    or the map grep combo:
    @modified_list = map { #do stuff here } grep { /good_stuff/ } @array
    -enlil
Re: "foreach" is to "next" as "map" is to ???
by injunjoel (Priest) on May 26, 2004 at 16:24 UTC
    Greetings all,
    Isn't this what grep is for?
    As I understood it map will #DO STUFF for each element of @array and copy the results to whatever called map{}.
    If you don't want a copy for every element in @array use grep{} which will only copy the results based on some condition eg: next "Bogus" unless m["good stuff"]
    Of course this is to the best of my understanding...
    -injunjoel
Re: "foreach" is to "next" as "map" is to ??? (why obfu?)
by tye (Sage) on May 26, 2004 at 17:41 UTC

    It looks like you aren't using the returned list from map. That is one good reason to use foreach instead. Now you also want to use next. From what you've shown, I think the best solution is to use foreach (unless you are going for obfuscation). (:

    - tye        

Re: "foreach" is to "next" as "map" is to ???
by dimar (Curate) on May 26, 2004 at 17:49 UTC
    ### INIT pragma use strict; use warnings; ### PROCESS my @array = <DATA>; map { (0)?() : (/good stuff/) ? do{print "DO STUFF\n";} : (/[^a-z]+/) ? do{print "BAD STUFF: -> $_";} : do{} ### do nothing } @array; ### SAMPLE output q^*******************q DO STUFF BAD STUFF: -> garbage DO STUFF BAD STUFF: -> unknown BAD STUFF: -> BAD STUFF: -> the previous line is blank DO STUFF DO STUFF q********************q^if(0); __DATA__ good stuff garbage good stuff unknown the previous line is blank good stuff good stuff