Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

How to use a variable in tr///

by perl-novice (Novice)
on Apr 08, 2005 at 08:24 UTC ( [id://445971]=perlquestion: print w/replies, xml ) Need Help??

perl-novice has asked for the wisdom of the Perl Monks concerning the following question:

How to use a scalar variable as part of transliterate? My perl program asks for the original string and replacement string and the data file to be acted upon. I read my original and replacement string into scalar variables $orig and $replace as well as data filename to $fname How do I carryout the following? $fname =~ tr/$orig/$replace/g I read in the perl docs that it is not possible to use variable as part of tr///. However is there any other way of carrying this out. datafile contents are aaabbbcccddd $orig = 'abc' $replace = 'def' The goal of the program is to replace the characters in the data file such that the new data file contents would be dddeeefffddd Any help will be appreciated.

Replies are listed 'Best First'.
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 $@;
      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.
        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
        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;
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;
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?
      There is no transliteration built into s///g. The OP's example was tr/abc/def/. s/// can't do that without some help (some kind of lookup structure, or embedding a tr command in the eval'd replacement, and in that case, why not just eval?).

      Caution: Contents may have been coded under pressure.
        why not just eval?
        Because with eval, you must worry about delimiters. For example, if either string had contained "/", the eval example given elsewhere in this thread would fail. Also, eval invites disaster in a proxy-privilege environment (like CGI or other daemons).

        The lookup table isn't hard to create:

        ## 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;

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

        Umm... right you are... Silly me :). Usually i just try to stay away from eval as it can be nasty sometimes.
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!

    .{\('v')/}   C H E E R   U P !
     _`(___)' ___a_l_b_e_r_t_o_________
    
    Wherever I lay my KNOPPIX disk, a new FREE LINUX nation could be established.
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.
      show the benchmark code or it didnt happen
        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, });

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (2)
As of 2024-03-19 06:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found