Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Capturing occurrence counts via tr/// with variable interpolation

by KenW (Initiate)
on Aug 15, 2005 at 12:52 UTC ( [id://483832]=perlquestion: print w/replies, xml ) Need Help??

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

I want to count the number of occurrences of various characters in a string. I can do that with $ans=($str=~tr/z//); to count the z's in the string. But I want to change that based on a loop or a command line parameter (e.g., setting $SrchStr=ARGV[0], and doing somethuing like $ans=($str=~tr/$SrchStr//); This doesn't work because of compile-time issues, and supposedly there is a way to do this using eval (), but I don't know how. Does anyone? Or is there another way to accomplish this simple task?
  • Comment on Capturing occurrence counts via tr/// with variable interpolation

Replies are listed 'Best First'.
Re: Capturing occurrence counts via tr/// with variable interpolation
by tlm (Prior) on Aug 15, 2005 at 13:07 UTC
    $ans = eval "\$str =~ tr/$SrchStr/$SrchStr/"; die $@ if $@;

    Update: If you want to count slashes, define $SrchString as '\/', or use a separator other than / for tr. Thanks to Corion for reminding me to point this out.

    the lowliest monk

      Better to just use \Q\E:
      $ans = eval "\$str =~ y/\Q$SrchStr\E//";
      While eval { ... } is Ok, eval " ... " is evil!!!

Re: Capturing occurrence counts via tr/// with variable interpolation
by hv (Prior) on Aug 15, 2005 at 14:38 UTC

    Using eval is perfectly fine if you aren't going to be doing lots of them, but you may run into performance problems if you are.

    I did this the other day: I needed to relabel the digits in a string of [1-4]+ so that it would start "1234". I was calling this routine many times, but since there were only 24 possible transformations I could cache them to avoid repeated evals:

    my %tr; sub normalise { my $s = shift; my $from = substr $s, 0, 4; my $tr = $tr{$from} ||= eval qq{ sub { tr{$from}{1234} } }; &$tr for $s; $s; }

    If you are doing this many times and the character(s) you are counting may cover too many combinations to make memoization appropriate, the best general solution is probably Roy Johnson's approach:

    $count = () = ($s =~ /[$search]/g)

    If you specifically know that a) the $search string is always a single character, and b) that the target string is not too long then BrowserUK's xor approach:

    $count = ($s ^ ($search x length $s)) =~ tr/\0/\0/
    may be better.

    Hugo

Re:Capturing occurrence counts via tr/// with variable interpolation
by BrowserUk (Patriarch) on Aug 15, 2005 at 13:31 UTC

    It's much quicker to avoid the eval by xoring with a string of 'z's and then counting the nulls:

    my $ans = ( $str ^ ( $SrchStr x length $str ) ) =~ tr[\0][\0];

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
      Running that, i get:
      Can't modify bitwise xor (^) in transliteration (tr///) at /tmp/t line + 9, near "tr[\0][\0];"
      Works fine if split into two statements or modified slightly:
      my $ans = ($_=( $str ^ ( $SrchStr x length $str ) )) =~ tr[\0][\0];
      Note that it works IFF length($SrchStr)==1

        Hmm. Works for me?

        #! perl -slw use strict; use Benchmark qw[ cmpthese ]; our $str = 'azbzczdzezfzgzhzizjzkzlzmznzozpzqzrzsztzuzvzwzxzyzzz'; our $SrchStr = 'z'; my $n1 = eval "\$str =~ tr/$SrchStr/$SrchStr/"; my $n2 = ( $str ^ ($SrchStr x length $str ) ) =~ tr[\0][\0]; my $n3 = @{[ $str =~ m/$SrchStr/g ]}; print "$n1 : $n2 : $n3"; cmpthese -1, { eval => q[ my $n = eval "\$str =~ tr/$SrchStr/$SrchStr/" ], xor => q[ my $n = ( $str ^ ($SrchStr x length $str ) ) =~ tr[\0][ +\0]], match=> q[ my $n = @{[ $str =~ m/$SrchStr/g ]} ], }; __END__ [14:50:30.01] P:\test>eval-v-xor.pl 27 : 27 : 27 Rate eval match xor eval 24183/s -- -23% -98% match 31450/s 30% -- -97% xor 1127154/s 4561% 3484% --

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: Capturing occurrence counts via tr/// with variable interpolation
by Roger (Parson) on Aug 15, 2005 at 13:49 UTC
    Use $ans = @{[ $str =~ m/$SrchStr/g ]}; and forget about using eval.

    Your $SrchStr, say, if want to find occurances of 'a' and 'z', should be "[az]".

      The other idiom for getting the count from m// is
      $ans = () = $str =~ m/$SrchStr/g;

      Caution: Contents may have been coded under pressure.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://483832]
Approved by tbone1
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2024-04-18 20:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found