Глава 4 Массивы

4.4. Выполнение операции с каждым элементом списка

Проблема

Требуется повторить некоторую операцию для каждого элемента списка. Массивы часто используются для сбора интересующей информации - например, имен пользователей, превысивших свои дисковые квоты. Данные обрабатываются, при этом с каждым элементом массива выполняется некоторая операция. Скажем, в примере с дисковыми квотами

Решение

Воспользуйтесь циклом to reach:

foreach $item (LIST) { # Выполнить некоторые действия с
$item }

Комментарий

Предположим, в массиве @bad_users собран синеок пользователей, превысивших свои дисковые квоты. В следующем фрагменте для каждого нарушителя вызывается процедура complain():

foreach $user (@bad_users) { cornplain($user);
}
Столь тривиальные случаи встречаются редко. Как правило, для генерации списка часто используются функции

foreach $var (sort keys %ENV) { print "$var=$ENV{$var}\n";
}
Функции sort и keys строят отсортированный список имей переменных окружения. Конечно, многократно используемые списки следует сохранять в массивах. Но для одноразовых задач удобнее работать со списком напрямую. Возможности этой конструкции расширяются не только за счет построения списка в foreach, по и за счет дополнительных операций в блоке кода. Одно из распространенных применений foreach - сбор информации о каждом элементе списка и принятие некоторого решения

foreach $user (@all_users) {
$disk_space = get_usage($user); # Определить объем используемого # дискового пространства
if ($disk_space > $MAX_QUOTA) { # Если он больше допустимого,.,
complain($user); # ... предупредить о нарушении.
}
}
Возможны и более сложные варианты. Команда last прерывает цикл, next переходит к следующему элементу, a redo возвращается к первой команде внутри блока. Фактически вы говорите: "Нет смысла продолжать, это не то, что мне нужно" (next), "Я нашел то, что иск Переменная, которой последовательно присваиваются все элементы списка, называется переменной цикла или итератором. Если итератор не указан, используется глобальная неременная $_. Она используется по умолчанию во многих строковых, списковых и файловых функ
foreach ('who') { if (/tchrist/) { print:
}
}
Или в сочетании с циклом while:

while () { # Присвоить $_ очередную прочитанную строку chomp; # Удалить из $_ конечный символ \n,
# если он присутствует foreach (split) { # Разделить $_ по пропускам и получить @_
# Последовательно присвоить $_
# каждый из полученных фрагментов
$_ = reverse;
# Переставить символы $_
# в противоположном порядке print:
# Вывести значение $_
}
}
Многочисленные применения $_ заставляют понервничать. Особенно беспокоит то, что значение $_ изменяется как в foreach, так и в while. Возникает вопрос - не будет ли полная строка, прочитанная в $_ через , навсегда потеряна после выполнения foreach? К счастью, эти опасения необоснованны - по крайней мере, в данном случае. Perl не уничтожает старое значение $_, поскольку переменная-итератор ($_) существует в течение всего выполнения цикла. При входе во внутренний цикл старое значение автоматически сох Однако причины для беспокойства все же есть. Если цикл while будет внутренним, a foreach - внешним, ваши страхи в полной мере оправдаются. В отличие от foreach конструкция while разрушает глобальное значение $_ без предварительного сохранения! Следов Если в области действия (scope) присутствует лексическая переменная (объявленная с ту), то временная переменная будет иметь лексическую область действия, ограниченную данным циклом. В противном случае она будет считаться глобальной переменной с динамическ

foreach my $item (Oarray) { print "i = $item\n";
}
Цикл foreach обладает еще одним свойством: в цикле иеременная-итератор является не копией, а скорее синонимом (alias) текущего элемента. Иными словами, изменение итератора приводит к изменению каждого элемента списка.

@аrrау = (1,2,3);
foreach $item (©array) { $item--;
}
print "@array";
0 1 2
# Умножить каждый элемент @а и @Ь на семь @а = (.5, З): @Ь = (0, 1);
foreach $item (@a, @b) <
$item .= 7;
print "$item ";
} 3.5 21 0 7
Модификация списков в цикле foreach оказывается более понятной и быстрой, чем в эквивалентном коде с циклом for и указанием конкретных индексов. Это не ошибка; такая возможность была намеренно предусмотрена разработчиками языка. Не зная о ней, можно случа Например, применение s/// к элементам списка, возвращаемого функцией values, приведет к модификации только копий, но не самого хэша. Однако срез X3Uia@hash{keys %hash} (см. главу 5 "Хунт") дает нам нечто, что все же можно изменить с пользой для дела: # Убрать пропуски из скалярной величины, массива и всех элементов хэша
foreach ($scalar, @array, @hash{keys %hash}) {
s/-\s+//;
s/\s+$//;
}
По причинам, связанным с эквивалентными конструкциями командного интерпретатора Борна для UNIX, ключевые слова for и foreach взаимозаменяемы:

