ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
4.8. Вычисление объединения, пересечения и разности уникальных списковПроблемаИмеются два списка, каждый из которых содержит неповторяющиеся элементы. Требуется узнать, какие элементы присутствуют в обоих списках {пересечение), присутствуют в одном и отсутствуют в другом списке разность) или хотя бы в одном из списков {объединение).РешениеВ приведенных ниже решениях списки инициализируются следующим образом:@а = (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; Идиоматическое решение foreach $e (@a, Ob) { $union{$e}++ && $isect{$e}++ }
Косвенное решение
КомментарийВ первом решении происходит непосредственное вычисление объединения и пересечения двух списков, ни один из которых не содержит дубликатов. Для зп-писи элементов, принадлежащих к объединению и пересечению, используются два разных хэша. Сначала мы заносим каждый элемент первого массива в хэш объединения и ассоциируем с ним истинное значение. Затем при последовательной обработке элементов второго массива мы проверяем, присутствует ли элемент в объединении. Если присутствует, он также включается и в хэш пересечения. В любом случае элемент заносится в хэш объединения. После завершения перебора мы извлекаем ключи обоих хэшей. Ассоциированные с ними значения не нужны. Второе решение ("Идиоматическое") в сущности делает то же самое, однако для него потребуется хорошее знание операторов Perl (а также awk, С, C++ и Java) ++ и &&. Если ++ находится после переменной, то ее старое значение используется до приращения. Когда элемент встречается впервые, он еще отсутствует в объединении, поэтому первая часть && будет ложной, а вторая часть попросту игнорируется. Когда тот же элемент встретится во второй раз, он уже присутствует в объединении, поэтому мы заносим его и в пересечение. В третьем решении использован всего один хэш для хранения информации о том, сколько раз встретился тот или иной элемент. Записав элементы обоих массивов в хэш, мы последовательно перебираем его ключи. Каждый ключ автоматически попадает в объединение. Ключи, с которыми ассоциировано значение 2, присутствуют в обоих массивах и потому заносятся в массив нересече- ния. Ключи с ассоциированным значением 1 встречаются лишь в одном из двух массивов и заносятся в массив разности. В отличие от исходного решения, порядок элементов в выходных массивах не совпадает с порядком элементов входных массивов. В последнем решении, как и в предыдущем, используется всего один хэш с количеством экземпляров каждого элемента. Однако на этот раз реализация построена на массиве в блоке @{. . .}. Мы вычисляем не простую, а симметричную разность. Эти термины происходят из теории множеств. Симметричная разность представляет собой набор всех элементов, являющихся членами либо @А, либо @В, но не обоих сразу. Простая разность - набор всех элементов @А, отсутствующих в @В (см. рецепт 4.7).> Смотри также ------------------------------- Аналогичное применение хэшей продемонстрировано в рецептах 4.7 и 4.8. 4.9. Присоединение массиваПроблемаТребуется объединить два массива, дописав все элементы одного из них в конец другого.РешениеВоспользуйтесь функцией push:# push push(@ARRAY1, @ARRAY2); КомментарийФункция push оптимизирована для записи списка в конец массива. Два массива также можно объединить посредством сглаживания (flattening) списков Perl, однако в этом случае выполняется намного больше операций копирования, чем при использовании push:@ARRAY1 = ((SARRAY1, @ARRAY2); Ниже показан пример практического использования push: ©members = ("Time", "Flies");
splice(@members, 2, 0, "Like", ©initiates);
Результат выглядит так:
[> Смотри также ---------- Описание функции splice и push в perlfunc(1) раздел "List Value Constructors" perldata(l). 4.10. Обращение массиваПроблемаТребуется обратить массив (то есть переставить элементы в противоположном порядке).РешениеВоспользуйтесь функцией reverse: # Обращение @ARRAY дает@REVERSED @REVERSED = reverse @ARRAY; Также можно воспользоваться циклом for: for ($i = $"ARRAY; $i >= 0: $i--) { # Сделать что-то с $ARRAY[$i] } КомментарийНастоящее обращение списка выполняется функцией reverse; цикл for просто перебирает элементы в обратном порядке. Если обращенная копия списка не нужна, цикл for экономит память и время. Если функция reverse используется для обращения только что отсортированного списка, логичнее будет сразу отсортировать список в нужном порядке. Например: и Два шага: сортировка, затем обращение©ascending = sort { $а cmp $b } @users;
> Смотри также -------------------------------- Описание функции reverse в perlfunc(1). Она используется в рецепте 1.6. 4.11. Обработка нескольких элементов массиваПроблемаТребуется удалить сразу несколько элементов в начале или конце массива.РешениеВоспользуйтесь функцией splice:# Удалить $N элементов с начала @ARRAY (shift $N) @FRONT = splice(@ARRAY, 0, $N); # Удалить $N элементов с конца массива (pop $N) (SEND = splice(@ARRAY, -$N); КомментарийЧасто бывает удобно оформить эти операции в виде функций:sub shift2 (\@) {
@friends = qw(Peter Paul Mary Jim Tim);
$line[5] = \@list;
> Смотри также -------------------------------
4.12. Поиск первого элемента списка, удовлетворяющего некоторому критериюПроблемаТребуется найти первый элемент списка, удовлетворяющего некоторому критерию (или индекс этого элемента). Возможна и другая формулировка - определить, проходит ли проверку хотя бы один элемент. Критерий может быть как простым ("Присутствует ли элемент в списке?")', так и сложным ("Имеется список работников, отсортированный в порядке убывания оклада. У кого из менеджеров самый высокий оклад?"). В простых случаях дело обычно ограничивается значением элемента, но если сам массив может изменяться, вероятно, следует определять индекс первого подходящего элемента.РешениеПеребирайте элементы в цикле to reach и вызовите last, как только критерий будет выполнен:my($match, $found, $item); foreach $item(@array) { if ($critenon) { $match $item; # Необходимо сохранить $found = 1; last; } } if($found) { # Сделать что-то с $match } else { Но тогда почему бы не воспользоваться хэшем?
# Неудачный поиск
КомментарийСтандартных механизмов для решения этой задачи не существует, поэтому мы напишем собственный код для перебора и проверки каждого элемента. В нем используются циклы f о reach и for, а вызов last прекращает проверку при выполнении условия. Но перед тем, как прерывать поиск с помощью last, следует сохранить найденный индекс. Одна из распространенных ошибок - использование функции дгер. Дело в том, что дгер проверяет все элементы и находит все совпадения; если вас интересует только первое совпадение, этот вариант неэффективен. Если нас интересует значение первого найденного элемента, присвойте его переменной $match. Мы не можем просто проверять $item в конце цикла, потому что ^о reach автоматически локализует' переменную-итератор и потому не позволяет узнать ее последнее значение после завершения цикла (см. рецепт 4.4). Рассмотрим пример. Предположим, в массиве @employees находится список объектов с информацией о работниках, отсортированный в порядке убывания оклада. Мы хотим найти инженера с максимальным окладом; это будет первый инженер в массиве. Требуется только вывести имя инженера, поэтому нас интересует не индекс, а значение элемента.foreach $employee (@employees) {
Термин "локализация" по отношению к переменной означает придание ен локальной области действия. - Примеч. перев. Если нас интересует лишь значение индекса, можно сократить программу - достаточно вспомнить, что при неудачном поиске $i будет содержать недопустимый индекс. В основном экономится объем кода, а не время выполнения, поскольку затраты на присваивание невелики по сравнению с затратами на проверку элементов списка. Однако проверка условия if ($i < @ARRAY) выглядит несколько туманно по сравнению с очевидной проверкой defined из приведенного выше решения. for ($i =0; $i < @ARRAY; $i++) {
[> Смотри также ------------------------------- Разделы "For Loops", "Foreach Loops" и "Loop Control" perlsyn(1); описание функции grер в perlfunc(1). 4.13. Поиск всех элементов массива, удовлетворяющих определенному критериюПроблемаТребуется найти все элементы списка, удовлетворяющие определенному критерию. Проблема извлечения подмножества из списка остается прежней. Вопрос заключается в том, как найти всех инженеров в списке работников, всех пользователей в административной группе, все интересующие вас имена файлов и т. д.РешениеВоспользуйтесь функцией дгер. Функция применяет критерий ко всем элементам списка и возвращает лишь те, для которых он выполняется: @РЕЗУЛЬТАТ = greр { КРИТЕРИЙ ($_) } @СПИСОК;КомментарийТо же самое можно было сделать в цикле to reach:@РЕЗУЛЬТАТ =();
Функция Perl grep позволяет записать всю эту возню с циклами более компактно. В действительности функция grep сильно отличается от одноименной команды UNIX - она не имеет параметров для нумерации строк или инвертирования критерия и не ограничивается проверками регулярных выражений. Например, чтобы отфильтровать из массива очень большие числа или определить, с какими ключами хэша ассоциированы очень большие значения, применяется следующая запись: @bigs = grep { $_ > 1_000_000 } @nums;
В следующем примере в @matching заносятся строки, полученные от команды who и начинающиеся с "gnat ": @matching = grep { /"gnat / } 'who';
(Sengineers = grep { $_->position() eq 'Engineer' } @employees; Из массива @employees извлекаются только те объекты, для которых метод position() возвращает строку Engineer. Grep позволяет выполнять и более сложные проверки: @secondary_assistance = grep { $_->income >= 26_000 &&
> Смотри также -------------------------------
4.14. Числовая сортировка массиваПроблемаТребуется отсортировать список чисел, однако функция Perl sort (но умолчанию) выполняет алфавитную сортировку в ASCII-порядке.РешениеВоспользуйтесь функцией Perl sort с оператором числового сравнения, оператор <=>:@Sorted = sort { $a <=> $b } @Unsorted; КомментарийПри вызове функции sort можно передавать необязательный программный блок, с помощью которого принятый по умолчанию алфавитный порядок сравнения заменяется вашим собственным. Функция сравнения вызывается каждый раз, когда sort сравнивает две величины. Сравниваемые значения загружаются в специальные пакетные переменные $а и $Ь, которые автоматически локализуются. Функция сравнения должна возвращать отрицательное число, если значение $а должно находиться в выходных данных перед $b; 0, если они совпадают или порядок несущественен; и положительное число, если значение $а должно находиться после $Ь. В Perl существуют два оператора с таким поведением: оператор <=> сортирует числа по возрастанию в числовом порядке, а стр сортирует строки по возрастанию в алфавитном порядке. По умолчанию sort использует сравнения в стиле стр. Следующий фрагмент сортирует список идентификаторов процессов (PID) в массиве @pids, предлагает пользователю выбрать один PID.и посылает сигнал TERM, за которым следует сигнал KILL. В необязательном программном блоке $а сравнивается с $Ь оператором <=>, что обеспечивает числовую сортировку.# @pids - несортированный массив идентификаторов процессов
При использовании условия $a<:::>$b или $а cmp $b список сортируется в порядке возрастания. Чтобы сортировка выполнялась в порядке убывания, достаточно поменять местами $а и $Ь в функции сравнения: @descending = sort { $b <=> $а } @unsorted; Функции сравнения должны быть последовательными; иначе говоря, функция всегда должна возвращать один и тот же ответ для одинаковых величин. Непоследовательные функции сравнения приводят к зацикливанию программы или ее аварийному завершению, особенно в старых версиях Perl. Также возможна конструкция вида sort ИМЯ СПИСОК, где ИМЯ - имя функции сравнения, возвращающей -1,0 или +1. В интересах быстродействия нормальные правила вызова не соблюдаются, а сравниваемые значения, как по волшебству, появляются в глобальных пакетных переменных $а и $Ь. Из-за особенностей вызова этой функции в Perl рекурсия в ней может не работать. Предупреждение: значения $а и $Ь задаются в пакете, активном в момент вызова sort, - а он может не совпадать с пакетом, в котором была откомпилирована передаваемая sort функция сравнения (ИМЯ)! Например: package Sort_Subs;
@аll = sort { $b <=> $а } 4, 19, 8, 3; За дополнительной информацией о пакетах обращайтесь к главе 10 "Подпрограммы".
Описание операторов стр и <=> в реrlор(1); описание функций kill, sort и sleep в perlfunc(1); рецепт 4.15. 4.15. Сортировка списка по вычисляемому полюПроблемаТребуется отсортировать список, руководствуясь более сложным критерием, нежели простыми строковыми или числовыми сравнениям. Такая проблема часто встречается при работе с объектами или сложными структурами данных ("отсортировать но третьему элементу массива, на который указывает данная ссылка"). Кроме того, она относится к сортировке по нескольким ключам - например, когда список сортируется по дню рождения, а затем по имени (когда у нескольких людей совпадают дни рождения).РешениеВоспользуйтесь нестандартной функцией сравнения в sort:@ordered = sort { compare() } @unordered; Для ускорения работы значение поля можно вычислить заранее: (Sprecomputed = map { [computeQ, $_] } @unordered;
Наконец, эти три шага можно объединить:
КомментарийО том, как пользоваться функциями сравнения, рассказано в рецепте 4.14. Помнимо использования встроенных операторов вроде <=>, можно конструировать более сложные условия:@ordered = sort { $a->name cmp $b->name } @employees; Функция sort часто используется подобным образом в циклах foreach:
Если вы собираетесь много работать с элементами, расположенными в определенном порядке, эффективнее будет сразу отсортировать их и работать с отсортированным списком: @sorted_employees = sort { $a->name cmp $b->name } @employees;
В функцию можно включить несколько условий и разделить их операторами |. Оператор [ | возвращает первое истинное (ненулевое) значение. Следовательно, сортировку можно выполнять по одному критерию, а при равенстве элементов (когда возвращаемое значение равно 0) сортировать но другому критерию. Получается "сортировка внутри сортировки": @sorted = sort {$a->name cmp $b->name
Первый критерий сравнивает имена двух работников. Если они не совпадают, I прекращает вычисления и возвращает результат cmp (сортировка в порядке возрастания имен). Но если имена совпадают, | ] продолжает проверку и возвращает результат <=> (сортировка в порядке убывания возраста). Полученный список будет отсортирован по именам и по возрасту в группах с одинаковыми именами. Давайте рассмотрим реальный пример сортировки. Мы собираем информацию обо всех пользователям в виде объектов User:: pwent, после чего сортируем их по именам и выводим отсортированный список: use User::pwent qw(getpwent);
@temp = map { [ length $_, $_ ] } @strings;
@sorted = map { $_->["! ] }
Теперь операции перечисляются в обратном порядке. Встречая конструкцию map/sort/map, читайте ее снизу вверх: (@strings: в конце указываются сортируемые данные. В данном случае это массив, но как вы вскоре убедитесь, это может быть вызов подпрограммы или даже команда в обратных апострофах. Подходит все, что возвращает список для последующей сортировки. тар: нижний вызов тар строит временный список анонимных массивов. Список содержит пары из предварительно вычисленного поля (length $_) и исходного элемента ($_). В этой строке показано, как происходит вычисление поля. sort: список анонимных массивов сортируется посредством сравнения предварительно вычисленных полей. По этой строке трудно о чем-то судить - разве что о том, будет ли список отсортирован в порядке возрастания или убывания. тар: вызов тар в начале команды превращает отсортированный список анонимных массивов в список исходных отсортированных элементов. Как правило, во всех конструкциях map/sort/map он выглядит одинаково. Ниже показан более сложный пример, в котором сортировка выполняется по первому числу, найденному в каждой строке ©fields: @temp = map { [ /(\d+.)/, $_ ] } @fields;
Регулярное выражение в первой строке извлекает из строки, обрабатываемой тар, первое число. Мы используем регулярное выражение /(\d+)/ в списковом контексте. Из этого фрагмента можно убрать временный массив. Код принимает следующий вид: @sorted_fields = map { $_->[1] }
print map { $_->[0] } # Целая строка sort {
Компактная конструкция map/sort/map больше напоминает программирование на Lisp и Scheme, нежели обычное наследие Perl - С и awk. Впервые она была предложена Рэндалом Шварцем (Randal Schwartz) и потому часто называется "преобразованием Шварца". > Смотри также -------------------------------- Описание функции sort в реrlfunс(1), описание операторов стр и <=> в perlop(1); рецепт 4.14.
|