Reputation: 23
I was looking for clues on how to reverse a string in Perl without using the builtin reverse
function and came across the following piece of code for reversing $str.
print +($str =~ /./g)[-$_] for (1 .. $#{[$str =~ /./g]} + 1);
I was trying to understand how this works a bit more and expanded the above code to something like this.
for (1 .. $#{[$str =~ /./g]} + 1) {
$rev_str_1 = ($str =~ /./g)[-$_];
print $rev_str_1;
}
The above code snippet also works fine. But, the problem comes when I add any print inside the for
loop to understand how the string manipulation is working.
for (1 .. $#{[$str =~ /./g]} + 1) {
$rev_str_1 = ($str =~ /./g)[-$_];
print "\nin loop now ";
print $rev_str_1;
}
For input string of stressed
, following is the output for above code
in loop now d
in loop now e
in loop now s
in loop now s
in loop now e
in loop now r
in loop now t
in loop now s
It seems like the entire string reversal is happening in this part ($str =~ /./g)[-$_]
but I am trying to understand why is it not working when I add an extra print. Appreciate any pointers.
Upvotes: 0
Views: 3329
Reputation: 126722
You're assuming that the string is reversed before being printed, but the program just prints all the characters in the string one at a time in reverse order
Here's how it works
It's based around the expression $str =~ /./g
which uses a global regex match with a pattern that matches any single character. In list context it returns all the characters in the string as a list. Note that a dot .
without the /s
pattern modifier doesn't match linefeed. That's a bug, but probably isn't critical in this situation
This expression
$#{ [ $str =~ /./g ] } + 1
creates an anonymous array of the characters in $str
with [ $str =~ /./g ]
. Then uses $#
to get the index of the last element of the array, and adds 1 to get the total number of characters (because the index is zero-based). So the loop is executing with $_
in the range 1 to the number of characters in $str
. This is unnecessarily obscure and should probably be written 1 .. length($str)
except for the special case of linefeed characters mentioned above
The body of the loop uses ($str =~ /./g)[-$_]
, which splits $str
into characters again in the same way as before, and then uses the fact that negative indexes in Perl refer to elements relative to the end of the array or list. So the last character in $str
is at index -1, the second to last at index -2 and so on. Again, this is unnecessarily arcane; the expression is exactly equivalent to substr($str, -$_, 1)
, again with the exception that the regex version ignores linefeed characters
Printing the characters one at a time like this results in $str
being printed in reverse
It may be easier to understand if the string is split into a real array, and the reversed string is accumulated into a buffer, like this
my $reverse = '';
my @str = $str =~ /./sg;
for ( 1 .. @str ) {
$reverse .= $str[-$_];
}
print $reverse, "\n";
Or, using length
and substr
as described above, this is equivalent to
my $reverse = '';
$reverse .= substr($str, -$_, 1) for 1 .. length($str);
print $reverse, "\n";
Upvotes: 6