for $item (@array) { # То же, что и foreach $item (@array) # Сделать что-то
}
for (@аrrау) { # To же, что и foreach $_ (@array)
}
Подобный стиль часто показывает, что автор занимается написанием или сопровождением сценариев интерпретатора и связан с системным администрированием UNIX. Жизнь таких люден и без того сложна, поэтому не стоит судить их слишком строго.

> Смотри также -------------------------------- Разделы "For Loops", "Foreach Loops" н "Loop Control" perlsyn(1) раздел "Temporary Values via localQ" per!sub(l). Оператор local() рассматривается в рецепте 10.13, a my() - в рецепте 10.2.

4.5. Перебор массива по ссылке

Проблема

Имеется ссылка ма массив. Вы хотите использовать f о reach для обращения к каждому элементу массива.

Решение

Для перебора разыменованного (dereferenced) массива используется цикл to reach или for:
# Перебор элементов массива
$ARRAYREF foreach $item(@'$ARRAYREF) {# Сделать что-то с $item
}
for ($i = 0; $l <= $#$arrayref; $i++) { # Сделать что-то с
$ARARAYREF->[$i]
}

Комментарий

Приведенное решение предполагает, что у вас имеется скалярная переменная, содержащая ссылку на массив. Это позволяет делать следующее:

@fruits = ( "Apple", "Blackberry" );
$fruit_ref = \@fruits;
foreach $fruit (@$fruit_ref) {
print "$fruit tastes good in a pie.\n";
}

Apple tastes good in a pie,
Blackberry tastes good in a pie. Цикл foreach можно переписать в цикле for следующего вида:
for ($i=0; $i <= $#$fruit_ref; $i++) {
print "$fruit_ref->[$i] tastes good in a pie.\n";
}
Однако ссылка на массив нередко является результатом более сложного выражения. Для превращения такого результата в массив применяется конструкция @{ EXPR }:
$namelist{felines} = \@rogue_cats;
foreach $cat ( @>{ $namelist{felines} } ) {
print "Scat purrs hypnotically..\n";
}
print "--More--\nYou are controlled.\n";

Как и прежде, цикл foreach можно заменить эквивалентным циклом for:
for ($i=0; $i <= $#{ $namelist{felines} }; $i++) {
print "$namellst{felines}[$i] purrs hypnotically.\n";
}


