perl.programmer
perl.programmer

Reputation: 23

Reversing a string in perl without using "reverse" function

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

Answers (1)

Borodin
Borodin

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

Related Questions