Вопрос

На самом деле я не понимаю смысла функции map.Кто-нибудь может объяснить на примерах его использование?

Есть ли какие-либо преимущества в производительности при использовании этого вместо цикла или это просто сахар?

Это было полезно?

Решение

Каждый раз, когда вы хотите сгенерировать список на основе другого списка:

# 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);

(Редактировать:исправлено отсутствие "keys %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?

Карта используется для создания списка путем преобразования элементов другого списка.

grep используется для создания списка путем фильтрации элементов другого списка.

сортировать используется для создания списка путем сортировки элементов другого списка.

Каждый из этих операторов получает блок кода (или выражение), который используется для преобразования, фильтрации или сравнения элементов списка.

Для Карта, результатом выполнения блока становится один (или более) элемент (ы) в новом списке.Текущий элемент имеет псевдоним $_.

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

Для сортировать, блок получает два элемента (с псевдонимами $ a и $ b) и, как ожидается, вернет один из -1, 0 или 1, указывающий, является ли $ a больше, равно или меньше, чем $ b.

Тот Самый Преобразование Шварца использует эти операторы для эффективного кэширования значений (свойств), которые будут использоваться при сортировке списка, особенно когда вычисление этих свойств сопряжено с нетривиальными затратами.

Это работает путем создания промежуточного массива, который имеет в качестве элементов ссылки на массив с исходным элементом и вычисленным значением, по которому мы хотим выполнить сортировку.Этот массив передается в 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('*');

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

@names = ("andrew", "bob", "carol" );

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

@names = map (ucfirst, @names);

Функция map - это идея из парадигмы функционального программирования.В функциональном программировании функции являются объектами первого класса, что означает, что они могут передаваться в качестве аргументов другим функциям.Карта - простой, но очень полезный пример этого.Он принимает в качестве своих аргументов функцию (давайте вызовем ее f) и список l. f должна быть функция, принимающая один аргумент, и map просто применяется f к каждому элементу списка l. f можете делать все, что вам нужно, с каждым элементом:добавьте единицу к каждому элементу, возведите каждый элемент в квадрат, запишите каждый элемент в базу данных или откройте окно веб-браузера для каждого элемента, который, оказывается, является допустимым URL.

Преимущество использования map заключается в том, что он прекрасно инкапсулирует перебор элементов списка.Все, что вам нужно сделать, это сказать "делай f к каждому элементу, и это зависит от map чтобы решить, как лучше всего это сделать.Например map может быть реализовано для разделения его работы между несколькими потоками, и это было бы полностью прозрачно для вызывающего.

Обратите внимание, что map это вовсе не специфично для Perl.Это стандартный метод, используемый функциональными языками.Это даже может быть реализовано на C с использованием указателей на функции или на C ++ с использованием "функциональных объектов".

"Просто сахар" - это грубо.Помните, цикл - это просто сахар: if и goto могут делать все, что делают конструкции цикла, и даже больше.

Map - это функция достаточно высокого уровня, которая помогает вам удерживать в голове гораздо более сложные операции, чтобы вы могли кодировать и отлаживать более серьезные проблемы.

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

Создайте список квадратов из 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;

Я считаю, что одним из преимуществ операционных выражений является то, что они устраняют ошибки, возникающие из-за временных переменных.

Если г - нМаккой хочет отфильтровать все поля шляпы для рассмотрения, вы можете добавить эту проверку с минимальным кодированием.

my %soldiers_by_sn 
    = map  { $->{serial_number}, $_ } 
      grep { $_->{name} !~ m/Hatfield$/ } 
      values %soldiers
      ;

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

Как уже говорили другие, map создает списки из списков.Подумайте о "отображении" содержимого одного списка в другой.Вот некоторый код из 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);

Как уже говорили другие, map наиболее полезен для преобразования списка.Что не было упомянуто, так это разница между map и "эквивалентным" циклом for.

Одно из отличий заключается в том, что for не очень хорошо работает для выражения, которое изменяет список при его повторении.Один из них завершается, а другой нет:

perl -e '@x=("x"); map { push @x, $_ } @x'
perl -e '@x=("x"); push @x, $_ for @x'

Еще одно небольшое отличие заключается в том, что контекст внутри блока map находится контекст списка, но цикл for передает контекст void.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top