Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the list my Perl map returns just 1's?

Tags:

map

perl

The code I wrote is as below :

#!/usr/bin/perl 

my @input = ( "a.txt" , "b.txt" , "c.txt" ) ;
my @output = map { $_ =~ s/\..*$// } @input ;

print @output ;

My intention is to let the file name without the extension stored in the array @output. but instead it stores the value returned by s/// rather than the changed file name in @output, so the result looks like

1
1
1

so what is the correct way to use map under this situation?

like image 371
Haiyuan Zhang Avatar asked Sep 22 '09 06:09

Haiyuan Zhang


4 Answers

Out of all of those answers, no one simply said that map returns the result of the last evaluated expression. Whatever you do last is the thing (or things) map returns. It's just like a subroutine or do returning the result of their last evaluated expression.

Perl v5.14 adds the non-destructive substitution, which I write about in Use the /r substitution flag to work on a copy. Instead of returning the number of replacements, it returns the modified copy. Use the /r flag:

my @output = map { s/\..*$//r } @input;

Note that you don't need to use the $_ with the binding operator since that's the default topic.

like image 176
brian d foy Avatar answered Nov 11 '22 21:11

brian d foy


Ok, first off, you probably meant to have $_ =~ s/\..*$// — note the missing s in your example. Also, you probably mean map not grep.

Second, that doesn't do what you want. That actually modifies @input! Inside grep (and map, and several other places), $_ is actually aliased to each value. So you're actually changing the value.

Also note that pattern match does not return the matched value; it returns true (if there is a match) or false (if there isn't). That's all the 1's you're seeing.

Instead, do something like this:

my @output = map {
    (my $foo = $_) =~ s/\..*$//;
    $foo;
} @input ;

The first copies $_ to $foo, and then modifies $foo. Then, it returns the modified value (stored in $foo). You can't use return $foo, because its a block, not a subroutine.

like image 18
derobert Avatar answered Nov 11 '22 21:11

derobert


The problem of $_ aliasing the list's values was already discussed.

But what's more: Your question's title clearly says "map", but your code uses grep, although it looks like it really should use map.

grep will evaluate each element in the list you provide as a second argument. And in list context it will return a list consisting of those elements of the original list for which your expression returned true.

map on the other hand uses the expression or block argument to transform the elements of the list argument returning a new list consisting of the transformed arguments of the original.

Thus your problem could be solved with code like this:

@output = map { m/(.+)\.[^\.]+/ ? $1 : $_ } @input;

This will match the part of the file name that is not an extension and return it as a result of the evaluation or return the original name if there is no extension.

like image 7
innaM Avatar answered Nov 11 '22 22:11

innaM


You are missing the 's' for substitution.

$_ =~ /\..*$//

should be

$_ =~ s/\..*$//

Also you might be better off to use s/\.[^\.]*$// as your regular expression to make sure you just remove the extension even when the filename contains a '.' (dot) character.

like image 2
Nikhil Avatar answered Nov 11 '22 20:11

Nikhil