[> Смотри также
perlref(l) и perllol{\y, рецепты 4.4; 11.1.

4.6. Выборка уникальных элементов из списка

Проблема

Требуется удалить из списка повторяющиеся элементы - например, при построении списка из файла или на базе выходных данных некоей команды. Рецепт в равной мере относится как к удалению дубликатов при вводе, так и в уже заполненных массивах.

Решение

Хэш используется для сохранения встречавшихся ранее элементов, а функция keys - для их извлечения. Принятая в Perl концепция истинности позволит уменьшить объем программы и ускорить ее работу. Прямолинейно
%seen = ();
@uniq =();
foreach $item (@list) { unless ($seen{$ltem})
# Если мы попали сюда, значит, элемент не встречался ранее
$seen{$ltem} = 1;
push(@uniq, $item);
}
}

Быстро
%seen = ();
foreach $item (Olist) {
push(@uniq, $item) unless $seen{$item}++;
} Аналогично, но с пользовательской функцией

%seen = ();
foreach $item (@list) {
some_func($item) unless $seen{$item}++;
} Быстро, но по-другому

%seen =();
foreach $iteni (@list) { $seen{$item}++;
} @unlq = keys %seen; Быстро и совсем по-другому
%seen =();
@unique = grер { ! $seen{$_} ++ } @list:

Комментарий

Суть сводится к простому вопросу - встречался ли данный элемент раньше? Хэши идеально подходят для подобного поиска. В нервом варианте ("Прямолинейно") массив уникальных значении строится но мере обработки исходного списка, а для регистрации встречавшихся Второй вариант ("Быстро") представляет собой самый естественный способ решения подобных задач в Perl. Каждый раз, когда встречается новое значение, в хэш с помощью оператора ++ добавляется новый элемент. Побочный эффект состоит в том, что в хэш попадают в Третий вариант ("Аналогично, но с пользовательской функцией") похож на второй, однако вместо сохранения значения мы вызываем некоторую пользовательскую функцию и передаем ей это значение в качестве аргумента. Если ничего больше не требуется, хранить отдел В следующем варианте ("Быстро, но по-другому") уникальные ключи извлекаются из хэша %seen лишь после того, как он будет полностью построен. Иногда это удобно, но исходный порядок элементов утрачивается. В последнем варианте ("Быстро и совсем по-другому") построение хэша %seen объединяется с извлечением уникальных элементов. При этом сохраняется исходный порядок элементов. Использование хэша для записи значений имеет два побочных эффекта: при обработке длинных списков расходуется много памяти, а список, возвращаемый keys, не отсортирован в алфавитном или числовом порядке и не сохраняет порядок вставки. Ниже показано, как обрабатывать данные по мере ввода. Мы используем 'who' для получения сведений о текущем списке пользователей, а перед обновлением хэша извлекаем из каждой строки имя пользователя: # Построить список зарегистрированных пользователей с удалением дубликатов
%ucnt =();
for ('who') {
s/\s.*\n//; # Стереть от первого пробела до конца строки
# остается имя пользователя
$ucnt{$_}++; # Зафиксировать присутствие данного пользователя }
# Извлечь и вывести уникальные ключи
@users = sort keys %ucnt;
print "users logged in: @users\n";


> Смотри также ------------------------------- Раздел "Foreach Loops" perlsyn(1); описание функции keys в perlfunc(1). Аналогичное применение хэтей продемонстрировано в рецептах 4.7 и 4.8.

4.7. Поиск элементов одного массива, отсутствующих в другом массиве

Проблема

Требуется найти элементы, которые присутствуют в одном массиве, но отсутствуют в другом.

Решение

Мы ищем элементы @А, которых нет в @В. Постройте хэш из ключей @В - он будет использоваться в качестве таблицы просмотра. Затем проверьте каждый элемент @А и посмотрите, присутствует ли он в @В. Простейшая реализация # Предполагается, что @А и @В уже загружены
%seen =(); # Хэш для проверки принадлежности элемента В
@aonlу =(); # Ответ
# Построить таблицу просмотра
foreach $item (@B) { $seen{$item} = 1 }
# Найти элементы @А, отсутствующие в @В
foreach $item (@A) { unless $item (@A) {
# Отсутствует в %seen, поэтому добавить в @aоnlу
push(@aonly, $item):
}
}
1my %seen; # Таблица просмотра
my @aonly;
# Ответ
# Построить таблицу просмотра
@seen{@B} =();
foreach $item (@A) {
push(@aonly, $item.) unless exists $seen{$item};
}

Комментарий

Практически любая проблема, при которой требуется определить принадлежность скалярной величины к списку или массиву, решается в Perl с помощью хэ-uieii. Сначала мы обрабатываем @В и регнстрлрусм в хэше %seen все элементы @В, присваивая соответствующему эл В приведенном фрагменте ответ будет содержать дубликаты из массива @А. (Ситуацию нетрудно исправить, для этого достаточно включать элементы @А в %seen но мере обработки:
foreach $item (@А) {
push (@aonly, $item) unless $seen{$item};
$ seen{$item} =1; # Пометить как уже встречавшийся
}
Эти решения в основном отличаются по способу построения хэша. В первом варианте перебирается содержимое @В. Во втором для инициализации хэша используется срез. Следующий пример наглядно демонстрирует срезы хэша. Фрагмент:

$hash;"key1"} = 1;
$hash{"key2"} = 2;
# эквивалентен следующему:
@hash{"key1", "key2"} = (1,2);

Список в фигурных скобках содержит ключи, а список справа - значения. В нервом решении %seen инициализируется перебором всех элементов @В и присваиванием соответствующим элементам %seen значения 1. Во втором мы просто говорим:
@seen{@B} = ():
В этом случае элементы @В используются и качестве ключей для %seen, а с ними ассоциируется undef, поскольку количество значении в правой части меньше количества позиции для их размещения. Показанный вариант работает, поскольку мы проверяем только факт существования ключа, а не его логическую истинность или определенность. Но даже если с элементами @В потребуется ассоциировать истинные значения, срез все равно позволит сократить объем кода:
@seen{@B} = (1) х @В;


Смотри также -------------------------------- Описание срезов хэшей в perldata(1). Аналогичное применение хэшей продемонстрировано в рецептах 4.7 и 4.8.
© copyright 2000 Soft group

Используются технологии uCoz