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

hi
i need to know wich lines of text have specific characters such as if "areqrtyzx" have all the characters a,r,z,x
i have tried
my @a = ('a','r','z','x'); my $mystring = 'areqrtyzx'; for( my $i=0; $i<$#a+1; $i++ ) { if($mystring !~ /$a[$i]/) {print "no match";last;} }
my question is there a consise regex which can detect if a string have all of a predetemined characters?

Replies are listed 'Best First'.
Re: a regex which can detect if a string have all of some characters
by moritz (Cardinal) on May 13, 2008 at 21:21 UTC
    Lookaheads do the tricks, but I wouldn't call them "concise".
    #!/usr/bin/perl use strict; use warnings; use Test::More qw(no_plan); my @a = ('a','r','z','x'); my $re = join '', map { "(?=.*?$_)" } @a; like 'xzarfoobar', qr{$re}; unlike 'zarfoobar', qr{$re};

    (Update: formulated as test script)

      Safer with just a few extra chars: map { "(?=.*?\Q$_\E)" }
        .. or my favorite formulation:
        my $re = join '', map { "(?=.*?$_)" } map { quotemeta } @a;
Re: a regex which can detect if a string have all of some characters
by GrandFather (Saint) on May 13, 2008 at 22:05 UTC

    The usual technique for testing "foundness" is to use a hash. Consider:

    use strict; use warnings; my @a = ('a','r','z','x'); my $match = join '|', @a; my $mystring = 'areqrtyzx'; my %found; $found{$_}++ for grep /$match/, split '', $mystring; print "No match" unless @a == keys %found;

    which also notices how many times each searched for item is found, although that may not be important depending on your application.


    Perl is environmentally friendly - it saves trees
Re: a regex which can detect if a string have all of some characters
by pc88mxer (Vicar) on May 13, 2008 at 21:21 UTC
    Order matters in a regular expression, so I think your only option is to use a loop to check for all of the characters (update: this is essentially the same as your code):
    sub has_all { my $text = shift; for my $c ('a', 'r', 'z', 'x') { return 0 unless $text =~ m/$c/; } 1; }
    Update: Well, I guess I was wrong, and it can be done in a single regex. Now I'm going to have to start thinking about how else I can use lookaheads.
Re: a regex which can detect if a string have all of some characters
by leocharre (Priest) on May 14, 2008 at 03:29 UTC
    Here, I was playing with this.. there are bugs here..
    use Test::Simple 'no_plan'; use strict; use constant DEBUG=> 1; sub debug; sub debug { print STDERR (+shift) ."\n" if DEBUG; } my $string = 'This is a great string here. Really. It even has an @ ch +ar.'; # no.. for my $chars( qw(12345 %!$*& ^ 9 x) ) # interestingly fails for ^ ok( ! string_has_chars($string => $chars ) , " we dont have '$chars +'"); } # yes.. for my $chars( qw(even has an @ char .Really) ){ ok( string_has_chars($string => $chars ) , " we do have '$chars'"); } sub string_has_chars { my $string = shift; my %char; ARG: for my $element (@_){ defined $element or next; if ( ref $element and ref $element eq 'ARRAY' ){ $element="@$element"; } for my $char ( split('',$element) ){ $char{$char}=0; } } my @chars = sort keys %char; debug("chars are [@chars]"); for my $c (@chars){ $string=~/$c/ or return; } return 1; }
    On output..
    leo@dali$ clear; perl /tmp/test.pl 
    
    chars are 1 2 3 4 5
    ok 1 -  we dont have '12345'
    chars are ! $ % & *
    ok 2 -  we dont have '%!$*&'
    chars are ^
    not ok 3 -  we dont have '^'
    #   Failed test ' we dont have '^''
    #   at /tmp/test.pl line 22.
    chars are 9
    ok 4 -  we dont have '9'
    chars are x
    ok 5 -  we dont have 'x'
    chars are e n v
    ok 6 -  we do have 'even'
    chars are a h s
    ok 7 -  we do have 'has'
    chars are a n
    ok 8 -  we do have 'an'
    chars are @
    ok 9 -  we do have '@'
    chars are a c h r
    ok 10 -  we do have 'char'
    chars are . R a e l y
    ok 11 -  we do have '.Really'
    1..11
    # Looks like you failed 1 test of 11.
    
Re: a regex which can detect if a string have all of some characters
by mwah (Hermit) on May 14, 2008 at 06:54 UTC

    The only non-looping/non-map solution I could come up with is the code-assertion counting technique:

    ... # the array positions of the chars are viewed as 'bits' # Bits: 1 1 1 1 ==> decimal 15 my @a = qw'a r z x'; use re 'eval'; our $t = 1; # $t will be modified within the regex # build the regex, essentially: /a(?{...})| b(?{...}) | c(?{...}) ... + /g my $p = 0; # position count variable during initialization my $expr = join'|', map quotemeta($_)."(?{\$t|=".(1<<$p++)."})", @a; # initialize before run $t = 0; my $mystring = 'areqrtyz'; # evaluate the regex in list context () and # look after the result in $t (comma operator =>, ) print 'no match' if () = $mystring =~ /$expr/g, $t != 15; ...

    Regards

    mwa

Re: a regex which can detect if a string have all of some characters
by rovf (Priest) on May 15, 2008 at 09:39 UTC
    Of course aesthetics always depends on personal taste, but I like the following solution, which has a "functional" flavour:
    print( (length(join('',map { $mystring =~ /$_/ ? '' : 'X' } @a)) ? 'not ' : ''), "all letters occur\n");
    The idea is to build a string which consists of as many X as there are missing characters. Of course, if you don't like using =~ - maybe because @a might contain characters which have a special meaning in regexp context - you could also write the block inside the map as:
    index($mystring,$_) < 0 ? 'X' : ''
    -- 
    Ronald Fischer <ynnor@mm.st>