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

Проблема

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

Решение

Воспользуйтесь циклом 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) присутствует лексическая переменная (объявленная с ), то временная переменная будет иметь лексическую область действия, ограниченную данным циклом. В противном случае она будет считаться глобальной переменной с динамической областью действия. Во избежание странных побочных эффектов версия 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)
  # Сделать что-то
}

См. также




2013-09-10 17:05:19

Proverte kod v komentariyah gde pro list tam oshibki detskie




Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: