Why is my Perl map returning only 1 in the list?

The code I wrote is as follows:

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

My intention is to specify a file name without the extension stored in the @output array. but instead it saves the value returned by s/// and not the changed filename in @output , so the result looks like

 1 1 1 

So what is the correct way to use map in this situation?

+6
perl map
source share
8 answers

Ok, first you should have $_ =~ s/\..*$// - note missing s in your example. Also, you probably mean map not grep .

Secondly, it does not do what you want. This actually changes @input ! Inside grep (and map and in several other places) $_ actually smoothed out for each value. This way you actually change the value.

Also note that pattern matching does not return a matching value; it returns true (if there is a match) or false (if not). That is all you see.

Instead, do the following:

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

The first instances of $_ to $foo , and then changes $foo . Then it returns the changed value (stored in $foo ). You cannot use return $foo because its block is not a subroutine.

+14
source share

Of all these answers, no one said that map returns the result of the last evaluated expression. All you do last is what the map (or things) returns. It is just like a subroutine or do returns the result of the last expression evaluated.

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

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

Note that you do not need to use $_ with the binding operator, as this is the default theme.

+12
source share

The problem of $_ smoothing list values ​​has already been discussed.

But what else: The question name clearly says "map", but your code uses grep, although it looks like it really should use a map.

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

map , on the other hand, uses an argument of an expression or block to convert elements of a list argument that return a new list of converted original arguments.

Thus, your problem can be solved using code as follows:

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

This will correspond to the part of the file name that is not an extension, and returns it as a result of the evaluation or returns the original name if the extension is missing.

+7
source share

You are missing the 's' for replacement.

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

it should be

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

Also, you might be better off using s/\.[^\.]*$// as a regular expression to make sure that you simply remove the extension, even if the file name contains ".". (dot).

+2
source share

derobert shows the correct way to map @input to @output .

I would 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'
         ];
+1
source share

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

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

Another way is to use the Filter from Algorithm :: Loops

+1
source share

In the sample code, the s operator is missing in the matching 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; 

Exit:

  a
 b
 c
0
source share

Problem: the s/../.../ operator and the Perl map are required, expecting you to want to change each input element; Perl does not actually have a built-in for a functional map , which gives its results without changing the input.

When using s option is to add 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]; } 
0
source share

All Articles