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

For tr at a given pos:
#!/usr/bin/perl -w use strict; my @strings = qw(johnny cash willie nelson); my $pos = 0; for my $i (0..$#strings) { my @tmp = split ('', $strings[$i]); $tmp[$pos] =~ tr/jcwn/cjnw/; $strings[$i] = join ('', @tmp); } print "@strings\n";
Is there a compact one liner for: "Change string at pos (onePos) if pos (onePos) =~ /this/ and change pos (otherPos) if pos (otherPos) =~ /that/ "?

#!/usr/bin/perl -w use strict; my @strings = qw(johnny cash willie nelson); my $pos = 0; for my $i (0..$#strings) { substr($strings[$i],$pos,1) =~ tr/jcwn/cjnw/; } print "@strings\n";
This does it, but only one pos/tr at a time...
Let say you want to tr pos0 /jcwn/cjnw/ and pos 2 /xyz/abc/ within one tr.
Thanks

Replies are listed 'Best First'.
Re: tr at a given pos
by duff (Parson) on Aug 21, 2007 at 21:22 UTC

    I may be missing something, but why not just loop over your positions? Why must it all be "in one tr"?

      I have a high amount of 'strings' to tr. If I input:
      my $index = 0200104;
      It would have to change every 'string' at pos1 tr/02/20/, then at pos4 tr/01/10/, then at pos6 tr/04/40/. And there is quite an amount of 'indexes'. I thought there could be something faster ? Thanks

        What is the bigger picture? It may be that there is a better way of achieving the overall goal than using the technique you are trying to micro-optimize in this thread.

        Have you even done any timing to suggest that the speed of the problem being solved by this code is actually an issue?


        DWIM is Perl's answer to Gödel

        No matter how many lines it takes to write, if you've got to perform an operation on every element, it's (at least) an O(n) process. Give us some more information about the actual data you're converting. Is this something like a big-endian => little-endian conversion? Are the strings of a consistent length? Is it numeric data? In the case you show, it appears you want to change 0200104 to 220000 (tr/// would change the 0 in position 1 to a 2, the 1 in position 4 to a 0, and the 4 in position 6 to a 0). Is this correct?


        emc

        Information about American English usage here and here.

        Any New York City or Connecticut area jobs? I'm currently unemployed.

Re: tr at a given pos
by graff (Chancellor) on Aug 22, 2007 at 07:16 UTC
    As others have said, you need to provide a better explanation of what you are really trying to do, with some real input and real output. But if I take this as the basic goal:
    Is there a compact one liner for: "Change string at pos (onePos) if pos (onePos) =~ /this/ and change pos (otherPos) if pos (otherPos) =~ /that/ "?

    then maybe you don't really want to use tr///. It sounds like you want to avoid a nested loop (foreach string { foreach pos { ... }}), but if you insist on using tr/// (which also requires using substr), there's no other way.

    You could avoid the nested loop by using s/// instead. I don't know whether that would end up being faster than a nested loop with tr/// and substr, but it might be worth a try.

    In any case, the hard part is that you need to define in advance what mappings go with each character position. In a solution with s///, it might go like this:

    #!/usr/bin/perl use strict; my @repl_map; $repl_map[1] = { a => 'k', b => 'l', c => 'm' }; $repl_map[4] = { x => 'i', y => 'j', z => 'k' }; # positions 0,2,3 are undef (to be left unchanged) # positions >=5 (if present) will also be unchanged my $match = "^("; # note "^" to anchor match at offset 0 my $replc = ""; my $captr = 1; for my $pos ( 0 .. $#repl_map ) { if ( not defined( $repl_map[$pos] )) { $match .= "."; } else { warn sprintf( " mapping at pos %d: %s\n", $pos, join( ", ", map { "$_ => $repl_map[$pos]{$_}" } keys %{$repl_map[$pos]} )); $match .= ")([" . join("", sort keys %{$repl_map[$pos]}) . "])("; $replc .= sprintf( "\$%d\$repl_map[%d]{\$%d}", $captr, $pos, $captr+1 ); $captr += 2; } } chop $match; # remove trailing "(" warn " match: $match; replc: $replc\n\n"; my @strings = qw/aaaxxx bbbyyy ccczzz/; print "BEFORE: @strings\n"; for ( @strings ) { eval "s/$match/$replc/"; } print "AFTER: @strings\n";
    (updated to simplify the initialization of @repl_map)

    I stuck the "warn" statements in there just to report how things are being set up. The STDERR output is:

    mapping at pos 1: c => m, a => k, b => l mapping at pos 4: y => j, x => i, z => k match: ^(.)([abc])(..)([xyz]); replc: $1$repl_map[1]{$2}$3$repl_map[ +4]{$4}
    and the STDOUT output is:
    BEFORE: aaaxxx bbbyyy ccczzz AFTER: akaxix blbyjy cmczkz

    UPDATE: There's an important "side-effect" difference between the "single-loop s///" and "nested-loop tr///" approaches:

    With s///, if the initial mapping for editable positions does not account for all possible characters that might occur at each position, then some input strings will not match, and these will not be changed at all. In the script above, input strings like "aaa123" and "ABCxyz" will remain completely as-is (digits and upper-case letters won't match the given mappings, even though part of the string does match).

    With tr/// and substr in a nested loop, the mapping conditions could match at some positions and not at others, so input strings having some "unmapped" content would be partially changed.

    The choice of approach may depend more on how you should handle errors and "boundary conditions", rather than benchmark speed.

