Имеются два списка, каждый из которых содержит неповторяющиеся элементы. Требуется узнать, какие элементы присутствуют в обоих списках {пересечение), присутствуют в одном и отсутствуют в другом списке разность) или хотя бы в одном из списков {объединение)
Решение
В приведенных ниже решениях списки инициализируются следующим образом:
@а = (1, 3, 5, 6, 7, 8);
@b = (2, 3, 5, 7, 9);
@iunion = @isect = @diff = ();
%union = %isect = ();
%count =();
Простое решение для объединения и пересечения
foreach $е(@а) { $union{$е} = 1 }
foreach $e (@b) {
if ( $union{$e} ) { $isect{$e} = 1 } Sun-inn {$e} = 1;
}
@union = Keys %union;
@isect = keys %isect;
В первом решении происходит непосредственное вычисление объединения и пересечения двух списков, ни один из которых не содержит дубликатов. Для зп-писи элементов, принадлежащих к объединению и пересечению, используются два разных хэша. Сначала мы заносим к
Второе решение ("Идиоматическое") в сущности делает то же самое, однако для него потребуется хорошее знание операторов Perl (а также awk, С, C++ и Java) ++ и &&. Если ++ находится после переменной, то ее старое значение используется до приращения. Когда э
В третьем решении использован всего один хэш для хранения информации о том, сколько раз встретился тот или иной элемент. Записав элементы обоих массивов в хэш, мы последовательно перебираем его ключи. Каждый ключ автоматически попадает в объединение. Ключ
ния. Ключи с ассоциированным значением 1 встречаются лишь в одном из двух массивов и заносятся в массив разности. В отличие от исходного решения, порядок элементов в выходных массивах не совпадает с порядком элементов входных массивов.
В последнем решении, как и в предыдущем, используется всего один хэш с количеством экземпляров каждого элемента. Однако на этот раз реализация построена на массиве в блоке @{. . .}.
Мы вычисляем не простую, а симметричную разность. Эти термины происходят из теории множеств. Симметричная разность представляет собой набор всех элементов, являющихся членами либо @А, либо @В, но не обоих сразу. Простая разность - набор всех элементов @А,
> Смотри также -------------------------------
Аналогичное применение хэшей продемонстрировано в рецептах 4.7 и 4.8.
Функция push оптимизирована для записи списка в конец массива. Два массива также можно объединить посредством сглаживания (flattening) списков Perl, однако в этом случае выполняется намного больше операций копирования, чем при использовании push:
@ARRAY1 = ((SARRAY1, @ARRAY2);
Ниже показан пример практического использования push:
Требуется обратить массив (то есть переставить элементы в противоположном порядке).
Решение
Воспользуйтесь функцией reverse:
# Обращение @ARRAY дает
@REVERSED @REVERSED = reverse @ARRAY;
Также можно воспользоваться циклом for:
for ($i = $"ARRAY; $i >= 0: $i--) {
# Сделать что-то с $ARRAY[$i]
}
Комментарий
Настоящее обращение списка выполняется функцией reverse; цикл for просто перебирает элементы в обратном порядке. Если обращенная копия списка не нужна, цикл for экономит память и время.
Если функция reverse используется для обращения только что отсортированного списка, логичнее будет сразу отсортировать список в нужном порядке. Например:
и Два шага: сортировка, затем обращение
Требуется удалить сразу несколько элементов в начале или конце массива.
Решение
Воспользуйтесь функцией splice:
# Удалить $N элементов с начала
@ARRAY (shift $N) @FRONT = splice(@ARRAY, 0, $N);
# Удалить $N элементов с конца массива (pop $N)
(SEND = splice(@ARRAY, -$N);
Комментарий
Часто бывает удобно оформить эти операции в виде функций:
sub shift2 (\@) {
return splice(@i{$_[0]}, 0, 2);
}
&uu pop2 (\@) {
return splice(@{$_[0]}, 0, -2);
}
Использование функций делает код более наглядным:
@friends = qw(Peter Paul Mary Jim Tim);
($this, $that) = shift2((^'-tends);
# $this содержит Peter, $i.hat - Paul, # a @friends - Mary, Jim и Tim
@beverages = qw(Dew Jolt Cola Sprite Fresca);
@pair = pop2(@beverages);
# $pair[0] содержит $sprite, $pair[1] - Fresca,
# a @beverages - (Dew, Jolt, Cola)
Функция splice возвращает элементы, удаленные из массива, поэтому shift2 заменяет первые два элемента @ARRAY ничем (то есть удаляет их) и возвращает два удаленных элемента. Функция pop2 удаляет и возвращает два последних элемента.
В качестве аргументов этим функциям передается ссылка на массив - это сделано для того, чтобы они лучше имитировали встроенные функции shift и pop. При вызове ссылка не передается явно, с использованием символа \. Вместо этого компилятор, встречая прототи
объект должен выглядеть как настоящий массив с префиксом @, а не как скалярная величина, содержащая ссылку на массив. В противном случае придется добавлять префикс вручную, что сделает функцию менее наглядной:
$line[5] = \@list;
@got = рор2( @{ $line[5] } );
Перед вами еще один пример, когда вместо простого списка должен использоваться массив. Прототип \@ требует, чтобы объект, занимающий данную позицию в списке аргументов, был массивом. $ line [5] представляет собой не массив, а ссылку на него. Вот почему на
> Смотри также -------------------------------
Описание функции splice в perlfunc; раздел "Prototypes" perlsub(1). Функция splice используется в рецепте 4.9.
Требуется найти первый элемент списка, удовлетворяющего некоторому критерию (или индекс этого элемента). Возможна и другая формулировка - определить, проходит ли проверку хотя бы один элемент. Критерий может быть как простым ("Присутствует ли элемент в сп
Решение
Перебирайте элементы в цикле to reach и вызовите last, как только критерий будет выполнен:
my($match, $found, $item);
foreach $item(@array) { if ($critenon) {
$match $item; # Необходимо сохранить
$found = 1;
last; }
}
if($found) {
# Сделать что-то с $match } else {
Но тогда почему бы не воспользоваться хэшем?
# Неудачный поиск
}
Чтобы определить индекс, перебирайте все индексы массива и вызывайте last, как только критерий выполнится:
my($i, $match_idx);
for ($i =0; $i < @array: $i++) { if ($critenon) {
$match_idx = $i: # Сохранить индекс last;
} if(defined $match_idx) {
# Найден элемент
$array[$match_idx] } else {
# Неудачный поиск
}
Комментарий
Стандартных механизмов для решения этой задачи не существует, поэтому мы напишем собственный код для перебора и проверки каждого элемента. В нем используются циклы f о reach и for, а вызов last прекращает проверку при выполнении условия. Но перед тем, как
Одна из распространенных ошибок - использование функции дгер. Дело в том, что дгер проверяет все элементы и находит все совпадения; если вас интересует только первое совпадение, этот вариант неэффективен.
Если нас интересует значение первого найденного элемента, присвойте его переменной $match. Мы не можем просто проверять $item в конце цикла, потому что ^о reach автоматически локализует' переменную-итератор и потому не позволяет узнать ее последнее значен
Рассмотрим пример. Предположим, в массиве @employees находится список объектов с информацией о работниках, отсортированный в порядке убывания оклада. Мы хотим найти инженера с максимальным окладом; это будет первый инженер в массиве. Требуется только выве
Термин "локализация" по отношению к переменной означает придание ен локальной области действия. - Примеч. перев.
Если нас интересует лишь значение индекса, можно сократить программу - достаточно вспомнить, что при неудачном поиске $i будет содержать недопустимый индекс. В основном экономится объем кода, а не время выполнения, поскольку затраты на присваивание невели
for ($i =0; $i < @array; $i++) {
last if $criterion;
} if ($1 < @array) {
# Критерий выполняется по индексу
$i } else {
# Неудачный поиск
}
[> Смотри также -------------------------------
Разделы "For Loops", "Foreach Loops" и "Loop Control" perlsyn(1); описание функции grер в perlfunc(1).
Требуется найти все элементы списка, удовлетворяющие определенному критерию.
Проблема извлечения подмножества из списка остается прежней. Вопрос заключается в том, как найти всех инженеров в списке работников, всех пользователей в административной группе, все интересующие вас имена файлов и т. д.
Решение
Воспользуйтесь функцией дгер. Функция применяет критерий ко всем элементам списка и возвращает лишь те, для которых он выполняется:
@РЕЗУЛЬТАТ = greр { КРИТЕРИЙ ($_) } @СПИСОК;
Функция Perl grep позволяет записать всю эту возню с циклами более компактно. В действительности функция grep сильно отличается от одноименной команды UNIX - она не имеет параметров для нумерации строк или инвертирования критерия и не ограничивает
Из массива @employees извлекаются только те объекты, для которых метод position() возвращает строку Engineer.
Grep позволяет выполнять и более сложные проверки:
@secondary_assistance = grep { $_->income >= 26_000 &&
$_->income < 30_000 } (@applicants);
Однако в таких ситуациях бывает разумнее написать цикл.
> Смотри также -------------------------------
Разделы "For Loops", "Foreach Loops" и "Loop Control" perlsyn(1), описание функции grep в perlfunc(1); страница руководства whо(1) вашей системы (если есть); рецепт 4.12.
Требуется отсортировать список чисел, однако функция Perl sort (но умолчанию) выполняет алфавитную сортировку в ASCII-порядке.
Решение
Воспользуйтесь функцией Perl sort с оператором числового сравнения, оператор <=>:
@Sorted = sort { $a <=> $b } @Unsorted;
Комментарий
При вызове функции sort можно передавать необязательный программный блок, с помощью которого принятый по умолчанию алфавитный порядок сравнения заменяется вашим собственным. Функция сравнения вызывается каждый раз, когда sort сравнивает две величины. Срав
Функция сравнения должна возвращать отрицательное число, если значение $а должно находиться в выходных данных перед $b; 0, если они совпадают или порядок несущественен; и положительное число, если значение $а должно находиться после $Ь. В Perl существуют
Следующий фрагмент сортирует список идентификаторов процессов (PID) в массиве @pids, предлагает пользователю выбрать один PID.и посылает сигнал TERM, за которым следует сигнал KILL. В необязательном программном блоке $а сравнивается с $Ь оператором <=>, ч
# @pids - несортированный массив идентификаторов процессов
foreach my $pid (sort { $a <=> $b } @pids) {
print "$pid\n", }
print "Select a process ID to kill:\n":
chomp ($pid = <>):
die "Exiting .., \n" unless $pid && $pid =~ /"\d=$/;
kill ('TERM',$pid);
sleep 2;
kill ('KILL',$pid);
При использовании условия $a<:::>$b или $а cmp $b список сортируется в порядке возрастания. Чтобы сортировка выполнялась в порядке убывания, достаточно поменять местами $а и $Ь в функции сравнения:
@descending = sort { $b <=> $а } @unsorted;
Функции сравнения должны быть последовательными; иначе говоря, функция всегда должна возвращать один и тот же ответ для одинаковых величин. Непоследовательные функции сравнения приводят к зацикливанию программы или ее аварийному завершению, особен
Также возможна конструкция вида sort ИМЯ СПИСОК, где ИМЯ - имя функции сравнения, возвращающей -1,0 или +1. В интересах быстродействия нормальные правила вызова не соблюдаются, а сравниваемые значения, как по волшебству, появляются в глобальных пакетных п
Предупреждение: значения $а и $Ь задаются в пакете, активном в момент вызова sort, - а он может не совпадать с пакетом, в котором была откомпилирована передаваемая sort функция сравнения (ИМЯ)! Например:
package Sort_Subs;
sub revnum { $b <=> $a }
package Other_Pack;
@all = sort Sort_Subs: : revnum 4, 19, 8, 3;
Такая попытка тихо закапчивается неудачей - впрочем, при наличии ключа -w о неудаче будет заявлено вслух. Дело в том, что вызов sort создает пакетные переменные $а и $Ь в своем собственном пакете, Other_Pack, а функция revnum будет использовать версии из
@аll = sort { $b <=> $а } 4, 19, 8, 3;
За дополнительной информацией о пакетах обращайтесь к главе 10 "Подпрограммы".
> Смотри также --------------------------------
Описание операторов стр и <=> в реrlор(1); описание функций kill, sort и sleep в perlfunc(1); рецепт 4.15.
Требуется отсортировать список, руководствуясь более сложным критерием, нежели простыми строковыми или числовыми сравнениям.
Такая проблема часто встречается при работе с объектами или сложными структурами данных ("отсортировать но третьему элементу массива, на который указывает данная ссылка"). Кроме того, она относится к сортировке по нескольким ключам - например, когда списо
Решение
Воспользуйтесь нестандартной функцией сравнения в sort:
@ordered = sort { compare() } @unordered;
Для ускорения работы значение поля можно вычислить заранее:
Наконец, эти три шага можно объединить:
@ordered = map { $_->[1] }
sort { $a->[0] <=> $b->[0] } map { [computeO, $_] } @unordered;
Комментарий
О том, как пользоваться функциями сравнения, рассказано в рецепте 4.14. Помнимо использования встроенных операторов вроде <=>, можно конструировать более сложные условия:
@ordered = sort { $a->name cmp $b->name } @employees;
Функция sort часто используется подобным образом в циклах foreach:
foreach $employee (sort {$a->name cmp $b->name } @employees) { print $etTiployee->nanie, " earns \$", $employee->salary, "\n";
Если вы собираетесь много работать с элементами, расположенными в определенном порядке, эффективнее будет сразу отсортировать их и работать с отсортированным списком:
В функцию можно включить несколько условий и разделить их операторами |. Оператор [ | возвращает первое истинное (ненулевое) значение. Следовательно, сортировку можно выполнять по одному критерию, а при равенстве элементов (когда возвращаемое знач
Первый критерий сравнивает имена двух работников. Если они не совпадают, I прекращает вычисления и возвращает результат cmp (сортировка в порядке возрастания имен). Но если имена совпадают, | ] продолжает проверку и возвращает результат <=> (сорти
Давайте рассмотрим реальный пример сортировки. Мы собираем информацию обо всех пользователям в виде объектов User:: pwent, после чего сортируем их по именам и выводим отсортированный список:
@temp = map { [ length $_, $_ ] } @strings;
@temp = sort { $a->[0] <=> $b->[0] } @temp;
""sorted = map { $_->[1] } @temp;
В первой строке map создает временный массив строк с их длинами. Вторая строка сортирует временный массив, сравнивая их предварительно вычисленные длины. Третья строка превращает временный массив строк/длин в отсортированный массив строк. Таким образом, д
Поскольку входные данные каждой строки представляют собой выходные данные предыдущей строки (массив @temp, созданный в строке 1, передается sort в строке 2, а результат сортировки передается тар в строке 3), их можно объединить в одну команду и отказаться
Регулярное выражение в первой строке извлекает из строки, обрабатываемой тар, первое число. Мы используем регулярное выражение /(\d+)/ в списковом контексте.
Из этого фрагмента можно убрать временный массив. Код принимает следующий вид:
@sorted_fields = map { $_->[1] }
sort { $a->[0] <=> $b->[0] } map { [ /(\d+)/, $_ ] } @fields;
В последнем примере выполняется компактная сортировка данных, разделенных запятыми (они взяты из файла UNIX passwd). Сначала выполняется числовая сортировка файла по четвертому нолю (идентификатору группы), затем - числовая сортировка по третьему полю (ид
Компактная конструкция map/sort/map больше напоминает программирование на Lisp и Scheme, нежели обычное наследие Perl - С и awk. Впервые она была предложена Рэндалом Шварцем (Randal Schwartz) и потому часто называется "преобразованием Шварца".
> Смотри также --------------------------------
Описание функции sort в реrlfunс(1), описание операторов стр и <=> в perlop(1);
рецепт 4.14.