Не совсем понимаю функцию карты. Может ли кто-нибудь объяснить на примерах его использование?
Есть ли какие-то преимущества в производительности при использовании этого вместо цикла или это просто сахар?
Не совсем понимаю функцию карты. Может ли кто-нибудь объяснить на примерах его использование?
Есть ли какие-то преимущества в производительности при использовании этого вместо цикла или это просто сахар?
Каждый раз, когда вы хотите создать список на основе другого списка:
# Double all elements of a list
my @double = map { $_ * 2 } (1,2,3,4,5);
# @double = (2,4,6,8,10);
Поскольку списки легко конвертируются попарно в хеши, если вам нужна хеш-таблица для объектов на основе определенного атрибута:
# @user_objects is a list of objects having a unique_id() method
my %users = map { $_->unique_id() => $_ } @user_objects;
# %users = ( $id => $obj, $id => $obj, ...);
Это действительно универсальный инструмент, вам нужно просто начать использовать его, чтобы найти хорошее применение в своих приложениях.
Некоторые могут предпочесть подробный код цикла для удобства чтения, но лично я нахожу map
более читабельным.
Прежде всего, это простой способ преобразования массива: вместо того, чтобы сказать, например,
my @raw_values = (...);
my @derived_values;
for my $value (@raw_values) {
push (@derived_values, _derived_value($value));
}
ты можешь сказать
my @raw_values = (...);
my @derived_values = map { _derived_value($_) } @raw_values;
Это также полезно для создания таблицы быстрого поиска: а не, например,
my $sentence = "...";
my @stopwords = (...);
my @foundstopwords;
for my $word (split(/\s+/, $sentence)) {
for my $stopword (@stopwords) {
if ($word eq $stopword) {
push (@foundstopwords, $word);
}
}
}
ты мог бы сказать
my $sentence = "...";
my @stopwords = (...);
my %is_stopword = map { $_ => 1 } @stopwords;
my @foundstopwords = grep { $is_stopword{$_} } split(/\s+/, $sentence);
Это также полезно, если вы хотите вывести один список из другого, но не обязательно иметь временную переменную, загромождающую место, например скорее, чем
my %params = ( username => '...', password => '...', action => $action );
my @parampairs;
for my $param (keys %params) {
push (@parampairs, $param . '=' . CGI::escape($params{$param}));
}
my $url = $ENV{SCRIPT_NAME} . '?' . join('&', @parampairs);
вы говорите намного проще
my %params = ( username => '...', password => '...', action => $action );
my $url = $ENV{SCRIPT_NAME} . '?'
. join('&', map { $_ . '=' . CGI::escape($params{$_}) } keys %params);
(Изменить: исправлены отсутствующие "ключи% params" в последней строке)
Функция map
используется для преобразования списков. По сути, это синтаксический сахар для замены определенных типов for[each]
циклов. Как только вы обернете его головой, вы увидите, что он повсюду использует:
my @uppercase = map { uc } @lowercase;
my @hex = map { sprintf "0x%x", $_ } @decimal;
my %hash = map { $_ => 1 } @array;
sub join_csv { join ',', map {'"' . $_ . '"' } @_ }
См. Также преобразование Шварца для расширенного использования карты.
Это также удобно для создания хэшей поиска:
my %is_boolean = map { $_ => 1 } qw(true false);
эквивалентно
my %is_boolean = ( true => 1, false => 1 );
Здесь не так много экономии, но предположим, вы хотите определить %is_US_state
?
map используется для создания списка путем преобразования элементов другого списка.
grep используется для создания списка путем фильтрации элементов другого списка.
sort используется для создания списка путем сортировки элементов другого списка.
Каждый из этих операторов получает блок кода (или выражение), который используется для преобразования, фильтрации или сравнения элементов списка.
Для карты результатом блока становится один (или несколько) элементов в новом списке. Текущий элемент имеет псевдоним $ _.
Для grep логический результат блока определяет, будет ли элемент исходного списка скопирован в новый список. Текущий элемент имеет псевдоним $ _.
Для sort блок получает два элемента (с псевдонимами $ a и $ b) и, как ожидается, вернет один из -1, 0 или 1, указывающий, больше ли $ a, равно или меньше $ б.
Преобразование Шварца использует эти операторы для эффективного кэширования значений (свойств), которые будут использоваться при сортировке списка. , особенно когда вычисление этих свойств имеет нетривиальную стоимость.
Он работает путем создания промежуточного массива, который имеет в качестве элементов ссылки на массив с исходным элементом и вычисленным значением, по которому мы хотим выполнить сортировку. Этот массив передается в sort, который сравнивает уже вычисленные значения, создавая еще один промежуточный массив (он отсортирован), который, в свою очередь, передается на другую карту, которая отбрасывает кешированные значения, таким образом восстанавливая массив до его начальных элементов списка (но в желаемом порядке сейчас).
Пример (создает список файлов в текущем каталоге, отсортированный по времени их последнего изменения):
@file_list = glob('*');
@file_modify_times = map { [ $_, (stat($_))[8] ] } @file_list;
@files_sorted_by_mtime = sort { $a->[1] <=> $b->[1] } @file_modify_times;
@sorted_files = map { $_->[0] } @files_sorted_by_mtime;
При объединении операторов в цепочку объявление переменных для промежуточных массивов не требуется;
@sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, (stat($_))[8] ] } glob('*');
Вы также можете отфильтровать список перед сортировкой, вставив grep (если вы хотите фильтровать по тому же кешированному значению):
Пример (список файлов, измененных за последние 24 часа, отсортирован по времени последнего изменения):
@sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } grep { $_->[1] > (time - 24 * 3600 } map { [ $_, (stat($_))[8] ] } glob('*');
Функция карты запускает выражение для каждого элемента списка и возвращает результаты списка. Допустим, у меня был следующий список
@names = ("andrew", "bob", "carol" );
и я хотел сделать первые буквы каждого из этих имен заглавными. Я мог бы перебрать их и вызвать ucfirst для каждого элемента, или я мог бы просто сделать следующее
@names = map (ucfirst, @names);
Функция карты является идеей парадигмы функционального программирования. В функциональном программировании функции являются объектами первого класса, что означает, что их можно передавать в качестве аргументов другим функциям. Карта - простой, но очень полезный пример этого. В качестве аргументов он принимает функцию (давайте назовем ее f
) и список l
. f
должна быть функцией, принимающей один аргумент, а map просто применяет f
к каждому элементу списка l
. f
может делать все, что вам нужно, с каждым элементом: добавлять по одному к каждому элементу, возводить в квадрат каждый элемент, записывать каждый элемент в базу данных или открывать окно веб-браузера для каждого элемента, который оказывается действительным URL-адресом.
Преимущество использования map
заключается в том, что он прекрасно инкапсулирует итерацию по элементам списка. Все, что вам нужно сделать, это сказать «сделайте f
каждому элементу, и map
решает, как лучше всего это сделать. Например, map
может быть реализован так, чтобы разделить его работу между несколькими потоками, и это будет полностью прозрачно для звонящий.
Обратите внимание, что map
совершенно не специфичен для Perl. Это стандартный метод, используемый функциональными языками. Его даже можно реализовать на C, используя указатели на функции, или на C ++, используя «объекты функций».
«Просто сахар» - это жестко. Помните, что цикл - это просто сахар - if и goto могут делать все, что делают конструкции цикла, и многое другое.
Карта - это функция достаточно высокого уровня, которая помогает удерживать в голове гораздо более сложные операции, чтобы вы могли кодировать и отлаживать более крупные проблемы.
Перефразируя «Эффективное программирование на Perl» Холла и Шварца, map можно злоупотреблять, но я думаю, что лучше всего использовать ее для создания нового списка из существующего списка.
Составьте список квадратов 3,2 и 1:
@numbers = (3,2,1);
@squares = map { $_ ** 2 } @numbers;
Сгенерировать пароль:
$ perl -E'say map {chr(32 + 95 * rand)} 1..16'
# -> j'k=$^o7\l'yi28G
Вы используете map для преобразования списка и присвоения результатов другому списку, grep для фильтрации списка и присвоения результатов другому списку. «Другой» список может быть той же переменной, что и список, который вы преобразовываете / фильтруете.
my @array = ( 1..5 );
@array = map { $_+5 } @array;
print "@array\n";
@array = grep { $_ < 7 } @array;
print "@array\n";
Он позволяет преобразовывать список в выражение, а не в операторы. Представьте себе группу солдат, определенную так:
{ name => 'John Smith'
, rank => 'Lieutenant'
, serial_number => '382-293937-20'
};
тогда вы можете работать со списком имен отдельно.
Например,
map { $_->{name} } values %soldiers
является выражением. Он может располагаться везде, где разрешено выражение, за исключением того, что вы не можете ему присвоить.
${[ sort map { $_->{name} } values %soldiers ]}[-1]
индексирует массив, принимая макс.
my %soldiers_by_sn = map { $->{serial_number} => $_ } values %soldiers;
Я считаю, что одно из преимуществ операционных выражений состоит в том, что они сокращают количество ошибок, возникающих из-за временных переменных.
Если г-н Маккой хочет отфильтровать все поля Hat для рассмотрения, вы можете добавить эту проверку с минимальным написанием кода.
my %soldiers_by_sn
= map { $->{serial_number}, $_ }
grep { $_->{name} !~ m/Hatfield$/ }
values %soldiers
;
Я могу продолжить объединение этих выражений в цепочку, так что если мое взаимодействие с этими данными должно достигать глубоких целей для определенной цели, мне не нужно писать много кода, который делает вид, что я собираюсь сделать намного больше.
Он используется в любое время, когда вы хотите создать новый список из существующего списка.
Например, вы можете сопоставить функцию синтаксического анализа со списком строк, чтобы преобразовать их в целые числа.
Как говорили другие, карта создает списки из списков. Подумайте о «отображении» содержимого одного списка в другой. Вот код из программы CGI для получения списка номеров патентов и печати гиперссылок на заявки на патенты:
my @patents = ('7,120,721', '6,809,505', '7,194,673');
print join(", ", map { "<a href=\"http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PALL&p=1&u=/netahtml/srchnum.htm&r=0&f=S&l=50&TERM1=$_\">$_</a>" } @patents);