разрушает глобальное значение $_ без предварительного сохранения! Следовательно, в начале любой процедуры (или блока), где $_ используется в подобной конструкции, всегда должно присутствовать объявление local $ .
Если в области действия (scope) присутствует лексическая переменная (объявленная с ту), то временная переменная будет иметь лексическую область действия, ограниченную данным циклом. В противном случае она будет считаться глобальной переменной с динамической областью действия. Во избежание странных побочных эффектов версия 5.004 допускает более наглядную и понятную запись:
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.
Проблема
Имеется ссылка ма массив. Вы хотите использовать 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.
Проблема
Требуется удалить из списка повторяющиеся элементы - например, при построении списка из файла или на базе выходных данных некоей команды. Рецепт в равной мере относится как к удалению дубликатов при вводе, так и в уже заполненных массивах.
Решение
Хэш используется для сохранения встречавшихся ранее элементов, а функция 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.
Проблема
Требуется найти элементы, которые присутствуют в одном массиве, но отсутствуют в другом.
Решение
Мы ищем элементы @А, которых нет в @В. Постройте хэш из ключей @В - он будет использоваться в качестве таблицы просмотра. Затем проверьте каждый элемент @А и посмотрите, присутствует ли он в @В.
Простейшая реализация
# Предполагается, что @А и @В уже загружены
%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 все элементы @В, присваивая соответствующему элементу хэша значение 1. Затем мы последовательно перебираем все элементы @А и проверяем, присутствует ли данный элемент в хэше %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.