Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Invert grep match with qr// variable

by Anonymous Monk
on Feb 06, 2024 at 16:21 UTC ( [id://11157579] : perlquestion . print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question: (regular expressions)

I'm trying to get the effect of grep(!/re/, @array) with the /re/ saved in a variable. Nothing that I try seems to work. I don't find that there is any way to place the inversion within the regex variable, so I guess that it needs to go in the grep call. Starting with $tmp=qr/pattern/; I've tried grep(!$tmp, @array); and grep(! $tmp, @array); (that has a space added) and grep {! $tmp} @array;, but no joy. What variation would work?

Replies are listed 'Best First'.
Re: Invert grep match with qr// variable
by Corion (Patriarch) on Feb 06, 2024 at 16:33 UTC

    Purely from a syntax point of view, the following would work:

    my @res = grep { !/$tmp/ } @array;

    There is no good general way to negate a (Perl) regular expression. Negative lookahead/lookbehind like (?! and (?<! will easily fail for (say):

    use 5.020; use Data::Dumper; my $tmp = qr/re/; my @positive = grep {/$tmp/} 'rerere', 'foo'; my @negative = grep {/(?!$tmp)/} 'rerere', 'foo'; say Dumper \@positive; # 'rerere' say Dumper \@negative; # 'rerere', 'foo'

    In the past, whenever I wanted this I've used an external variable for the grep result like this:

    use 5.020; use Data::Dumper; my $tmp = qr/re/; my $reverse = 0; # my @positive = grep {!! /$tmp/ xor $reverse } 'rerere', 'foo'; $reverse = 1; my @negative = grep {!! /$tmp/ xor $reverse } 'rerere', 'foo'; say Dumper \@positive; # 'rerere' say Dumper \@negative; # 'foo'

      There is no good general way to negate a (Perl) regular expression.

      See my answer.

Re: Invert grep match with qr// variable
by hv (Prior) on Feb 06, 2024 at 18:27 UTC

    It is possible to put the inversion in the regexp, but I don't recommend it: it will in almost all cases be much less efficient.

    Here's how it works: $string =~ /$re/ matches if $re matches at any point in the string; $string =~ /(?!$re)/ matches if there is any point in the string that does not match $re; $string =~ /^(?!.*$re)/s therefore matches only if there is no point in the string that matches $re, and is therefore the inverse.

    Inverting outside the pattern just requires one of a couple of ways to ensure the interpreter knows it should do a pattern match:

    grep !/$re/, @array; # use // round $re to treat as a pattern match grep $_ !~ $re, @array; # use a negating match

    Note that if $re is a precompiled regexp, perl won't need to recompile it just because you put // round it.

    Hugo

      • .* should be (?s:.*?).
        • Without /s, it could affect whether some strings match or not.
        • Without the non-greedy, it will affect the value of capture vars ($&, $1, etc) on a match. Not relevant in this case.
      • ^ should be be \G
        • This will affect the number of matches when /g is used. Not relevant in this case.
      Cool. Frontpaged the OP for this answer.
Re: Invert grep match with qr// variable
by ikegami (Patriarch) on Feb 06, 2024 at 22:08 UTC

    You could use

    grep( !/$re/, @array )
    grep( $_ !~ $re, @array )

    What about something within the regex itself?

    /$re/
    is effectively short for
    /\G(?s:.*?)$re/

    This is a form we can negate.

    /\G(?!(?s:.*?)$re)/

    So you could use

    grep( /\G(?!(?s:.*?)$re)/, @array )