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

Hi Monks,

I have an arrayref that I'd like to split into two arrays: one for elts that match a regex and one for elts that do not match.

Currently, I just do two opposite greps, but I'm sure there's a better way.

my @ok = grep { $_->name =~ m/[0-9][0-9]$/ }@{ $rec->vals }; my @no = grep { $_->name !~ m/[0-9][0-9]$/ }@{ $rec->vals };

Replies are listed 'Best First'.
Re: Splitting array into two with a regex
by Fletch (Bishop) on Nov 08, 2005 at 14:46 UTC
    my( @ok, @no ); push @{ $_->name =~ /\d{2}$/ ? \@ok : \@no }, $_ for @{ $rec->vals }

    Update: Heh, I was going to mention Ruby as well.

      Could you explain what does it mean @{$rec->vals}. Is it array ref? Could you give me an example of datastructure how it will look like. I am eager to learn the concept.

        $rec->vals we can presume is some OO code, but's it's not defined by the OP.

        What Fletch is doing here is testing the value returned by $rec->val, and according to the result, returning a reference to one of the two arrays. This whole things is wrapped in a @{ } which will deference the array ref returned in the first instance, which he can then push the value to.

        Here's a slightly less complicated version, using the same principal

        use strict; use Data::Dumper; my @numbers = (1,2,3,45,6,76,8,5,7,8); my(@odd, @even); foreach my $number (@numbers) { push @{ is_odd($number) ? \@odd : \@even}, $number; } print Dumper(\@odd, \@even); sub is_odd {$_[0] % 2}
        ---
        my name's not Keith, and I'm not reasonable.
        Could you explain what does it mean @{$rec->vals}.

        @{$rec->vals}
        can be written as
        @{$rec->vals()}
        or as
        my $aref = $rec->vals();
        @{$aref}

        Is it array ref?

        $rec->vals() is an expression that returns an array reference.
        @{$rec->vals()} is an expression that returns an array lvalue (which means it can be used like a real array).

        Update: Fixed problem noted by merlyn.

Re: Splitting array into two with a regex
by dragonchild (Archbishop) on Nov 08, 2005 at 14:46 UTC
    This is what Ruby's Array.partition() is for. You can replicate it in Perl pretty easily.
    sub partition (&@) { my ($condition, @array) = @_; my (@true, @false); foreach (@array) { if ( $condition->($_) ) { push @true, $_; } else { push @false, $_; } } return \@true, \@false; } my ($ok, $no) = partition { $_[0]->name =~ m/[0-9]{2}$/ } @{ $rec->val +s };

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      If you're going to replicate it, can you at least make it idiomatically perl? ;-) You have the desired test value in $_ already (good!), but then you go and ruin it by passing it in. Why not just:

      sub partition (&@) { my ($condition, @array) = @_; my (@true, @false); foreach (@array) { if ( $condition->() ) { push @true, $_; } else { push @false, $_; } } return \@true, \@false; } my ($ok, $no) = partition { $_->name =~ m/[0-9]{2}$/ } @{ $rec->vals } +;

      In what is likely to be a common case, where the array is an array of strings rather than object references, that partition block becomes even more trivial. I've taken to this idiom in the last few months just because of the way it simplifies the callbacks. That it is probably faster (no parameters to pass around) actually has had no bearing on whether I use this or not (may shave off a second or two over three hours - not a concern).

        To generalize it even further:

        sub partition (&$@) { my $condition = shift; my $receivers_ar = shift; push @{ $receivers_ar->[ &$condition ] }, $_ for @_; @$receivers_ar } # Example 1: my( @good, @bad ); partition { /u/ ? 1 : 0 } [ \@good, \@bad ], qw( foo bar quux ); print "good=@good\n"; print "bad=@bad\n"; # Example 2: my @r = partition { $_ % 3 } [ [], [], [], ], 0 .. 12; for my $i ( 0 .. $#r ) { print "$i = @{$r[$i]}\n"; }
        We're building the house of the future together.
Re: Splitting array into two with a regex
by Roy Johnson (Monsignor) on Nov 08, 2005 at 15:54 UTC
    Another Way To Do It, with a single grep (and a little trick):
    my @no; my @ok = grep { $_->name =~ m/[0-9][0-9]$/ or !push(@no, $_) }@{ $rec- +>vals };

    Caution: Contents may have been coded under pressure.
      well, if you're going to do that, you might as well stick it on one line...
      my (@ok, @no) = grep { $_->name =~ m/[0-9][0-9]$/ or !push(@no, $_) }@ +{ $rec->vals };
      ... hmmmm, perhaps not :)

      Update: Definitely not. As Roy Johnson kindly pointed out, @no will not exist in time to be pushed to in the grep.
      ---
      my name's not Keith, and I'm not reasonable.
Re: Splitting array into two with a regex
by GrandFather (Saint) on Nov 08, 2005 at 20:24 UTC

    And the benchmark:

    Prints:

    Rate Tanktalus OP JDPorter Roy Fletch + GF Tanktalus 39861/s -- -14% -23% -43% -44% + -57% OP 46391/s 16% -- -11% -34% -35% + -50% JDPorter 51919/s 30% 12% -- -26% -27% + -44% Roy 70510/s 77% 52% 36% -- -1% + -23% Fletch 71157/s 79% 53% 37% 1% -- + -23% GF 91995/s 131% 98% 77% 30% 29% + --

    Update: forgot Roy Johnston's version


    Perl is Huffman encoded by design.
Re: Splitting array into two with a regex
by blahblahblah (Priest) on Nov 09, 2005 at 00:26 UTC
    There's a module on CPAN that does this in a general way, even letting you split the list into more than two result lists: List::Part.

    -Joe

Re: Splitting array into two with a regex
by GrandFather (Saint) on Nov 08, 2005 at 19:53 UTC

    Another way:

    (@ok, @no) = ((), ()); map {$_->name =~ /[0-9][0-9]$/ ? push @ok, $_ : push @no, $_} @{$rec-> +vals};

    Perl is Huffman encoded by design.