Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re: Replacing a given character starting with the xth occurence in a string

by sharle (Acolyte)
on May 22, 2001 at 03:30 UTC ( [id://82130]=note: print w/replies, xml ) Need Help??


in reply to Replacing a given character starting with the xth occurence in a string

Well, I guess I deserved to lose an experience point for my hastily posted "solution" yesterday. In an effort to redeem myself, here is yet another solution to this problem:

#!/usr/bin/perl -w reg.pl my ($p, @q, $matchchar, $nummatch, $rep, $count, $q, $out); $p = 'Terrence and Phillip are sweet'; $count = 0; $matchchar = "e"; $nummatch = 3; $repchar = "1"; @q = split (/(.*?)/, $p); for($i = 0; $i < $#q + 1; $i++) { $_ = $q[$i]; $count += 1 if (/$matchchar/); if (/$matchchar/ && ($count > $nummatch)) { $q[$i] = $repchar; } } $out = join ("", @q[0 .. $#q]); print "out === $out";

This one works, and solves the correct problem.

sharle

Replies are listed 'Best First'.
Re: Re: Replacing a given character starting with the xth occurence in a string
by ZZamboni (Curate) on May 22, 2001 at 04:09 UTC
    I can see that you put some effort in this program, so I have ++'d your post. Now I'll offer some comments as constructive criticism, and a rewriting of your program to show some more Perlish ways of doing things:
    • The following line:
      @q = split(/(.*?)/, $p);
      Is doing a lot more work than it should. You are actually splitting on empty strings (that's what .*? will always evaluate to) and storing the delimiters (the empty strings), so the string "foo" gets split as ("f", "", "o", "", "o"). If you want to split in individual characters, it's better to do:
      @q = split(//,$p);
      which splits on an empty string, but without the regex, and does not store the delimiters, which you do not need anyway, and does not affect the subsequent code.
    • A matter of style and possibly efficiency: I would much prefer using $i <= $#q as the termination condition in your for. But the really Perlish way of doing it would be:
      foreach (@q) {
      which also automatically assigns $_ for you on each iteration.
    • You don't really need to use regular expressions to do the matching, you could use eq instead, both for clarity and possibly for efficiency.
    • $count++ could be used instead of $count+=1.
    • When you are using $_ inside of a foreach, it becomes an implicit reference to each element of the array, so in this case it has an associated side benefit: you can assign to $_ to modify the array, instead of assigning to $q[$i].
    • The logic could be rearranged to only check against $matchchar once.
    • As per the original specification of the problem, you are to replace starting with the Nth occurrence, so the check should be $count >= $nummatch.
    • You don't need to use @q[0 .. $#q]! Saying @q by itself represents the whole array.
    So here's my first rewrite of the main section of your program:
    @q = split (//, $p); foreach (@q) { $count++ if $_ eq $matchchar; if ($_ eq $matchchar && ($count >= $nummatch)) { $_ = $repchar; } } $out = join ("", @q); print "out === $out";
    Restructuring the insides of the loop, we can get:
    foreach (@q) { if ($_ eq $matchchar) { if (++$count >= $nummatch) { $_ = $repchar; } } }
    Now compressing the two if's, we get:
    foreach (@q) { if ($_ eq $matchchar && ++$count >= $nummatch) { $_ = $repchar; } }
    Now, notice that we are assigning one value to $_ when a certain condition is satisfied, and another (actually leaving its old value) when it's not. So we could use the conditional operator to eliminate the if altogether:
    foreach (@q) { $_ = ($_ eq $matchchar && ++$count >= $nummatch)?$repchar:$_; }
    And now, notice that we are using the foreach to compute a value based on each element of @q. Ideal use of map!
    @q = map { ($_ eq $matchchar && ++$count >= $nummatch)?$repchar:$_ } @q;
    And now we don't need to initially asign the result of the split to @q, because all we are doing with it is passing it as argument to map, so we can do:
    @q = map { ($_ eq $matchchar && ++$count >= $nummatch)?$repchar:$_ } split(//, $p);
    And finally, we can eliminate @q altogether because we can pass the result of the map directly to the join:
    $out = join ("", map { ($_ eq $matchchar && ++$count >= $nummatch)?$repchar:$_ } split (//, $p));
    Proof that any program can be transformed to a one-liner in Perl :-)

    Man that was fun :-)

    --ZZamboni

      This was great! Thanks!

      I spent a huge amount of time trying to figure out how to split on characters, I tried everything except what you used. (I was at work, and so only had the man pages to work from).

      All of your tips were greatly appreciated. I didn't know that foreach automatically assigned $_. Useful knowledge, that.

      I like the result much better than my original, and it was very cool to see how to use map.

      Still Learning, sharle

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://82130]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (6)
As of 2024-03-28 08:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found