tags:

views:

169

answers:

7

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?

+2  A: 

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.

Nikhil
@Nikhil: Yea, your regex is better , thanks.
Haiyuan Zhang
+11  A: 

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.

derobert
@derobert: the missing 's' is a edit error, I've fix it . and I've tested your solution and it works !
Haiyuan Zhang
Glad to hear it works.
derobert
List::MoreUtils http://search.cpan.org/perldoc/List::MoreUtils offers `apply` which is perfect for this sort of thing. It works like a `map`, but will not alter the values in the array argument. `use List::MoreUtils 'apply'; my @output = apply { s/\..*$// } @input;`
daotoad
daotoad: Good point, apply is definitely a good choice here, too.
derobert
A: 

Your code sample is missing an s in the match operator. Other than that, it worked fine for me:

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

Output is:

a
b
c
bobbymcr
Do a `print @input` and note how your code mangles `@input`, which is unexpected and very likely unwanted.
derobert
That's probably true.
bobbymcr
+5  A: 

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.

innaM
+1  A: 

derobert shows you a correct way of mapping @input to @output.

I would, however, recommend using File::Basename:

#!/usr/bin/perl

use strict;
use warnings;

use File::Basename;

my @input = qw( a.1.txt b.txt c.txt );
my @output = map { scalar fileparse($_, qr/\.[^.]*/) } @input ;

use Data::Dumper;
print Dumper \@output;

Output:

C:\Temp> h
$VAR1 = [
          'a.1',
          'b',
          'c'
        ];
Sinan Ünür
+1  A: 

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 is the thing (or things) map returns.

It just like a subroutine or do returning the result their last evaluated expression.

brian d foy
+1  A: 

As mentioned, s/// returns the number of substitutions performed, and map returns the last expression evaluated from each iteration, so your map returns all 1's. One way to accomplish what you want is:

s/\..*$// for my @output = @input;

Another way is to use Filter from Algorithm::Loops

runrig