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

I am basically looking for a function that will take 2 lists (call them @a and @b for now) and replace all occurrences of @a in a string ($str) with @b.

Now that may seem simple, but this is done within a loop, so the regular expression keeps getting compiled, providing the same response on each loop. So whatever the first loop returns, the subsequent loops return.

HELP!

You'll see what I mean if you run this code. I have tried everything I can think of. Am I WAY overcomplicating this or is this not as simple as I think?

I have other languages that just have a "replacelist" function - you know... provide a string, with 2 lists, and all occurrences of list1 in string are replaced by list2. Does this exist anywhere?

Code:

#!/usr/bin/perl use strict; my @animals = ( "bear<1>\n", "camel<0> <2>\n", "<2>horse\n", "duck<3>\ +n" ); my @changeTo = ( 'A', 'B', 'C', 'D' ); my @toFind = ( '<0>', '<1>', '<2>', '<3>' ); my $k = 0; while($k < 5) { print map( change(\$_, \@toFind, \@changeTo) , @animals ); my $t = shift(@changeTo); my $r = int(rand(100)); push(@changeTo, $r); print "\n$k:\n" . "@changeTo" . "\n";; $k++; } sub change { my $str = shift; my $toFind = shift; my $changeTo = shift; my @f = @$toFind; my @c = @$changeTo; my $i = 0; foreach my $find (@f) { my $change = $c[$i]; $$str =~ s/$find/$change/gi; $i++; } return $$str; }

Replies are listed 'Best First'.
Re: Regular Expression Loop Hell
by japhy (Canon) on Mar 07, 2006 at 18:29 UTC
    The reason your @animals array never changes after the first loop is because the @animals array has changed to the point that it doesn't HAVE the <1> tags in it anymore. If you didn't use a reference to the elements of the array, you'd be fine:
    print map( change($_, \@toFind, \@toChange), @animals ); ... sub change { my $str = shift; ... { $str =~ ... } return $str; }
    But your loop is dangerous. What if <1>'s "change to" value happens to be this <3> thing? You need to figure out if you want recursive (or chaining) replacements to be allowed. Here's how I'd approach the matter:
    # replace($string, \@from, \@to, $allow_recursion) # recursion is turned off by default sub replace { my ($str, $from, $to, $recurse) = @_; # set up a hash for easy lookup my %replace; @replace{@$from} = @$to; # this is the safe way to create a regex # alternation of the possible "from" strings my $from_rx = join '|', map { quotemeta } sort { length($b) <=> length($a) } @$from; # compile the regex $from_rx = qr/($from_rx)/; # this handles non-recursion # as well as recursion... you # might consider this a hack do { $str =~ s/$from_rx/$replace{$1}/g or $recurse = 0; } while $recurse; return $str; }

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: Regular Expression Loop Hell
by duff (Parson) on Mar 07, 2006 at 18:22 UTC

    From your description, it sounds like you want something like this (mildly tested):

    #!/usr/bin/perl use strict; my @animals = ( "bear<1>\n", "camel<0> <2>\n", "<2>horse\n", "duck<3>\ +n" ); my %changemap = ( "<0>" => 'A', "<1>" => 'B', "<2>" => 'C', "<3>" => 'D', ); # build a single regular expression to match all of the # items we're changing from my $from = join '|', keys %changemap; for (@animals) { s/($from)/$changemap{$1}/g; } print @animals; __END__
    update: removed line in the code that compiled the RE. I don't think it adds anything in the way of understanding.
      As a general precaution, when joining a bunch of terms into a regex alternation series, it's best to sort them by length, longest first, so that, for example, foobar will not be pre-empted by foo. It doesn't make any difference in your example, of course.

      Caution: Contents may have been coded under pressure.
Re: Regular Expression Loop Hell
by QM (Parson) on Mar 07, 2006 at 18:12 UTC
    I haven't grokked your code, but I'd suggest it's as simple as this:
    $string = join '', 'a'..'z'; @find = 'a'..'g'; @change = 'h'..'m'; foreach $i (0..$#find) { $string =~ s/$find[$i]/$change[$i]/g; }

    Update: left off the /g

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

Re: Regular Expression Loop Hell
by Praveen (Friar) on Mar 08, 2006 at 09:12 UTC
    Try This
    my @animals = ( "bear<1>\n", "camel<0> <2>\n", "<2>horse\n", "duck<3>\ +n" ); my @changeTo = ( 'A', 'B', 'C', 'D' ); my @toFind = ( '<0>', '<1>', '<2>', '<3>' ); @hs{@toFind}= (@changeTo); foreach $anm (@animals) { $anm =~ s/$_/$hs{$_}/g, for(keys %hs); print "$anm"; }

      Run this code and you'll see exactly why that code is not helpful. It's not about the regex, it's about the double loop.

      #!/usr/bin/perl -w my @animals = ( "bear<1>\n", "camel<0> <2>\n", "<2>horse\n", "duck<3>\ +n" ); my @changeTo = ( 'A', 'B', 'C', 'D' ); my @toFind = ( '<0>', '<1>', '<2>', '<3>' ); @hs{@toFind}= (@changeTo); $i = 0; while($i < 5) { foreach $anm (@animals) { $anm =~ s/$_/$hs{$_}/g, for(keys %hs); print "$anm"; } shift(@changeTo); push(@changeTo, int(rand(10000))); @hs{@toFind} = (@changeTo); print "\n"; print "@changeTo"; $i++; print "\n\n"; }