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

Dear Monks,

I would like to count the number of occurrences of some separator(s) in a string. For example:

$string='((1,2) (b,c)' separators are '(' and ')'

The number of separators for the string is 5.

I use the tr operator to count the characters. Because the separators may vary and the transliteration table is built at compile time I use an eval. In the future more separators may be added. My code:

use strict; use warnings; sub count_separators { my $value = shift; my $delim = shift; my $set; SWITCH: { if ($delim eq '{') { $set = '{|}'; last SWITCH; } if ($delim eq '(') { $set = '(|)'; last SWITCH; } if ($delim eq '"') { $set = '"'; last SWITCH; } # Should never occur... } print 'sub { return shift =~ tr/' . $set . '//; }' . "\n"; return eval 'sub { return shift =~ tr/' . $set . '//; }'; } my $str = '((1,2)( a,b)'; my $sep = '('; my $result = count_separators ($str, $sep); $result=$result->($str); print "$result \n";

I'm not to happy about the code. I have the feeling it can be done more simple/efficient/... but I'm kinda stuck. Any ideas? Constraint: it has to run under Perl 5.8.8

Thanks upfront!

Replies are listed 'Best First'.
Re: count number of occurrences of specific character(s) in a string
by BrowserUk (Patriarch) on Feb 03, 2009 at 10:50 UTC

    Using ifs in conjunction with last and an anonymous block is just weird. An if/elsif cascade is clearer and more efficient:

    sub count_separators { my $value = shift; my $delim = shift; my $set; if ($delim eq '{') { $set = '{|}'; } elsif ($delim eq '(') { $set = '(|)'; } elsif ($delim eq '"') { $set = '"'; } else { die; # Should never occur... } print 'sub { return shift =~ tr/' . $set . '//; }' . "\n"; return eval 'sub { return shift =~ tr/' . $set . '//; }'; }

    If you really want to use the obscure fabricated switch statement, do it right :)

    sub count_separators { my $value = shift; my $delim = shift; my $set; { $delim eq '{' and $set = '{|}' and last; $delim eq '(' and $set = '(|)' and last; $delim eq '"' and $set = '"' and last; die; # Should never occur... } print 'sub { return shift =~ tr/' . $set . '//; }' . "\n"; return eval 'sub { return shift =~ tr/' . $set . '//; }'; }

    But, given that you have only hardcoded sets, even though you have several of them, bringing eval into the equation is completely unnecessary.

    This is far simpler and more efficient:

    sub count_separators { my $value = shift; my $delim = shift; $delim eq '{' and return $value =~ tr[{|}][]; $delim eq '(' and return $value =~ tr[(|)][]; $delim eq '"' and return $value =~ tr["][]; #"; die; }

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: count number of occurrences of specific character(s) in a string
by holli (Abbot) on Feb 03, 2009 at 10:35 UTC
    $n = () = '((1,2)(b,c)' =~ /[()]/g; print $n;
    Special thanks to tinita on irc. Golf anyone?


    holli

    When you're up to your ass in alligators, it's difficult to remember that your original purpose was to drain the swamp.
Re: count number of occurrences of specific character(s) in a string
by moritz (Cardinal) on Feb 03, 2009 at 10:29 UTC
    I don't get it, why do you include | in the delimiter? Do you also want to count that? it has no special meaning in tr.

    I'd write that as

    sub count_separators { my $value = shift; my $delim = shift; my %map = ( '(' => '()', '{' => '{}', '"' => '"'; ); my $tr = $map{$delim} or die "Not a valid separator"; return eval "\$value =~ tr/$tr/;" }

    (untested).