note
ZZamboni
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:
<ul>
<li> The following line:
<code>
@q = split(/(.*?)/, $p);
</code>
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:
<code>
@q = split(//,$p);
</code>
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.
<li> A matter of style and possibly efficiency: I would
much prefer using <tt>$i <= $#q</tt> as the termination
condition in your [for]. But the really Perlish way of
doing it would be:
<code>
foreach (@q) {
</code>
which also automatically assigns $_ for you on each iteration.
<li> 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.
<li> <tt>$count++</tt> could be used instead of <tt>$count+=1</tt>.
<li> 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].
<li> The logic could be rearranged to only check against
$matchchar once.
<li> As per the original specification of the problem, you
are to replace starting with the Nth occurrence, so the check
should be <tt>$count >= $nummatch</tt>.
<li> You don't need to use <tt>@q[0 .. $#q]</tt>! Saying @q
by itself represents the whole array.
</ul>
So here's my first rewrite of the main section of your program:
<code>
@q = split (//, $p);
foreach (@q) {
$count++ if $_ eq $matchchar;
if ($_ eq $matchchar && ($count >= $nummatch)) {
$_ = $repchar;
}
}
$out = join ("", @q);
print "out === $out";
</code>
Restructuring the insides of the loop, we can get:
<code>
foreach (@q) {
if ($_ eq $matchchar) {
if (++$count >= $nummatch) {
$_ = $repchar;
}
}
}
</code>
Now compressing the two [if]'s, we get:
<code>
foreach (@q) {
if ($_ eq $matchchar && ++$count >= $nummatch) {
$_ = $repchar;
}
}
</code>
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
[perlop|conditional operator] to eliminate the [if] altogether:
<code>
foreach (@q) {
$_ = ($_ eq $matchchar && ++$count >= $nummatch)?$repchar:$_;
}
</code>
And now, notice that we are using the foreach to compute
a value based on each element of @q. Ideal use of [map]!
<code>
@q = map { ($_ eq $matchchar &&
++$count >= $nummatch)?$repchar:$_ } @q;
</code>
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:
<code>
@q = map { ($_ eq $matchchar &&
++$count >= $nummatch)?$repchar:$_ }
split(//, $p);
</code>
And finally, we can eliminate @q altogether because we can
pass the result of the [map] directly to the [join]:
<code>
$out = join ("",
map { ($_ eq $matchchar &&
++$count >= $nummatch)?$repchar:$_ }
split (//, $p));
</code>
Proof that any program can be transformed to
a one-liner in Perl :-)<p>
Man that was fun :-)
<p>--<A HREF="/index.pl?node=ZZamboni&lastnode_id=1072">ZZamboni</A>
81881
82130