Reputation: 7863
I am trying to count the characters in a string and found an easy solution counting a single character using the tr
operator. Now I want to do this with every character from a to z. The following solution doesn't work because tr///
matches every character.
my @chars = ('a' .. 'z');
foreach my $c (@chars)
{
$count{$c} = ($text =~ tr/$c//);
}
How do I correctly use the char variable in tr///
?
Upvotes: 6
Views: 3763
Reputation:
You may want to use s
instead. Substitution is much more powerful than tr
My solution:
$count{$c} =~ s/\$search/$replace/g;
g
at the end means "use it globally".
See:
Upvotes: 0
Reputation: 150021
tr
doesn't support variable interpolation (neither in the search list nor in the replacement list). If you want to use variables, you must use eval()
:
$count{$c} = eval "\$text =~ tr/$c/$c/";
That said, a more efficient (and secure) approach would be to simply iterate over the characters in the string and increment counters for each character, e.g.:
my %count = map { $_ => 0 } 'a' .. 'z';
for my $char (split //, $text) {
$count{$char}++ if defined $count{$char};
}
Upvotes: 6
Reputation: 185550
My solution with some modification based from http://www.perlmonks.org/?node_id=446003
sub lowerLetters {
my $string = shift;
my %table;
@table{split //, $letters_uc} = split //, $letters_lc;
my $table_re = join '|', map { quotemeta } reverse sort keys %table;
$string =~ s/($table_re)/$table{$1}/g;
return if not defined $string;
return $string;
}
Upvotes: 1
Reputation: 37146
tr///
doesn't work with variables unless you wrap it in an eval
But there is a nicer way to do this:
$count{$_} = () = $text =~ /$_/g for 'a' .. 'z';
For the TIMTOWTDI:
$count{$_}++ for grep /[a-z]/i, split //, $text;
Upvotes: 7
Reputation: 27549
If you look at the perldoc for tr/SEARCHLIST/REPLACEMENTLIST/cdsr
, then you'll see, right at the bottom of the section, the following:
Because the transliteration table is built at compile time, neither the SEARCHLIST nor the REPLACEMENTLIST are subjected to double quote interpolation. That means that if you want to use variables, you must use an eval():
eval "tr/$oldlist/$newlist/";
die $@ if $@;
eval "tr/$oldlist/$newlist/, 1" or die $@;
Thus, you would need an eval to generate a new SEARCHLIST.
This is going to be very inefficient... the code might feel neat, but you're processing the complete string 26 times. You're also not counting uppercase characters.
You'd be better off stepping through the string once and just incrementing counters for each character found.
Upvotes: 3
Reputation: 455312
From the perlop documentation:
tr/AAA/XYZ/
will transliterate any A to X.
Because the transliteration table is built at compile time, neither the SEARCHLIST nor the REPLACEMENTLIST are subjected to double quote interpolation. That means that if you want to use variables, you must use an eval()
Alternatively in your case you can use the s///
operator as:
foreach my $c (@chars) {
$count{$c} += ($text =~ s/$c//g);
}
Upvotes: 1