2009-09-22 3 views
6

Код я написал, как показано ниже:Почему список карт Perl возвращает только 1?

#!/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 в этой ситуации?

ответ

14

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

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

Также обратите внимание, что совпадение шаблонов не возвращает согласованное значение; он возвращает true (если есть совпадение) или false (если нет). Это все, что вы видите.

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

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

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

+0

@derobert: недостающие 's' - ошибка редактирования, я исправил ее.и я протестировал ваше решение, и оно работает! –

+0

Рад слышать, как это работает. – derobert

+4

Список :: MoreUtils http://search.cpan.org/perldoc/List::MoreUtils предлагает 'apply', который идеально подходит для такого рода вещей. Он работает как «карта», но не изменяет значения в аргументе массива. 'use List :: MoreUtils 'apply'; my @output = apply {s /\..*$//} @input; ' – daotoad

2

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

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

должен быть

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

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

+0

@Nikhil: Да, ваше регулярное выражение лучше, спасибо. –

0

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

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

Выход:

 
a 
b 
c 
+2

Сделайте 'print @ input' и обратите внимание, как ваш код управляет' @ input', что является неожиданным и очень нежелательным. – derobert

+0

Это, наверное, так. – bobbymcr

7

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

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

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

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

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

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

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

1

derobert показывает правильный путь картирования @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' 
     ]; 
12

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

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

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

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

+2

Благодарим вас за указание флага '/ r', которого я не знал. –

1

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

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

Другой способ заключается в использовании фильтра из Algorithm::Loops

+0

Это маленькие детали, которые вы попали на Perl, это хорошая причина, если вы не считаете, что полностью понимаете язык, должны придерживаться модулей, когда что-то становится рискованным. – osirisgothra

0

Проблема: как the s/../.../ operator и Perl's 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"; 

Общего решения (предложенного derobert) является использование 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]; 
}