Re: How to use a variable in tr///
by inman (Curate) on Apr 08, 2005 at 08:43 UTC
|
From the documentation:
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 $@;
# or
eval "tr/$oldlist/$newlist/, 1" or die $@;
| [reply] [d/l] |
|
As merlyn points out, that's not delimiter safe. However, it can be made safe with quotemeta:
eval sprintf "tr/%s/%s/", map quotemeta, $oldlist, $newlist;
Update: of course nobull's construction below is much clearer. It should have occurred to me to say eval "tr/\Q$oldlist\E/\Q$newlist\E/";
Caution: Contents may have been coded under pressure.
| [reply] [d/l] [select] |
|
This is great, but I was confused for a while and tinkered with it. Here's an annotated version:
#! /usr/bin/perl -w
use strict;
my $fname = "aaabbbcccddd";
# Some creepy / characters to prove the quotemeta is doing its job
my $orig = "abc/";
my $repl = "def/";
print "before: $fname\n";
$_ = $fname;
eval sprintf "tr/%s/%s/", map quotemeta, $orig, $repl;
$fname = $_;
print "after: $fname\n";
=for explain
The eval line above was confusing to me at first (and I'm not even
a total newbie). It may help to see it delimited with more
parenthesis:
eval (sprintf ("tr/%s/%s/", map (quotemeta, $orig, $repl)));
From right to left:
The map uses quotemeta as its EXPR and $orig, $repl as its input list
quotemeta is operating on a local $_
The two variables sprintf is expecting are in the list output from map
eval is evaluating the resulting string made by sprintf. The tr/// is
operating on $_
=cut
| [reply] [d/l] |
|
I think I'd probably want to precompile too if this was to be used repeatedly.
my $tr = eval "sub { tr/\Q$oldlist\E/\Q$newlist\E/ }" or die $@;
&$tr for $things, $that, $i, $want, $to, $transliterate;
| [reply] [d/l] |
Re: How to use a variable in tr///
by ysth (Canon) on Apr 08, 2005 at 08:52 UTC
|
Use eval. Or use substitution with a hash instead (untested):
my %translit;
# map chars in $orig to chars in $replace
@translit{split //, $orig} = split //, $replace;
$fname =~ s/(.)/defined($translit{$1}) ? $translit{$1} : $1/eg;
| [reply] [d/l] |
Re: How to use a variable in tr///
by chanio (Priest) on Apr 08, 2005 at 19:00 UTC
|
Two points to remember:
- 1. I shouldn't trust so easily in what others say.
- 2. I should frequently practice with my monitor some oneliners with the examples that I learn...
Why? This is what I have just tryed...
perl -e"$old='abc';$old = tr/a/z/g;print \"$old \n\";"
Bareword found where operator expected at -e line 1, near "tr/a/z/g"
syntax error at -e line 1, near "tr/a/z/g"
Execution of -e aborted due to compilation errors.
There is no tr///g since every replacement is done on all the characters. So the /g would always be unnecesary. Just try it!
| [reply] [d/l] |
Re: How to use a variable in tr///
by Hena (Friar) on Apr 08, 2005 at 10:16 UTC
|
Is there some reason that you don't want to use s///g instead of tr///g? | [reply] |
|
| [reply] |
|
## initialization
my $old = 'abc';
my $new = 'xyz';
my %table; @table{split //, $old} = split //, $new;
my $table_re = join '|', map quotemeta reverse sort keys %table;
## the deed
my $string = 'abracadabra';
$string =~ s/($table_re)/$table{$1}/g;
| [reply] [d/l] |
|
|
Umm... right you are... Silly me :). Usually i just try to stay away from eval as it can be nasty sometimes.
| [reply] |
Re: How to use a variable in tr///
by Anonymous Monk on Jan 15, 2021 at 17:03 UTC
|
Regarding performance, s/(.)/$tr{$1}/eg is about 700 times slower than tr/// for long strings. The solution using eval and its precompiled version have only a constant overhead per invocation, which for very short strings is about 30% for the precompiled version, and about 30 times for eval. I was testing translation of all 256 possible byte values. | [reply] [d/l] [select] |
|
show the benchmark code or it didnt happen
| [reply] |
|
Below is the code. I tried different lengths of strings (upper bound in 1..10000) and number of iterations (first argument to timethese).
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw/cmpthese timethese/;
my $y = join "", 1..10000;
my %tr;
@tr{map chr, 0..255} = map chr, 1..255,0;
my $tr = eval 'sub {$_[0] =~ tr/\x00-\xff/\x01-\xff\x00/r}';
my $trans = sub
{
my $x = $y;
$x =~ tr/\x00-\xff/\x01-\xff\x00/r;
};
my $subst = sub
{
my $x = $y;
$x =~ s/(.)/$tr{$1}/egr;
};
my $eval = sub
{
my $x = $y;
eval '$x =~ tr/\x00-\xff/\x01-\xff\x00/r';
};
my $preceval = sub
{
my $x = $y;
&$tr($x);
};
my $z = &$trans;
$z eq &$subst or die;
$z eq &$eval or die;
$z eq &$preceval or die;
length($y) == length($z) or die;
printf "length: %s\n", length($y);
timethese(0, {
trans => $trans,
subst => $subst,
eval => $eval,
preceval => $preceval,
});
| [reply] [d/l] |