Выполнение операции с каждым элементом списка
Проблема
Требуется повторить некоторую операцию для каждого элемента списка.
Массивы часто используются для сбора интересующей информации – например, имен пользователей, превысивших
свои дисковые квоты. Данные обрабатываются, при этом с каждым элементом массива выполняется некоторая операция.
Скажем, в примере с дисковыми квотами каждому пользователю отправляется предупреждающее сообщение.
Решение
Воспользуйтесь циклом fоreach:
foreach $item (LIST) {
# Выполнить некоторые действия с $item
}
Комментарий
Предположим, в массиве @bad_users собран список пользователей, превысивших свои дисковые квоты. В следующем фрагменте
для каждого нарушителя вызывается процедура complain():
foreach $user (@bad_users) {
complain($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),
«Я нашел то, что искал, и проверять остальные элементы незачем» (last) пли «Я тут кое-что изменил,
так что проверки и вычисления лучше выполнить заново» (redo).
Переменная, которой последовательно присваиваются все элементы списка, называется переменной цикла или
итератором. Если итератор не указан, используется глобальная переменная $_. Она используется по умолчанию
во многих строковых, списковых и файловых функциях Perl. В коротких программных блоках пропуск $_ упрощает
чтение программы (хотя в длинных блоках излишек неявных допущений делает программу менее понятной). Например:
foreach ('who') {
if (/tchrist/) {
print;
}
}
Или в сочетании с циклом while:
while (<FH>) { # Присвоить $_ очередную прочитанную строку
chomp; # Удалить из $_ конечный символ \п,
# если он присутствует
foreach (split) { # Разделить $_ по пропускам и получить @_
# Последовательно присвоить $_
# каждый из полученных фрагментов
$_ = reverse; # Переставить символы $_
# в противоположном порядке
print: # Вывести значение $_
}
}
Многочисленные применения $_ заставляют понервничать. Особенно беспокоит то, что значение $_ изменяется
как в foreach, так и в while. Возникает вопрос — не будет ли полная строка, прочитанная в $_ через <FH>,
навсегда потеряна после выполнения foreach?
К счастью, эти опасения необоснованны — по крайней мере, в данном случае. Perl не уничтожает старое значение $_,
поскольку переменная-итератор ($_) существует в течение всего выполнения цикла. При входе во внутренний цикл старое
значение автоматически сохраняется, а при выходе — восстанавливается.
Однако причины для беспокойства все же есть. Если цикл while будет внутренним, a foreach — внешним, ваши страхи в
полной мере оправдаются. В отличие от foreach конструкция while <FH> разрушает глобальное значение $_ без
предварительного сохранения! Следовательно, в начале любой процедуры (пли блока), где $_ используется в подобной
конструкции, всегда должно присутствовать объявление local $_.
Если в области действия (scope) присутствует лексическая переменная (объявленная с
mу), то временная переменная
будет иметь лексическую область действия, ограниченную данным циклом. В противном случае она будет считаться
глобальной переменной с динамической областью действия. Во избежание странных побочных эффектов версия 5.004
допускает более наглядную и понятную запись:
foreach my $item (@array) {
print "i = $item\n";
}
Цикл foreach обладает еще одним свойством: в цикле переменная-итератор является не копией, а скорее
синонимом (alias) текущего элемента. Иными словами, изменение итератора приводит к изменению каждого элемента списка.
@arrау = (1, 2, 3);
foreach $item (@array) {
$item--;
}
print "@array";
0 1 2
# Умножить каждый элемент @а и @Ь на семь
@а = (.5, 3);
@b = (0, 1);
foreach $item (@a, @b) {
$item *= 7;
print "$item ";
}
3.5 21 0 7
Модификация списков в цикле foreach оказывается более понятной и быстрой, чем в эквивалентном коде с циклом
for и указанием конкретных индексов. Это не ошибка; такая возможность была намеренно предусмотрена разработчиками языка.
Не зная о ней, можно случайно изменить содержимое списка. Теперь вы знаете.
Например, применение s/// к элементам списка, возвращаемого функцией values, приведет к модификации только
копий, но не самого хэша. Однако срез хэша @hash {keys %hash} дает нам нечто, что все же можно изменить с пользой для дела:
# Убрать пропуски из скалярной величины, массива и всех элементов хэша
foreach ($scalar, @array, @hash{keys %hash}) {
s/^\s+//;
s/\s+$//;
}
По причинам, связанным с эквивалентными конструкциями командного интерпретатора Борна для UNIX,
ключевые слова for и foreach взаимозаменяемы:
for $item (@array) { # То же, что и foreach $item (@array)
# Сделать что-тo
}
for (@array) { # То же, что и foreach $_ (@array)
# Сделать что-то
}
См. также
Proverte kod v komentariyah gde pro list tam oshibki detskie
Оставить комментарий:
|
|