Почему список, на котором моя карта Perl возвращает только 1? - PullRequest
7 голосов
/ 22 сентября 2009

Код, который я написал, выглядит следующим образом:

#!/usr/bin/perl 

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

print @output ;

Мое намерение - оставить имя файла без расширения в массиве @output. но вместо этого он сохраняет значение, возвращаемое s///, а не измененное имя файла в @output, поэтому результат выглядит как

1
1
1

так, как правильно использовать map в этой ситуации?

Ответы [ 8 ]

18 голосов
/ 22 сентября 2009

Из всех этих ответов никто просто не сказал, что map возвращает результат последнего вычисленного выражения. Что бы вы ни делали последним, это вещь (или вещи) map возвращается. Это как подпрограмма или do, возвращающие результат их последнего вычисленного выражения.

Perl v5.14 добавляет неразрушающую подстановку, о которой я пишу в Используйте флаг подстановки / r для работы с копией . Вместо того, чтобы возвращать количество замен, он возвращает измененную копию. Используйте флаг /r:

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

Обратите внимание, что вам не нужно использовать $_ с оператором привязки, поскольку это тема по умолчанию.

15 голосов
/ 22 сентября 2009

Хорошо, во-первых, вы, вероятно, хотели иметь $_ =~ s/\..*$// - обратите внимание на отсутствующий s в вашем примере. Кроме того, вы, вероятно, имеете в виду map не grep.

Во-вторых, это не то, что вы хотите. Это на самом деле изменяет @input! Внутри grepmap и нескольких других местах) $_ фактически имеет псевдоним для каждого значения. Таким образом, вы на самом деле меняете значение.

Также обратите внимание, что сопоставление с образцом не возвращает сопоставленное значение; он возвращает истину (если есть совпадение) или ложь (если нет). Вот и все 1, которые вы видите.

Вместо этого сделайте что-то вроде этого:

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

Сначала копируется $_ в $foo, а затем изменяется $foo. Затем он возвращает измененное значение (хранится в $foo). Вы не можете использовать return $foo, потому что это блок, а не подпрограмма.

7 голосов
/ 22 сентября 2009

Проблема $_ наложения значений списка уже обсуждалась.

Но более того: заголовок вашего вопроса четко говорит "map", но ваш код использует grep, хотя похоже, что он действительно должен использовать map.

grep оценит каждый элемент в списке, который вы предоставляете в качестве второго аргумента. И в контексте списка он вернет список, состоящий из тех элементов исходного списка, для которых ваше выражение вернуло true.

map с другой стороны использует выражение или аргумент блока для преобразования элементов аргумента списка, возвращая новый список, состоящий из преобразованных аргументов оригинала.

Таким образом, ваша проблема может быть решена с помощью следующего кода:

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

Это будет соответствовать той части имени файла, которая не является расширением, и будет возвращать ее в результате оценки или будет возвращать исходное имя, если расширение отсутствует.

2 голосов
/ 22 сентября 2009

Вам не хватает 's' для замены.

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

должно быть

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

Также вам может быть лучше использовать s/\.[^\.]*$// в качестве регулярного выражения, чтобы убедиться, что вы просто удалите расширение, даже если имя файла содержит «.» (точка) символ.

1 голос
/ 23 сентября 2009

Как уже упоминалось, s /// возвращает количество выполненных подстановок, а map возвращает последнее выражение, вычисленное на каждой итерации, поэтому ваша карта возвращает все 1. Один из способов выполнить то, что вы хотите, это:

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

Другой способ - использовать Фильтр из Алгоритм :: Петли

1 голос
/ 22 сентября 2009

Дероберт показывает правильный способ отображения @input в @output.

Однако я бы рекомендовал использовать 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;

Выход:

C:\Temp> h
$VAR1 = [
          'a.1',
          'b',
          'c'
        ];
0 голосов
/ 11 ноября 2014

Проблема: оба оператор s/../.../ и Perl map являются обязательными, ожидая, что вы захотите изменить каждый элемент ввода; На самом деле в Perl нет встроенного функционала map, который выдает результаты без изменения ввода.

При использовании s можно добавить модификатор r:

#!/usr/bin/perl 

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

print join(' ', @output), "\n";

Общее решение (предложенное Деробертом) заключается в использовании 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";

или скопируйте его определение в ваш код:

sub apply (&@) {
    my $action = shift;
    &$action foreach my @values = @_;
    wantarray ? @values : $values[-1];
}
0 голосов
/ 22 сентября 2009

В вашем примере кода отсутствует оператор s. Кроме того, он работал нормально для меня:

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

Вывод:

a
b
c
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...