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

hello
i want to check if all the letters of a certain set are found in a sentence ,ie: if a set is zxv all the letters z,x,v must be found in the sentence "abxcd zwe rrv" and in this case it is here true my approach is as the following, i need a more smart solution:
thanks
#!/usr/bin/perl use strict; use warnings; my $sentence = "abxcd zwe rrv"; my $wantedLetters = "zxv"; my $flag=0; my @a = split '',$wantedLetters; for( my $i=0; $i<$#a+1; $i++ ) { if($sentence !~ /$a[$i]/) {$flag=1;last;} } if ($flag==1){print "not all wanted letters found"} else {print "all letters are found"}

Replies are listed 'Best First'.
Re: check if all certain chars are found in a sentence
by moritz (Cardinal) on Aug 27, 2008 at 10:28 UTC
    You can build a regex that does the job:
    my $map = 'abc'; my $re = '^' . join '', map "(?=.*?$_)", map quotemeta, split m//, $ma +p; if ('text without all three chars' =~ m/$re/) { print "Failure"; }

    While you might call it elegant, it's probably not the most efficient way for long strings.

      On the other hand, it's quite efficient if you check multiple sentences for the same letters.
Re: check if all certain chars are found in a sentence
by RMGir (Prior) on Aug 27, 2008 at 10:29 UTC
    tallulah, your current answer does the job pretty well.

    You should certainly break out "checkForLetters" as a subroutine, and pass in the sentence and the wanted letters.

    That would make it easier to test your code, which is always a plus.

    You could replace your for(;;) loop with a foreach(@a), which might make things a bit more readable.

    But apart from that, I don't see any major improvements. You could do the whole thing as one grep statement, but I don't think that would add any clarity.

    Edit: In the TMTOWTDI spirit, here's an (almost, except for the split) regex-free implementation:

    sub checkForLetters { my ($sentence, $wantedLetters)=@_; # don't need this variable (or any of them, in # fact -- they're just here for clarity. # we could work straight out of @_ if we wanted # this terser # Also, the $[ check is just pedantic - if someone # changes $[, shoot them. my $foundLetters=scalar (grep index($sentence,$_)>=$[, split //,$wantedLetters); return length($wantedLetters)==$foundLetters; }

    Mike

      I'd probably simplify that a bit, with a possible (minor) speed benefit.

      First, we should conceptualise how to approach the problem. As a human. (My experience with Perl is that it's even better than many other languages at doing what a human does, which improves both readability and reliability.) So, what I would do, as a human, would be to look for each letter in the target string, and if all of them are there, then I have a match. That "all" is a big clue - checking modules for such behaviour, I find it in List::MoreUtils. (Side note: since other monks have pointed me to List::MoreUtils, I've found more and more use for it, so much so that I'm now wishing it were core!)

      The advantage, of course, of using such a tool is that it short-circuits your code (returning at the first failure rather than continuing), and, in this case, it's written in XS, so should be faster than a simple perl duplicate. In the worst case, where all letters are present (or all but the last letter), this should perform basically as well as yours (except I don't need to create a list to count it), and on average, it should perform better. However, that should all be pretty minimal - instead, the real benefit is doing what a human does exactly the same way. And that makes it more likely to work in weird circumstances.

      So, after all that build-up, I had better show some code. Note that I've renamed the function because I like my boolean-returning functions to sound, well, boolean. "Check for letters" doesn't tell me what it returns (sounds like it doesn't have a return value, actually)...

      #! /usr/bin/perl use strict; use warnings; use List::MoreUtils qw(all); sub hasAllLetters { my ($sentence, $letters) = @_; # all we're doing is checking for each letter. all { $sentence =~ $_ } split //, $letters; # same as above, but with index which I think is less readable. #all { index($sentence, $_) >= $[ } split //, $letters; } # tests for my $t ( [ "abxcd zwe rrv", "zxv" ], [ "abxcd zwe rrv", "qxv" ], ) { if (hasAllLetters(@$t)) { print "YES [$t->[0]] ~~ [$t->[1]]\n"; } else { print "NO [$t->[0]] !~ [$t->[1]]\n"; } }
      (If performance was really an issue, I'd not only benchmark it, but I'd use @_ directly instead of giving them readable names.)

        Nice!

        I was shooting for a "base perl" solution, or I would also have reached for List::MoreUtils. You're right, it's a very useful module (and I'm not saying that just because the author sits 10 meters away from me :) ).

        It'd be interesting to benchmark the match vs. index operations. I suspect that benchmarking the match case might actually lead to a useful use of "study"... (Edit: it doesn't: Benchmarking "Are all these characters in this sentence?")


        Mike
Re: check if all certain chars are found in a sentence
by JavaFan (Canon) on Aug 27, 2008 at 10:43 UTC
    I think your approach is basically sound. I would do the details somewhat differently - avoiding some of the temporary variables - but that's just personal preference; I'd still use the same approach. I'd probably write something like:
    use 5.010; my $mess = "all letters are found"; my $_ = "..."; my $letters = "..."; foreach my $l (split //, $letters) { $mess = "not all wanted letters found", last unless /$l/; } say $mess;
Re: check if all certain chars are found in a sentence
by gone2015 (Deacon) on Aug 27, 2008 at 14:43 UTC

    Just for fun, here's a modest alternative:

    sub scan { my ($sentence, $wanted) = @_ ; while (length($wanted)) { return 0 if ($sentence !~ m/([$wanted])/g) ; $wanted =~ s/$1// ; } ; return 1 ; } ;
    which starts by scanning the sentence for any of the wanted characters, and removes each one it finds from the wanted list before scanning from where it left off.

    This may, or may not, be quicker than scanning for each wanted character individually -- indeed, that is probably quicker if you could arrange to scan for unusual characters first.

    Mind you, you didn't say why you need a smarter solution. Or, indeed, what counts as smarter in this context.

Re: check if all certain chars are found in a sentence
by varian (Chaplain) on Aug 27, 2008 at 17:51 UTC
    In the spirit of TMTOWTDI using a hash provides for an alternative quick approach and e.g. could tell you required items that are missing:
    #!/usr/bin/perl use strict; use warnings; my $sentence = "abxcd zwe rrv"; my $wantedLetters = "tzxv"; my %required = map {$_ => 1} split //,$wantedLetters; map delete $required{$_}, split //, $sentence; if (keys %required) { print "required yet missing: ",keys %required,"\n"; } else { print "all requirements were met!\n"; }
      I wonder if that could be re-written w/ hash slices?
      #!/usr/bin/perl use strict; use warnings; my $sentence = "abxcd zwe rrv"; my $wantedLetters = "tzxv"; my %required; @required{split //,$wantedLetters}=(); delete @required{split //, $sentence}; if (keys %required) { print "required yet missing: ",keys %required,"\n"; } else { print "all requirements were met!\n"; }
      Quick test... Yup, that works! Fun...

      Mike
Re: check if all certain chars are found in a sentence
by JadeNB (Chaplain) on Aug 27, 2008 at 17:08 UTC
    Without changing your basic approach, I'd note that index($string, 'literal') is a good option for finding literal strings without having to break out the full power of regexes (although probably the compiler already does essentially this for you); and that also there's the old favourite $count = $string =~ tr/c/, which puts in $count the number of occurrences of the character c in $string.

    UPDATE: Whoops, RMGir already gave the index-based solution.