Re: tr at a given pos
by Anonymous Monk on Aug 22, 2007 at 05:37 UTC
    this is not exactly a one-liner, and i don't think it's possible to do it "within one tr", but once you have the translation hash table, a lot of possibilities open up.

    use warnings; use strict; sub make_translator { my ($from, $to, ) = @_; # this check may not be needed. # see tr/// docs for effect of unequal strings. die "from `$from' and to `$to' string lengths differ" if length($from) != length($to); return eval "sub { \$_[0] =~ tr{$from}{$to} }"; } my %trans = ( # translation mappings for target string offsets # at this # do this # offset, # translation. # 1 => make_translator ('abc' => 'XYZ'), 1 => sub { $_[0] =~ tr{abc} {XYZ} }, 3 => make_translator ('def' => 'PQR'), 99 => make_translator ('abc' => '987'), 0 => make_translator ('stu' => '*%&'), ); my %test = ( 'kaldm' => 'kXlPm', 'hbiej' => 'hYiQj', 'sytf' => '*ytR', ); # # works - both ST and for-loop variations # for my $word (keys %test) { # # my @chars = split '', $word; # # # map { $trans{$_}->($chars[$_]) } # 2. xlate char at offset. # # grep { $_ < @chars } # 1. that are in word... # # keys %trans; # 0. for all offsets... # # # for all offsets within the word... # for (grep { $_ < @chars } keys %trans) { # $trans{$_}->($chars[$_]); # translate char at offset. # } # # my $translated_word = join '', @chars; # # printf "%-5s -> %s \n", $word, $translated_word; # # print "ERROR: $word badly translated: \n" # . "should be: $test{$word} \n" # . " got: $translated_word \n" # if $translated_word ne $test{$word}; # # } # works - simplest, probably fastest while (my ($word, $translation) = each %test) { my $original = $word; # save copy: word translated in place printf "%-5s -> ", $word; # for all offsets within the word... for my $offset (grep { $_ < length $word } keys %trans) { # translate char in place: 3-arg substr() returns lvalue. # could translate groups of more than one character if # the width of the group could be defined for each # translation. $trans{$offset}->(substr $word, $offset, 1); } print qq($word \n); print "ERROR: $original badly translated: \n" . "should be: $translation \n" . " got: $word \n" if $word ne $translation; }