tags:

views:

136

answers:

3

I need to walk over an array, and conditionally delete elements, in Perl. I know about slice, but am not sure how to use it in a foreach context.

In Ruby, there is reject!:

foo = [2, 3, 6, 7]
foo.reject! { |x| x > 3 }
p foo   # outputs "[2, 3]"

Is there a Perl equivalent?

+17  A: 
@foo = (2, 3, 6, 7);
@foo = grep { $_ <= 3 } @foo;
print @foo; # 23
knittl
Woah, impressive. I thought I was asking a complete beginner's Perl question there, but your answer is your third most upvoted. :o
Shtééf
@Shtééf: beginner's questions are often useful questions.
Andrew Grimm
+4  A: 
vinko@parrot:/home/vinko# more reject.pl
my @numbers = (2, 3, 6 ,7);
print join(",", grep { $_ <= 3 } @numbers)."\n";

vinko@parrot:/home/vinko# perl reject.pl
2,3
Vinko Vrsalovic
+7  A: 

As the other answers suggest, grep is typically all you need.

However, it is possible with perl prototypes to code a function that, like ruby's Array#reject!:

  • accepts a block
  • can modify its argument array in place

Usage is:

@foo = (2, 3, 6, 7);            # Void context - modify @foo in place
reject { $_ > 3 } @foo;         # @foo is now (2, 3)

@foo = (2, 3, 6, 7);            # Scalar context - modify @foo in place
$n = reject { $_ > 3 } @foo;    # @foo is now (2, 3), $n is length of modified @foo

@foo = (2, 3, 6, 7);            # Array context - return a modified copy of @foo
@cpy = reject { $_ > 3 } @foo;  # @cpy is (2, 3), @foo is (2, 3, 6, 7)

Implementation:

sub reject(&\@) {
    my ($block, $ary) = @_;

    # Return a copy in an array context
    return grep {! $block->() } @$ary if wantarray;

    # Otherwise modify in place.  Similar to, but not
    # quite, how rb_ary_reject_bang() does it.
    my $i = 0;
    for (@$ary) {
            next if $block->();
            ($ary->[$i], $_) = ($_, $ary->[$i]); # swap idiom to avoid copying
            $i++;                                # possibly huge scalar
    }
    $#$ary = $i - 1;  # Shorten the input array

    # We differ from Array#reject! in that we return the number of
    # elements in the modified array, rather than an undef value if
    # no rejections were made
    return scalar(@$ary) if defined(wantarray);
}
pilcrow
+1 A Perl pearl.
Konerak
Thanks, @Konerak. Like many others I tend to shy away from prototypes/block-syntax, but this one seemed intuitive and possibly useful. :)
pilcrow
CPAN's List::UtilsBy has an extract_by() function which is like reject above, but it returns the rejected elements (here called extracted elements) instead of a copy of an array that you already have (and which you clearly wanted to work with anyway, else why bother modifying it). I think Leonerd's function is a more useful return value. It also uses an \@ proto.
masonk
@masonk, cool! I'd looked at various List/Array modules on CPAN, but didn't see that one. FWIW, I agree that that style of return is a bit more natural in a perl frame of mind.
pilcrow