The code I wrote is as below :
#!/usr/bin/perl
my @input = ( \"a.txt\" , \"b.txt\" , \"c.txt\" ) ;
my @output = map { $_ =~ s/\\..*$// } @input ;
print @o
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.
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
The problem: both the s/../.../ operator and Perl's map are imperative, expecting you to want to modify each input item; Perl doesn't in fact have a builtin for the functional map
that yields its results without modifying the input.
When using s
, an option is to append the r
modifier:
#!/usr/bin/perl
my @input = ( "a.txt" , "b.txt" , "c.txt" ) ;
my @output = map { s/\..*$//r } @input ;
print join(' ', @output), "\n";
The general solution (suggested by derobert) is to use List::MoreUtils::apply:
#!/usr/bin/perl
use List::MoreUtils qw(apply);
my @input = ( "a.txt" , "b.txt" , "c.txt" ) ;
my @output = apply { s/\..*$// } @input ;
print join(' ', @output), "\n";
or copy its definition into your code:
sub apply (&@) {
my $action = shift;
&$action foreach my @values = @_;
wantarray ? @values : $values[-1];
}
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
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 just like a subroutine or do
returning the result 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.
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' ];