perlfaq4
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Данные: Числа
- Почему я получаю длинные десятичные дроби (например, 19.9499999999999) вместо ожидаемых чисел (например, 19.95)?
- Почему int() работает некорректно?
- Почему моя восьмеричная информация интерпретируется неверно?
- Есть ли в Perl функция round()? Что насчёт ceil() и floor()? Тригонометрические функции?
- Как преобразовать между числовыми представлениями/основаниями/системами счисления?
- Почему & работает не так, как мне нужно?
- Как умножить матрицы?
- Как выполнить операцию над последовательностью целых чисел?
- Как вывести римские цифры?
- Почему мои случайные числа не случайны?
- Как получить случайное число между X и Y?
- Данные: Даты
- Как найти день или неделю года?
- Как найти текущий век или тысячелетие?
- Как сравнить две даты и найти разницу?
- Как преобразовать строку в количество секунд с эпохи?
- Как найти юлианский день?
- Как найти дату вчерашнего дня?
- Есть ли в Perl проблема с годом 2000 или 2038? Соответствует ли Perl стандарту Y2K?
- Данные: Строки
- Как валидировать входные данные?
- Как раскодировать строку?
- Как удалить последовательные пары символов?
- Как раскрыть вызовы функций в строке?
- Как найти совпадения/вложенность чего-либо?
- Как перевернуть строку?
- Как расширить табуляцию в строке?
- Как переформатировать абзац?
- Как получить доступ к N символам строки или изменить их?
- Как изменить N-ый экземпляр чего-либо?
- Как посчитать количество вхождений подстроки в строке?
- Как сделать все слова на одной строке заглавными?
- Как разделить строку, ограниченную символом [символ], кроме случаев, когда он находится внутри [символ]?
- Как удалить пробелы в начале/конце строки?
- Как дополнить строку пробелами или число нулями?
- Как извлечь выбранные столбцы из строки?
- Как найти значение Soundex строки?
- Как расширить переменные в текстовых строках?
- В чём проблема с постоянным цитированием «$vars»?
- Почему мои документы <<HERE не работают?
- Данные: Массивы
- В чём разница между списком и массивом?
- В чём разница между $array[1] и @array[1]?
- Как удалить дубликаты из списка или массива?
- Как определить, содержится ли определённый элемент в списке или массиве?
- Как вычислить разность двух массивов? Как вычислить пересечение двух массивов?
- Как проверить, равны ли два массива или два хэша?
- Как найти первый элемент массива, для которого выполняется условие?
- Как обрабатывать связанные списки?
- Как обрабатывать циклические списки?
- Как случайным образом перемешать массив?
- Как обработать/изменить каждый элемент массива?
- Как выбрать случайный элемент из массива?
- Как переставить N элементов списка?
- Как отсортировать массив по (любому) критерию?
- Как манипулировать массивами бит?
- Почему defined() возвращает true для пустых массивов и хэшей?
- Данные: Хэши (Ассоциативные массивы)
- Как обработать весь хэш?
- Как объединить два хэша?
- Что происходит, если добавить или удалить ключи из хэша во время итерации по нему?
- Как найти элемент хэша по значению?
- Как узнать, сколько элементов в хэше?
- Как отсортировать хэш (по желанию по значению, а не ключу)?
- Как всегда поддерживать хэш отсортированным?
- В чем разница между «delete» и «undef» для хэшей?
- Почему мои привязанные хэши не делают различия defined/exists?
- Как перезапустить операцию each() на части пути?
- Как получить уникальные ключи из двух хэшей?
- Как сохранить многомерный массив в файле DBM?
- Как заставить хэш запоминать порядок, в котором я поместил элементы?
- Почему передача подпрограмме неопределенного элемента в хэше создает его?
- Как создать в Perl эквивалент структуры C/класса C++/хэша или массива хэшей или массивов?
- Как использовать ссылку в качестве ключа хэша?
- Как проверить, существует ли ключ в многоуровневом хэше?
- Как предотвратить добавление нежелательных ключей в хэш?
- Данные: Разное
- Как правильно обрабатывать двоичные данные?
- Как определить, является ли скаляр числом/целым/целым/вещественным?
- Как сохранить постоянные данные между вызовами программы?
- Как вывести или скопировать рекурсивную структуру данных?
- Как определить методы для каждого класса/объекта?
- Как проверить контрольную сумму кредитной карты?
- Как упаковать массивы double или float для кода XS?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq4 - Обработка данных
ВЕРСИЯ
версия 5.20190126
ОПИСАНИЕ
Этот раздел FAQ отвечает на вопросы, связанные с обработкой чисел, дат, строк, массивов, хэшей и другими проблемами работы с данными.
Данные: Числа
Почему я получаю длинные десятичные дроби (например, 19.9499999999999) вместо ожидаемых чисел (например, 19.95)?
Для подробного объяснения см. статью Дэвида Голдберга «Что каждый специалист по компьютерным наукам должен знать о числах с плавающей точкой» (http://web.cse.msu.edu/~cse320/Documents/FloatingPoint.pdf).
Внутренне компьютер представляет числа с плавающей точкой в двоичном формате. Цифровые (в смысле степеней двойки) компьютеры не могут хранить все числа точно. Некоторые вещественные числа теряют точность в процессе. Это проблема с хранением чисел в компьютерах и затрагивает все языки программирования, а не только Perl.
perlnumber показывает подробности представления и преобразования чисел.
Чтобы ограничить количество десятичных знаков в ваших числах, можно использовать функцию printf или sprintf. См. "Арифметика с плавающей точкой" в perlop для получения дополнительной информации.
printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3; Почему int() сломан?
Ваша int(), скорее всего, работает нормально. Это числа, которые не совсем такие, как вы ожидаете.
Сначала посмотрите ответ на вопрос «Почему я получаю длинные десятичные дроби (например, 19.9499999999999) вместо ожидаемых чисел (например, 19.95)?».
Например, это
print int(0.6/0.2-2), "\n"; на большинстве компьютеров выведет 0, а не 1, потому что даже такие простые числа, как 0,6 и 0,2, не могут быть точно представлены числами с плавающей точкой. То, что вы считаете «тройкой» в примере, на самом деле больше похоже на 2.9999999999999995559.
Почему мои данные в восьмеричном формате интерпретируются неверно?
(предложено brian d foy)
Вероятно, вы пытаетесь преобразовать строку в число, которое Perl интерпретирует только как десятичное число. При преобразовании строки в число Perl игнорирует ведущие пробелы и нули, а затем предполагает, что оставшиеся цифры находятся в системе счисления с основанием 10:
my $string = '0644';
print $string + 0; # prints 644
print $string + 44; # prints 688, certainly not octal! Эта проблема обычно связана с одним из встроенных Perl-функций, которые имеют то же имя, что и Unix-команда, использующая восьмеричные числа в качестве аргументов в командной строке. В этом примере chmod в командной строке знает, что первый аргумент является восьмеричным, так как это ее функция:
%prompt> chmod 644 file Если вы хотите использовать те же цифры (644) в Perl, нужно указать Perl, что они должны рассматриваться как восьмеричные числа, либо добавив префикс 0, либо используя oct:
chmod( 0644, $filename ); # right, has leading zero
chmod( oct(644), $filename ); # also correct Проблема возникает, когда вы берете числа из чего-то, что Perl считает строкой, например, из аргумента командной строки в @ARGV:
chmod( $ARGV[0], $filename ); # wrong, even if "0644"
chmod( oct($ARGV[0]), $filename ); # correct, treat string as octal Вы всегда можете проверить используемое значение, выведя его в восьмеричной нотации, чтобы убедиться, что оно соответствует тому, что вы ожидаете. Выведите его в восьмеричном и десятичном форматах:
printf "0%o %d", $number, $number; Есть ли в Perl функция round()? А функции ceil() и floor()? Тригонометрические функции?
Запомните, что int() просто усекает к нулю. Для округления до определенного числа знаков обычно проще использовать sprintf() или printf().
printf("%.3f", 3.1415926535); # prints 3.142 Модуль POSIX (входит в стандартную поставку Perl) реализует ceil(), floor() и ряд других математических и тригонометрических функций.
use POSIX;
my $ceil = ceil(3.5); # 4
my $floor = floor(3.5); # 3 В Perl-версиях 5.000 до 5.003 тригонометрические функции реализовывались в модуле Math::Complex. С версии 5.004 модуль Math::Trig (входит в стандартную поставку Perl) реализует тригонометрические функции. Внутренне он использует модуль Math::Complex, и некоторые функции могут выходить за пределы вещественной оси в комплексную плоскость, например, обратный синус от 2.
Округление в финансовых приложениях может иметь серьезные последствия, и используемый метод округления должен быть точно определён. В таких случаях, скорее всего, не стоит полагаться на используемый Perl метод округления, а следует самостоятельно реализовать необходимую функцию округления.
Чтобы понять почему, обратите внимание на проблему с альтернацией на середине:
for (my $i = -5; $i <= 5; $i += 0.5) { printf "%.0f ",$i }
-5 -4 -4 -4 -3 -2 -2 -2 -1 -0 0 0 1 2 2 2 3 4 4 4 5 Не нужно винить Perl. Это то же самое, что и в C. Стандарт IEEE требует именно этого. Perl-числа, абсолютное значение которых — целые числа меньше 2**31 (на 32-битных машинах), будут работать практически как математические целые числа. Для других чисел гарантий нет.
Как преобразовать между числовыми представлениями/основаниями/системами счисления?
Как всегда в Perl, существует несколько способов решения этой задачи. Ниже приведены несколько примеров подходов к преобразованию между различными представлениями чисел. Этот список не претендует на полноту.
Некоторые примеры далее в perlfaq4 используют модуль Bit::Vector из CPAN. Причина, по которой вы можете выбрать Bit::Vector вместо встроенных функций Perl, заключается в том, что он работает с числами ЛЮБОГО размера, что он оптимизирован по скорости для некоторых операций, и для некоторых программистов обозначение может быть знакомым.
- Как преобразовать шестнадцатеричное в десятичное
-
Используя встроенное преобразование Perl для обозначения
0x:my $dec = 0xDEADBEEF;Используя функцию
hex:my $dec = hex("DEADBEEF");Используя
pack:my $dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));Используя модуль CPAN
Bit::Vector:use Bit::Vector; my $vec = Bit::Vector->new_Hex(32, "DEADBEEF"); my $dec = $vec->to_Dec(); - Как преобразовать из десятичного в шестнадцатеричное
-
Используя
sprintf:my $hex = sprintf("%X", 3735928559); # upper case A-F my $hex = sprintf("%x", 3735928559); # lower case a-fИспользуя
unpack:my $hex = unpack("H*", pack("N", 3735928559));Используя Bit::Vector:
use Bit::Vector; my $vec = Bit::Vector->new_Dec(32, -559038737); my $hex = $vec->to_Hex();И Bit::Vector поддерживает нечетное количество битов:
use Bit::Vector; my $vec = Bit::Vector->new_Dec(33, 3735928559); $vec->Resize(32); # suppress leading 0 if unwanted my $hex = $vec->to_Hex(); - Как преобразовать из восьмеричного в десятичное
-
Используя встроенное преобразование Perl для чисел с ведущими нулями:
my $dec = 033653337357; # note the leading 0!Используя функцию
oct:my $dec = oct("33653337357");Используя Bit::Vector:
use Bit::Vector; my $vec = Bit::Vector->new(32); $vec->Chunk_List_Store(3, split(//, reverse "33653337357")); my $dec = $vec->to_Dec(); - Как преобразовать из десятичного в восьмеричное
-
Используя
sprintf:my $oct = sprintf("%o", 3735928559);Используя Bit::Vector:
use Bit::Vector; my $vec = Bit::Vector->new_Dec(32, -559038737); my $oct = reverse join('', $vec->Chunk_List_Read(3)); - Как преобразовать из двоичного в десятичное
-
Perl 5.6 позволяет записывать двоичные числа непосредственно с помощью обозначения
0b:my $number = 0b10110110;Используя
oct:my $input = "10110110"; my $decimal = oct( "0b$input" );Используя
packиord:my $decimal = ord(pack('B8', '10110110'));Используя
packиunpackдля более длинных строк:my $int = unpack("N", pack("B32", substr("0" x 32 . "11110101011011011111011101111", -32))); my $dec = sprintf("%d", $int); # substr() is used to left-pad a 32-character string with zeros.Используя Bit::Vector:
my $vec = Bit::Vector->new_Bin(32, "11011110101011011011111011101111"); my $dec = $vec->to_Dec(); - Как преобразовать из десятичного в двоичное
-
Используя
sprintf(perl 5.6+):my $bin = sprintf("%b", 3735928559);Используя
unpack:my $bin = unpack("B*", pack("N", 3735928559));Используя Bit::Vector:
use Bit::Vector; my $vec = Bit::Vector->new_Dec(32, -559038737); my $bin = $vec->to_Bin();Остальные преобразования (например, hex -> oct, bin -> hex и т. д.) оставлены в качестве упражнения для заинтересованного читателя.
Почему & не работает так, как я хочу?
Поведение бинарных арифметических операторов зависит от того, используются ли они с числами или строками. Операторы обрабатывают строку как серию битов и работают с этим (строка "3" представляет собой битовую последовательность 00110011). Операторы работают с двоичной формой числа (число 3 обрабатывается как битовая последовательность 00000011).
Таким образом, оператор 11 & 3 выполняет операцию "и" над числами (давая результат 3). Оператор "11" & "3" выполняет операцию "и" над строками (давая результат "1").
Большинство проблем с & и | возникают потому, что программист считает, что у него есть число, но на самом деле это строка, или наоборот. Чтобы избежать этого, явно преобразуйте аргументы в строки (используя "" или qq()) или явно преобразуйте их в числа (используя 0+$arg). Остальные возникают потому, что программист пишет:
if ("\020\020" & "\101\101") {
# ...
} но строка, состоящая из двух нулевых байтов (результат "\020\020" & "\101\101"), не является ложным значением в Perl. Вам нужно:
if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
# ...
} Как умножить матрицы?
Используйте модули Math::Matrix или Math::MatrixReal (доступны из CPAN) или расширение PDL (также доступно из CPAN).
Как выполнить операцию над последовательностью целых чисел?
Чтобы вызвать функцию для каждого элемента в массиве и собрать результаты, используйте:
my @results = map { my_func($_) } @array; Например:
my @triple = map { 3 * $_ } @single; Чтобы вызвать функцию для каждого элемента массива, но игнорировать результаты:
foreach my $iterator (@array) {
some_func($iterator);
} Чтобы вызвать функцию для каждого целого числа в (небольшом) диапазоне, вы можете использовать:
my @results = map { some_func($_) } (5 .. 25); но вы должны знать, что в этом виде оператор .. создаёт список всех целых чисел в диапазоне, что может занимать много памяти для больших диапазонов. Однако проблема не возникает при использовании .. внутри цикла for, поскольку в этом случае оператор диапазона оптимизирован для итерации по диапазону без создания всего списка. Поэтому
my @results = ();
for my $i (5 .. 500_005) {
push(@results, some_func($i));
} или даже
push(@results, some_func($_)) for 5 .. 500_005; не создаст промежуточного списка из 500 000 целых чисел.
Как вывести римские цифры?
Получите модуль http://www.cpan.org/modules/by-module/Roman.
Почему мои случайные числа не случайны?
Если вы используете версию Perl до 5.004, вы должны вызвать srand один раз в начале вашей программы для инициализации генератора случайных чисел.
BEGIN { srand() if $] < 5.004 } Версии 5.004 и выше автоматически вызывают srand в начале. Не вызывайте srand более одного раза — это сделает ваши числа менее случайными, а не более.
Компьютеры хорошо предсказуемы и плохо генерируют случайные числа (несмотря на видимость, вызванную ошибками в ваших программах :-). Статья «Случайные» в коллекции «Гораздо больше, чем вы когда-либо хотели знать» в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz, любезно предоставленная Томом Фёниксом, рассказывает об этом подробнее. Джон фон Нейман сказал: «Любой, кто пытается генерировать случайные числа с помощью детерминированных средств, конечно, живёт в состоянии греха».
Perl полагается на базовую систему для реализации rand и srand; на некоторых системах сгенерированные числа недостаточно случайны (особенно на Windows: см. http://www.perlmonks.org/?node_id=803632). Несколько модулей CPAN в пространстве имен Math реализуют лучшие псевдогенераторы случайных чисел; см., например, Math::Random::MT («Мерсенн Твистер», быстродействие) или Math::TrulyRandom (использует недостатки таймера системы для генерации случайных чисел, что довольно медленно). Больше алгоритмов для генерации случайных чисел описаны в «Numerical Recipes in C» по адресу http://www.nr.com/
Как получить случайное число между X и Y?
Чтобы получить случайное число между двумя значениями, вы можете использовать встроенную функцию rand() для получения случайного числа между 0 и 1. Оттуда вы переместите его в желаемый диапазон.
rand($x) возвращает число такое, что 0 <= rand($x) < $x. Таким образом, то, что вы хотите, чтобы Perl вычислил, — это случайное число в диапазоне от 0 до разницы между вашим X и Y.
То есть, чтобы получить число между 10 и 15 включительно, вы хотите случайное число между 0 и 5, которое затем можно добавить к 10.
my $number = 10 + int rand( 15-10+1 ); # ( 10,11,12,13,14, or 15 ) Поэтому вы получаете следующую простую функцию для абстрагирования этого. Она выбирает случайное целое число между двумя заданными целыми числами (включительно). Например: random_int_between(50,120).
sub random_int_between {
my($min, $max) = @_;
# Assumes that the two arguments are integers themselves!
return $min if $min == $max;
($min, $max) = ($max, $min) if $min > $max;
return $min + int rand(1 + $max - $min);
} Данные: Даты
Как найти день или неделю года?
День года находится в списке, возвращаемом функцией localtime. Без аргумента localtime используется текущее время.
my $day_of_year = (localtime)[7]; Модуль POSIX также может форматировать дату как день или неделю года.
use POSIX qw/strftime/;
my $day_of_year = strftime "%j", localtime;
my $week_of_year = strftime "%W", localtime; Чтобы получить день года для любой даты, используйте mktime из модуля POSIX для получения времени в эпохальных секундах для аргумента функции localtime.
use POSIX qw/mktime strftime/;
my $week_of_year = strftime "%W",
localtime( mktime( 0, 0, 0, 18, 11, 87 ) ); Вы также можете использовать Time::Piece, который входит в Perl и предоставляет метод localtime, возвращающий объект:
use Time::Piece;
my $day_of_year = localtime->yday;
my $week_of_year = localtime->week; Модуль Date::Calc также предоставляет две функции для вычисления этих значений:
use Date::Calc;
my $day_of_year = Day_of_Year( 1987, 12, 18 );
my $week_of_year = Week_of_Year( 1987, 12, 18 ); Как найти текущий век или тысячелетие?
Используйте следующие простые функции:
sub get_century {
return int((((localtime(shift || time))[5] + 1999))/100);
}
sub get_millennium {
return 1+int((((localtime(shift || time))[5] + 1899))/1000);
} На некоторых системах функция strftime() модуля POSIX была расширена нестандартным образом, чтобы использовать формат %C, который иногда называют «веком». Это не так, потому что на большинстве таких систем это только две первые цифры четырёхзначного года, и поэтому нельзя надёжно определить текущий век или тысячелетие.
Как сравнить две даты и найти разницу?
(вклад brian d foy)
Вы можете просто сохранить все даты как число, а затем вычесть их. Однако жизнь не всегда так проста.
Модуль Time::Piece, который входит в Perl, заменяет localtime на версию, возвращающую объект. Он также перегружает операторы сравнения, так что вы можете сравнивать даты напрямую:
use Time::Piece;
my $date1 = localtime( $some_time );
my $date2 = localtime( $some_other_time );
if( $date1 < $date2 ) {
print "The date was in the past\n";
} Вы также можете получить разницу с помощью вычитания, что возвращает объект Time::Seconds:
my $date_diff = $date1 - $date2;
print "The difference is ", $date_diff->days, " days\n"; Если вам нужно работать с отформатированными датами, модули Date::Manip, Date::Calc или DateTime могут помочь вам.
Как преобразовать строку в эпохальные секунды?
Если это достаточно регулярная строка, имеющая всегда одинаковый формат, вы можете разбить её и передать части в timelocal в стандартном модуле Time::Local. В противном случае, вам следует изучить модули Date::Calc, Date::Parse и Date::Manip из CPAN.
Как найти юлианский день?
(вклад brian d foy и Dave Cross)
Вы можете использовать модуль Time::Piece, который входит в стандартную библиотеку Perl, для преобразования даты/времени в юлианский день:
$ perl -MTime::Piece -le 'print localtime->julian_day'
2455607.7959375 Или модифицированный юлианский день:
$ perl -MTime::Piece -le 'print localtime->mjd'
55607.2961226851 Или даже день года (который некоторые люди считают юлианским днём):
$ perl -MTime::Piece -le 'print localtime->yday'
45 Вы также можете сделать то же самое с модулем DateTime:
$ perl -MDateTime -le'print DateTime->today->jd'
2453401.5
$ perl -MDateTime -le'print DateTime->today->mjd'
53401
$ perl -MDateTime -le'print DateTime->today->doy'
31 Вы можете использовать модуль Time::JulianDay, доступный в CPAN. Однако убедитесь, что вы действительно хотите найти юлианский день, так как у многих людей есть разные представления о юлианских днях (см., например, http://www.hermetic.ch/cal_stud/jdn.htm):
$ perl -MTime::JulianDay -le 'print local_julian_day( time )'
55608 END_OF_DOCUMENT_MARKER Как найти дату вчерашнего дня?
(предоставлено brian d foy)
Для правильного выполнения можно использовать один из модулей Date, поскольку они работают с календарями, а не со временем. Модуль DateTime упрощает задачу и предоставляет то же время суток, только на день раньше, независимо от изменений летнего времени:
use DateTime;
my $yesterday = DateTime->now->subtract( days => 1 );
print "Yesterday was $yesterday\n"; Также можно использовать модуль Date::Calc с его функцией Today_and_Now.
use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );
print "@date_time\n"; Большинство людей пытаются использовать время, а не календарь, чтобы определить даты, но это предполагает, что каждый день длится 24 часа. Для большинства людей есть два дня в году, когда это не так: переход на летнее и зимнее время сбивает с толку. Например, остальные предложения будут иногда неверны:
Начиная с Perl 5.10, Time::Piece и Time::Seconds входят в стандартную дистрибуцию, поэтому вы можете подумать, что можете сделать что-то вроде этого:
use Time::Piece;
use Time::Seconds;
my $yesterday = localtime() - ONE_DAY; # WRONG
print "Yesterday was $yesterday\n"; Модуль Time::Piece экспортирует новый localtime, который возвращает объект, а Time::Seconds экспортирует константу ONE_DAY, которая представляет собой заданное количество секунд. Это означает, что он всегда возвращает время 24 часа назад, что не всегда является вчерашним днём. Это может вызвать проблемы в конце летнего времени, когда один день длится 25 часов.
Та же проблема возникает с Time::Local, который даст неправильный ответ для тех же особых случаев:
# contributed by Gunnar Hjalmarsson
use Time::Local;
my $today = timelocal 0, 0, 12, ( localtime )[3..5];
my ($d, $m, $y) = ( localtime $today-86400 )[3..5]; # WRONG
printf "Yesterday: %d-%02d-%02d\n", $y+1900, $m+1, $d; Есть ли у Perl проблема с годом 2000 или 2038? Perl совместим с Y2K?
(предоставлено brian d foy)
У самого Perl никогда не было проблемы Y2K, хотя это не мешало людям создавать проблемы Y2K самостоятельно. Обратитесь к документации для localtime, чтобы узнать о правильном его использовании.
Начиная с Perl 5.12, localtime и gmtime могут обрабатывать даты после 03:14:08 19 января 2038 года, когда 32-битное время переполняется. Вы все равно можете получить предупреждение на 32-битном perl:
% perl5.12 -E 'say scalar localtime( 0x9FFF_FFFFFFFF )'
Integer overflow in hexadecimal number at -e line 1.
Wed Nov 1 19:42:39 5576711 На 64-битном perl вы можете получить ещё более поздние даты для действительно долгосрочных проектов:
% perl5.12 -E 'say scalar gmtime( 0x9FFF_FFFFFFFF )'
Thu Nov 2 00:42:39 5576711 Однако вам всё ещё не повезёт, если вам нужно отслеживать распад протонов.
Данные: Строки
Как валидировать входные данные?
(предоставлено brian d foy)
Существует множество способов убедиться, что значения соответствуют вашим ожиданиям или тому, что вы хотите принять. Помимо конкретных примеров, которые мы рассматриваем в perlfaq, вы также можете посмотреть на модули с "Assert" и "Validate" в их названиях, а также на другие модули, такие как Regexp::Common.
Некоторые модули имеют валидацию для определённых типов входных данных, например, Business::ISBN, Business::CreditCard, Email::Valid и Data::Validate::IP.
Как раскодировать строку?
Это зависит от того, что вы имеете в виду под "раскодированием". URL-раскодирование обрабатывается в perlfaq9. Оболочки раскодирования с символом обратного слэша (\) удаляются с помощью
s/\\(.)/$1/g; Это не будет расширять "\n" или "\t" или любые другие специальные коды.
Как удалить последовательные пары символов?
(предоставлено brian d foy)
Вы можете использовать оператор подстановки для поиска пар символов (или цепочек символов) и замены их на один экземпляр. В этой подстановке мы ищем символ в (.). Скобки памяти сохраняют сопоставленный символ в обратной ссылке \g1, и мы используем это, чтобы потребовать, чтобы то же самое следовали за ним. Мы заменяем эту часть строки символом в $1.
s/(.)\g1/$1/g; Мы также можем использовать оператор транслитерации, tr///. В этом примере список поиска в нашем tr/// пуст, но опция c дополняет это, поэтому он содержит всё. Список замены также пуст, поэтому транслитерация почти является операцией без изменений, так как она не выполнит никаких замен (или, точнее, заменит символ на самого себя). Однако опция s сжимает дублированные и последовательные символы в строке, чтобы символ не появлялся рядом с собой.
my $str = 'Haarlem'; # in the Netherlands
$str =~ tr///cs; # Now Harlem, like in New York Как расширить вызовы функций в строке?
(предоставлено brian d foy)
Это описано в perlref, и хотя это не самое лёгкое чтение, оно работает. В каждом из этих примеров мы вызываем функцию внутри фигурных скобок, используемых для разыменования ссылки. Если у нас более одного возвращаемого значения, мы можем создать и разыменовать анонимный массив. В этом случае мы вызываем функцию в контексте списка.
print "The time values are @{ [localtime] }.\n"; Если мы хотим вызвать функцию в скалярном контексте, нам нужно сделать немного больше работы. Мы можем использовать любой код, который нам нравится внутри фигурных скобок, поэтому нам просто нужно закончить со скалярной ссылкой, хотя как вы это сделаете, зависит от вас, и вы можете использовать код внутри фигурных скобок. Обратите внимание, что использование круглых скобок создаёт контекст списка, поэтому нам нужна scalar, чтобы принудительно установить скалярный контекст для функции:
print "The time is ${\(scalar localtime)}.\n"
print "The time is ${ my $x = localtime; \$x }.\n"; Если ваша функция уже возвращает ссылку, вам не нужно создавать ссылку самостоятельно.
sub timestamp { my $t = localtime; \$t }
print "The time is ${ timestamp() }.\n"; Модуль Interpolation также может выполнить много магии за вас. Вы можете указать имя переменной, в данном случае E, чтобы настроить связанный массив, который выполняет интерполяцию за вас. Он также имеет несколько других методов для этого.
use Interpolation E => 'eval';
print "The time values are $E{localtime()}.\n"; В большинстве случаев, вероятно, проще просто использовать конкатенацию строк, что также принудительно устанавливает скалярный контекст.
print "The time is " . localtime() . ".\n"; Как найти совпадения/вложенность чего-либо?
Чтобы найти что-то между двумя одиночными символами, шаблон вроде /x([^x]*)x/ получит промежуточные части в $1. Для нескольких элементов, тогда потребуется что-то более похожее на /alpha(.*?)omega/. Для вложенных шаблонов и/или сбалансированных выражений см. так называемый конструктор (?PARNO) (доступен начиная с Perl 5.10). Модуль CPAN Regexp::Common может помочь в создании таких регулярных выражений (см. особенно Regexp::Common::balanced и Regexp::Common::delimited).
Более сложные случаи потребуют написания парсера, вероятно, используя модуль парсинга из CPAN, например, Regexp::Grammars, Parse::RecDescent, Parse::Yapp, Text::Balanced или Marpa::R2.
Как перевернуть строку?
Используйте reverse() в скалярном контексте, как описано в "reverse" в perlfunc.
my $reversed = reverse $string; Как расширить табуляции в строке?
Вы можете сделать это самостоятельно:
1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e; Или вы можете просто использовать модуль Text::Tabs (входит в стандартную дистрибуцию Perl).
use Text::Tabs;
my @expanded_lines = expand(@lines_with_tabs); Как переформатировать абзац?
Используйте Text::Wrap (входит в стандартную дистрибуцию Perl):
use Text::Wrap;
print wrap("\t", ' ', @paragraphs); Абзацы, которые вы передаёте в Text::Wrap, не должны содержать вложенных символов перевода строки. Text::Wrap не выравнивает строки по правому краю.
Или используйте модуль CPAN Text::Autoformat. Форматирование файлов можно легко выполнить, создав алиас оболочки, как показано ниже:
alias fmt="perl -i -MText::Autoformat -n0777 \
-e 'print autoformat $_, {all=>1}' $*" См. документацию по Text::Autoformat, чтобы оценить его многочисленные возможности.
Как получить доступ к N символам строки или изменить их?
Вы можете получить доступ к первым символам строки с помощью substr(). Чтобы получить первый символ, например, начните с позиции 0 и возьмите строку длиной 1.
my $string = "Just another Perl Hacker";
my $first_char = substr( $string, 0, 1 ); # 'J' Чтобы изменить часть строки, вы можете использовать необязательный четвёртый аргумент, который является строкой замены.
substr( $string, 13, 4, "Perl 5.8.0" ); Вы также можете использовать substr() как lvalue.
substr( $string, 13, 4 ) = "Perl 5.8.0"; Как изменить N-й экземпляр чего-либо?
Вам нужно самостоятельно отслеживать N. Например, предположим, что вы хотите заменить пятое вхождение "whoever" или "whomever" на "whosoever" или "whomsoever", не чувствительно к регистру. Все они предполагают, что $_ содержит строку, которую нужно изменить.
$count = 0;
s{((whom?)ever)}{
++$count == 5 # is it the 5th?
? "${2}soever" # yes, swap
: $1 # renege and leave it there
}ige; В более общем случае вы можете использовать модификатор /g в цикле while, подсчитывая совпадения.
$WANT = 3;
$count = 0;
$_ = "One fish two fish red fish blue fish";
while (/(\w+)\s+fish\b/gi) {
if (++$count == $WANT) {
print "The third fish is a $1 one.\n";
}
} Это выведет: "The third fish is a red one." Вы также можете использовать счёт повторений и повторяющиеся шаблоны таким образом:
/(?:\w+\s+fish\s+){2}(\w+)\s+fish/i; Как подсчитать количество вхождений подстроки в строке?
Есть несколько способов, с различной эффективностью. Если вам нужен счёт определённого одиночного символа (X) в строке, вы можете использовать функцию tr/// так:
my $string = "ThisXlineXhasXsomeXx'sXinXit";
my $count = ($string =~ tr/X//);
print "There are $count X characters in the string"; Это нормально, если вы ищете только один символ. Однако, если вы пытаетесь подсчитать несколько символов подстроки в большей строке, tr/// не сработает. Вы можете обернуть цикл while() вокруг глобального совпадения шаблона. Например, подсчитаем количество отрицательных целых чисел:
my $string = "-9 55 48 -2 23 -76 4 14 -44";
my $count = 0;
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string"; Другая версия использует глобальное соответствие в контексте списка, затем присваивает результат скаляру, получая счёт совпадений.
my $count = () = $string =~ /-\d+/g; Как сделать заглавными все слова в строке?
(предоставлено brian d foy)
Damian Conway's Text::Autoformat выполняет все вычисления за вас.
use Text::Autoformat;
my $x = "Dr. Strangelove or: How I Learned to Stop ".
"Worrying and Love the Bomb";
print $x, "\n";
for my $style (qw( sentence title highlight )) {
print autoformat($x, { case => $style }), "\n";
} Как вы хотите сделать эти слова заглавными?
FRED AND BARNEY'S LODGE # all uppercase
Fred And Barney's Lodge # title case
Fred and Barney's Lodge # highlight case Это не такая простая задача, как кажется. Сколько слов, по-вашему, там? Подождите... подождите... Если вы ответили 5, вы правы. Слова Perl — это группы \w+, но это не то, что вы хотите сделать заглавными. Как Perl должен знать, не делать заглавными s после апострофа? Вы можете попробовать регулярное выражение:
$string =~ s/ (
(^\w) #at the beginning of the line
| # or
(\s\w) #preceded by whitespace
)
/\U$1/xg;
$string =~ s/([\w']+)/\u\L$1/g; А теперь, что если вы не хотите делать заглавными "and"? Просто используйте Text::Autoformat и переходите к следующей задаче. :)
Как разделить строку, ограниченную [символом], за исключением случаев, когда он находится внутри [символа]?
Несколько модулей могут обрабатывать такой тип разбора — Text::Balanced, Text::CSV, Text::CSV_XS и Text::ParseWords, среди прочих.
Рассмотрим пример попытки разбить строку, разделенную запятыми, на разные поля. Вы не можете использовать split(/,/), потому что не следует разделять строку, если запятая находится внутри кавычек. Например, рассмотрите строку данных:
SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped" Из-за ограничения кавычек эта проблема довольно сложная. К счастью, у нас есть Джеффри Фридл, автор книги Mastering Regular Expressions, который может справиться с этим. Он предлагает (предполагая, что ваша строка содержится в $text):
my @new = ();
push(@new, $+) while $text =~ m{
"([^\"\\]*(?:\\.[^\"\\]*)*)",? # groups the phrase inside the quotes
| ([^,]+),?
| ,
}gx;
push(@new, undef) if substr($text,-1,1) eq ','; Если вы хотите представить кавычки внутри поля, ограниченного кавычками, экранируйте их обратными слешами (например, "like \"this\"").
В качестве альтернативы, модуль Text::ParseWords (часть стандартного дистрибутива Perl) позволяет сказать:
use Text::ParseWords;
@new = quotewords(",", 0, $text); Однако для разбора или генерации CSV настоятельно рекомендуется использовать Text::CSV, а не реализовывать это самостоятельно; вы сэкономите себе трудности, связанные с появлением странных ошибок в будущем, используя код, который уже успешно используется в производстве на протяжении многих лет.
Как удалить пробелы в начале и конце строки?
(представлено brian d foy)
Подстановку можно использовать для этого. Для одной строки вы хотите заменить все ведущие и хвостовые пробелы на пустое значение. Это можно сделать с помощью пары подстановок:
s/^\s+//;
s/\s+$//; Вы также можете записать это как одну подстановку, хотя оказывается, что комбинированное выражение медленнее отдельных. Однако это может быть неважно:
s/^\s+|\s+$//g; В этом регулярном выражении альтернатива соответствует началу или концу строки, так как якоря имеют более низкий приоритет, чем альтернатива. С флагом /g подстановка производит все возможные совпадения, поэтому она получает оба. Помните, что соответствие завершающей новой строки происходит до \s+, а якорь $ может соответствовать абсолютному концу строки, поэтому новая строка также исчезает. Просто добавьте новую строку в вывод, что имеет дополнительное преимущество в сохранении пустых строк (состоящих только из пробелов), которые ^\s+ бы удалили сами:
while( <> ) {
s/^\s+|\s+$//g;
print "$_\n";
} Для многострочной строки вы можете применить регулярное выражение к каждой логической строке в строке, добавив флаг /m (для "многострочных"). С флагом /m, $ совпадает перед вложенной новой строкой, поэтому ее не удаляет. Этот шаблон все равно удаляет новую строку в конце строки:
$string =~ s/^\s+|\s+$//gm; Помните, что строки, состоящие только из пробелов, исчезнут, так как первая часть альтернативы может соответствовать всей строке и заменить ее пустым значением. Если вам необходимо сохранить вложенные пустые строки, вам придется немного поработать. Вместо того, чтобы сопоставлять любые пробелы (так как это включает новую строку), просто сопоставьте другие пробелы:
$string =~ s/^[\t\f ]+|[\t\f ]+$//mg; Как добавить пробелы к строке или нули к числу?
В следующих примерах $pad_len — длина, до которой вы хотите дополнить строку, $text или $num содержит строку для дополнения, а $pad_char содержит символ дополнения. Вы можете использовать константу строки с одним символом вместо переменной $pad_char, если она известна заранее. И точно так же вы можете использовать целое число вместо $pad_len, если длина дополнения известна заранее.
Простейший метод использует функцию sprintf. Она может дополнять слева или справа пробелами, слева нулями и не будет усекать результат. Функция pack может дополнять строки справа пробелами, и она будет усекать результат до максимальной длины $pad_len.
# Left padding a string with blanks (no truncation):
my $padded = sprintf("%${pad_len}s", $text);
my $padded = sprintf("%*s", $pad_len, $text); # same thing
# Right padding a string with blanks (no truncation):
my $padded = sprintf("%-${pad_len}s", $text);
my $padded = sprintf("%-*s", $pad_len, $text); # same thing
# Left padding a number with 0 (no truncation):
my $padded = sprintf("%0${pad_len}d", $num);
my $padded = sprintf("%0*d", $pad_len, $num); # same thing
# Right padding a string with blanks using pack (will truncate):
my $padded = pack("A$pad_len",$text); Если вам необходимо дополнить другим символом, кроме пробела или нуля, вы можете использовать один из следующих методов. Все они генерируют строку дополнения с оператором x и комбинируют ее с $text. Эти методы не усекают $text.
Дополнение слева и справа любым символом, создание новой строки:
my $padded = $pad_char x ( $pad_len - length( $text ) ) . $text;
my $padded = $text . $pad_char x ( $pad_len - length( $text ) ); Дополнение слева и справа любым символом, непосредственное изменение $text:
substr( $text, 0, 0 ) = $pad_char x ( $pad_len - length( $text ) );
$text .= $pad_char x ( $pad_len - length( $text ) ); Как извлечь выбранные столбцы из строки?
(представлено brian d foy)
Если вы знаете столбцы, содержащие данные, вы можете использовать substr для извлечения одного столбца.
my $column = substr( $line, $start_column, $length ); Вы можете использовать split, если столбцы разделены пробелами или каким-либо другим разделителем, при условии, что пробелы или разделитель не могут быть частью данных.
my $line = ' fred barney betty ';
my @columns = split /\s+/, $line;
# ( '', 'fred', 'barney', 'betty' );
my $line = 'fred||barney||betty';
my @columns = split /\|/, $line;
# ( 'fred', '', 'barney', '', 'betty' ); Если вы хотите работать со значениями, разделенными запятыми, не делайте этого, так как этот формат немного сложнее. Используйте один из модулей, которые обрабатывают этот формат, таких как Text::CSV, Text::CSV_XS или Text::CSV_PP.
Если вы хотите разбить целую строку фиксированных столбцов, вы можете использовать unpack с форматом A (ASCII). Используя число после спецификатора формата, вы можете указать ширину столбца. Подробнее см. записи pack и unpack в perlfunc.
my @fields = unpack( $line, "A8 A8 A8 A16 A4" ); Обратите внимание, что пробелы в аргументе формата unpack не обозначают буквальных пробелов. Если у вас данные разделены пробелами, вам может понадобиться split.
Как найти значение Soundex для строки?
(представлено brian d foy)
Вы можете использовать модуль Text::Soundex. Если вы хотите выполнить нечеткое или близкое соответствие, вы также можете попробовать модули String::Approx, Text::Metaphone и Text::DoubleMetaphone.
Как можно расширить переменные в строках текста?
(представлено brian d foy)
Если вы можете этого избежать, не делайте этого, или если вы можете использовать систему шаблонов, такую как Text::Template или Template Toolkit, сделайте это вместо этого. Возможно, вы даже сможете выполнить задачу с помощью sprintf или printf:
my $string = sprintf 'Say hello to %s and %s', $foo, $bar; Однако для отдельного простого случая, когда я не хочу использовать полную систему шаблонов, я использую строку с двумя скалярными переменными Perl. В этом примере я хочу расширить $foo и $bar до значений их переменных:
my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar'; Один из способов сделать это — использовать оператор подстановки и флаг с двойным /e. Первый /e вычисляет $1 в части замены и преобразует его в $foo. Второй /e начинается с $foo и заменяет его своим значением. $foo, затем, преобразуется в 'Fred', и это в конечном итоге остается в строке:
$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney' /e также будет молча игнорировать нарушения строгости, заменяя имена неопределенных переменных пустой строкой. Поскольку я использую флаг /e (даже дважды!), у меня все те же проблемы с безопасностью, которые у меня есть с eval в его строковой форме. Если в $foo есть что-то странное, например, @{[ system "rm -rf /" ]}, то я могу попасть в беду.
Чтобы обойти проблему безопасности, я также могу извлечь значения из хеша вместо вычисления имен переменных. Используя один /e, я могу проверить хеш на наличие значения, и если его нет, я могу заменить отсутствующее значение маркером, в данном случае ???, чтобы указать, что я что-то пропустил:
my $string = 'This has $foo and $bar';
my %Replacements = (
foo => 'Fred',
);
# $string =~ s/\$(\w+)/$Replacements{$1}/g;
$string =~ s/\$(\w+)/
exists $Replacements{$1} ? $Replacements{$1} : '???'
/eg;
print $string; В чем проблема с постоянным использованием кавычек вокруг "$переменные"?
Проблема в том, что эти двойные кавычки принуждают к строковому представлению — принудительно преобразовывают числа и ссылки в строки — даже когда вы этого не хотите. Подумайте об этом так: расширение двойных кавычек используется для создания новых строк. Если у вас уже есть строка, зачем вам нужна еще одна?
Если вы привыкнете писать странные вещи, такие как эти:
print "$var"; # BAD
my $new = "$old"; # BAD
somefunc("$var"); # BAD Вы столкнетесь с проблемами. В 99,8% случаев это должно быть проще и яснее:
print $var;
my $new = $old;
somefunc($var); В противном случае, кроме замедления работы, вы нарушите код, когда элемент в скаляре на самом деле не является ни строкой, ни числом, а ссылкой:
func(\@array);
sub func {
my $aref = shift;
my $oref = "$aref"; # WRONG
} Вы также можете столкнуться с тонкими проблемами в некоторых операциях Perl, которые действительно учитывают разницу между строкой и числом, например, магическим оператором автоинкремента ++ или функцией syscall().
Строковое представление также разрушает массивы.
my @lines = `command`;
print "@lines"; # WRONG - extra blanks
print @lines; # right Почему мои <<HERE документы не работают?
Здесь документы находятся в perlop. Проверьте эти три вещи:
- После << не должно быть пробелов.
- Вероятно, после открывающего маркера должен стоять точка с запятой.
- Вы не можете (легко) иметь пробелы перед маркером.
- После закрывающего маркера должна быть, по крайней мере, одна новая строка.
Если вы хотите отступать текст в документе здесь, вы можете сделать это:
# all in one
(my $VAR = <<HERE_TARGET) =~ s/^\s+//gm;
your text
goes here
HERE_TARGET Однако HERE_TARGET все равно должен быть выровнен по левому краю. Если вы хотите, чтобы он тоже был отступом, вам нужно будет взять отступ в кавычки.
(my $quote = <<' FINIS') =~ s/^\s+//gm;
...we will have peace, when you and all your works have
perished--and the works of your dark master to whom you
would deliver us. You are a liar, Saruman, and a corrupter
of men's hearts. --Theoden in /usr/src/perl/taint.c
FINIS
$quote =~ s/\s+--/\n--/; Ниже приведена хорошая универсальная функция для форматирования отступаемых документов здесь. Она должна вызываться с документом здесь в качестве аргумента. Она проверяет, начинаются ли все строки с одного и того же подстроки, и если да, то удаляет эту подстроку. В противном случае она берет количество начальных пробелов, найденных в первой строке, и удаляет это количество из каждой последующей строки.
sub fix {
local $_ = shift;
my ($white, $leader); # common whitespace and common leading string
if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\g1\g2?.*\n)+$/) {
($white, $leader) = ($2, quotemeta($1));
} else {
($white, $leader) = (/^(\s+)/, '');
}
s/^\s*?$leader(?:$white)?//gm;
return $_;
} Это работает с ведущими специальными строками, определяемыми динамически:
my $remember_the_main = fix<<' MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@ SAVEI32(runlevel);
@@@ runlevel++;
@@@ while ( op = (*op->op_ppaddr)() );
@@@ TAINT_NOT;
@@@ return 0;
@@@ }
MAIN_INTERPRETER_LOOP Или с фиксированным количеством начальных пробелов, с правильным сохранением остающегося отступа:
my $poem = fix<<EVER_ON_AND_ON;
Now far ahead the Road has gone,
And I must follow, if I can,
Pursuing it with eager feet,
Until it joins some larger way
Where many paths and errands meet.
And whither then? I cannot say.
--Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON END_OF_DOCUMENT_MARKER Начиная с версии Perl 5.26, в язык добавлено значительно более простое и чистое средство для записи отступаемых здесь документов: модификатор тильды (~). Подробности см. в разделе "Отступаемые здесь документы" в perlop.
Данные: Массивы
В чём разница между списком и массивом?
(внёс brian d foy)
Список — это фиксированное множество скаляров. Массив — это переменная, которая хранит переменное множество скаляров. Массив может предоставить своё множество для операций со списками, поэтому операции со списками также работают с массивами:
# slices
( 'dog', 'cat', 'bird' )[2,3];
@animals[2,3];
# iteration
foreach ( qw( dog cat bird ) ) { ... }
foreach ( @animals ) { ... }
my @three = grep { length == 3 } qw( dog cat bird );
my @three = grep { length == 3 } @animals;
# supply an argument list
wash_animals( qw( dog cat bird ) );
wash_animals( @animals ); Операции с массивами, которые изменяют скаляры, переупорядочивают их или добавляют/удаляют некоторые скаляры, работают только с массивами. Они не могут работать со списком, который является фиксированным. К операциям с массивами относятся shift, unshift, push, pop и splice.
Массив также может изменять свою длину:
$#animals = 1; # truncate to two elements
$#animals = 10000; # pre-extend to 10,001 elements Вы можете изменить элемент массива, но вы не можете изменить элемент списка:
$animals[0] = 'Rottweiler';
qw( dog cat bird )[0] = 'Rottweiler'; # syntax error!
foreach ( @animals ) {
s/^d/fr/; # works fine
}
foreach ( qw( dog cat bird ) ) {
s/^d/fr/; # Error! Modification of read only value!
} Однако, если элемент списка сам является переменной, кажется, что вы можете изменить элемент списка. Однако элемент списка — это переменная, а не данные. Вы не изменяете элемент списка, а изменяете то, на что он ссылается. Сам элемент списка не меняется: это всё ещё та же переменная.
Вы также должны быть осторожны с контекстом. Вы можете присвоить массив скаляру, чтобы получить количество элементов в массиве. Это работает только с массивами:
my $count = @animals; # only works with arrays Если вы попытаетесь сделать то же самое со списком, вы получите совершенно другой результат. Хотя справа вы видите список, Perl на самом деле видит набор скаляров, разделённых запятыми:
my $scalar = ( 'dog', 'cat', 'bird' ); # $scalar gets bird Поскольку вы присваиваете скаляру, правая часть находится в скалярном контексте. Оператор запятой (да, это оператор!) в скалярном контексте оценивает свою левую часть, отбрасывает результат и оценивает свою правую часть, возвращая результат. По сути, этот список-подобие присваивает значение своей правой части. Многие люди ошибаются, выбирая список-подобие, последний элемент которого также является ожидаемым счётчиком:
my $scalar = ( 1, 2, 3 ); # $scalar gets 3, accidentally В чём разница между $array[1] и @array[1]?
(внёс brian d foy)
Разница в знаке, этом специальном символе перед именем массива. Знак $ означает "ровно один элемент", в то время как знак @ означает "ноль или более элементов". Знак $ даёт вам один скаляр, а знак @ даёт вам список.
Путаница возникает из-за того, что люди неправильно предполагают, что знак обозначает тип переменной.
$array[1] — это доступ к элементу массива по индексу. Он вернёт элемент с индексом 1 (или undef, если такого элемента нет). Если вы хотите получить ровно один элемент из массива, используйте именно эту форму.
@array[1] — это срез массива, хотя в нём и указан только один индекс. Вы можете извлечь несколько элементов одновременно, указав дополнительные индексы в виде списка, например, @array[1,4,3,0].
Использование среза в левой части присваивания предоставляет правой части контекст списка. Это может привести к неожиданным результатам. Например, если вы хотите прочитать одну строку из файлового дескриптора, присваивание скалярному значению хорошо:
$array[1] = <STDIN>; Однако в контексте списка оператор ввода строки возвращает все строки в виде списка. Первая строка попадает в @array[1], а остальные строки таинственно исчезают:
@array[1] = <STDIN>; # most likely not what you want Предикат use warnings или флаг -w предупредит вас при использовании среза массива с одним индексом.
Как можно удалить дубликаты элементов из списка или массива?
(внёс brian d foy)
Используйте хеш. Когда вы видите слова "уникальный" или "дублированный", думайте о "ключах хеша".
Если порядок элементов не важен, вы можете просто создать хеш, а затем извлечь ключи. Важно только, как вы создаёте этот хеш: просто используйте keys, чтобы получить уникальные элементы.
my %hash = map { $_, 1 } @array;
# or a hash slice: @hash{ @array } = ();
# or a foreach: $hash{$_} = 1 foreach ( @array );
my @unique = keys %hash; Если вы хотите использовать модуль, попробуйте функцию uniq из List::MoreUtils. В контексте списка она возвращает уникальные элементы, сохраняя их порядок в списке. В скалярном контексте она возвращает количество уникальных элементов.
use List::MoreUtils qw(uniq);
my @unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 1,2,3,4,5,6,7
my $unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 7 Вы также можете пройтись по каждому элементу и пропустить те, которые вы уже видели. Используйте хеш для отслеживания. В первый раз в цикле встречается элемент, этого элемента нет в %Seen. Оператор next создаёт ключ и сразу же использует его значение, которое является undef, поэтому цикл переходит к push и увеличивает значение для этого ключа. В следующий раз, когда цикл видит тот же элемент, его ключ существует в хеше и значение этого ключа истинно (поскольку оно не равно 0 или undef), поэтому цикл пропускает эту итерацию и переходит к следующему элементу.
my @unique = ();
my %seen = ();
foreach my $elem ( @array ) {
next if $seen{ $elem }++;
push @unique, $elem;
} Вы можете написать это более кратко, используя grep, который делает то же самое.
my %seen = ();
my @unique = grep { ! $seen{ $_ }++ } @array; Как можно проверить, содержит ли список или массив определённый элемент?
(части этого ответа внесли Anno Siegel и brian d foy)
Слово "в" указывает на то, что вы, вероятно, должны были использовать хеш, а не список или массив, для хранения данных. Хеши разработаны для быстрого и эффективного ответа на этот вопрос. Массивы — нет.
Тем не менее, есть несколько способов подойти к этому. В Perl 5.10 и более поздних версиях вы можете использовать умный оператор сравнения, чтобы проверить, содержится ли элемент в массиве или хеше:
use 5.010;
if( $item ~~ @array ) {
say "The array contains $item"
}
if( $item ~~ %hash ) {
say "The hash contains $item"
} В более ранних версиях Perl вам придётся потрудиться больше. Если вы собираетесь много раз задавать этот вопрос для произвольных строковых значений, самым быстрым способом, вероятно, будет инвертировать исходный массив и поддерживать хеш, ключами которого являются значения первого массива:
my @blues = qw/azure cerulean teal turquoise lapis-lazuli/;
my %is_blue = ();
for (@blues) { $is_blue{$_} = 1 } Теперь вы можете проверить, содержит ли $is_blue{$some_color}. Возможно, стоило бы сохранить все синие элементы в хеш с самого начала.
Если значения — это небольшие целые числа, вы можете использовать простой индексированный массив. Такой массив займёт меньше места:
my @primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
my @is_tiny_prime = ();
for (@primes) { $is_tiny_prime[$_] = 1 }
# or simply @istiny_prime[@primes] = (1) x @primes; Теперь проверьте $is_tiny_prime[$some_number].
Если значения — целые числа, а не строки, вы можете сэкономить много места, используя битовые строки:
my @articles = ( 1..10, 150..2000, 2017 );
undef $read;
for (@articles) { vec($read,$_,1) = 1 } Теперь проверьте, является ли vec($read,$n,1) истинным для какого-либо $n.
Эти методы гарантируют быстрые отдельные проверки, но требуют перестройки исходного списка или массива. Они оправдывают себя только при необходимости проверки множества значений против одного и того же массива.
Если вы проверяете только один раз, стандартный модуль List::Util экспортирует функцию first для этой цели. Она работает, останавливаясь, как только находит элемент. Она написана на C для скорости, и её Perl-эквивалент выглядит так:
sub first (&@) {
my $code = shift;
foreach (@_) {
return $_ if &{$code}();
}
undef;
} Если скорость не так важна, обычный подход использует grep в скалярном контексте (который возвращает количество элементов, удовлетворивших условию), чтобы пройтись по всему списку. Это даёт преимущество — узнать, сколько совпадений было найдено.
my $is_there = grep $_ eq $whatever, @array; Если вы хотите фактически извлечь совпадающие элементы, просто используйте grep в контексте списка.
my @matches = grep $_ eq $whatever, @array; Как вычислить разность двух массивов? Как вычислить пересечение двух массивов?
Используйте хеш. Вот код, выполняющий оба действия и ещё больше. Предполагается, что каждый элемент уникален в данном массиве:
my (@union, @intersection, @difference);
my %count = ();
foreach my $element (@array1, @array2) { $count{$element}++ }
foreach my $element (keys %count) {
push @union, $element;
push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
} Обратите внимание, что это симметричная разность, то есть все элементы либо в A, либо в B, но не в обоих. Подумайте об этом как об операции XOR.
Как проверить, равны ли два массива или хеша?
В Perl 5.10 и более поздних версиях оператор умного сравнения даст вам ответ с наименьшими усилиями:
use 5.010;
if( @array1 ~~ @array2 ) {
say "The arrays are the same";
}
if( %hash1 ~~ %hash2 ) # doesn't check values! {
say "The hash keys are the same";
} Следующий код работает для массивов с одним уровнем. Он использует строковое сравнение и не различает определённые и неопределённые пустые строки. Измените, если у вас другие потребности.
$are_equal = compare_arrays(\@frogs, \@toads);
sub compare_arrays {
my ($first, $second) = @_;
no warnings; # silence spurious -w undef complaints
return 0 unless @$first == @$second;
for (my $i = 0; $i < @$first; $i++) {
return 0 if $first->[$i] ne $second->[$i];
}
return 1;
} Для многоуровневых структур вы можете использовать подход, более похожий на этот. Он использует модуль CPAN FreezeThaw:
use FreezeThaw qw(cmpStr);
my @a = my @b = ( "this", "that", [ "more", "stuff" ] );
printf "a and b contain %s arrays\n",
cmpStr(\@a, \@b) == 0
? "the same"
: "different"; Этот подход также работает для сравнения хешей. Здесь мы продемонстрируем два разных ответа:
use FreezeThaw qw(cmpStr cmpStrHard);
my %a = my %b = ( "this" => "that", "extra" => [ "more", "stuff" ] );
$a{EXTRA} = \%b;
$b{EXTRA} = \%a;
printf "a and b contain %s hashes\n",
cmpStr(\%a, \%b) == 0 ? "the same" : "different";
printf "a and b contain %s hashes\n",
cmpStrHard(\%a, \%b) == 0 ? "the same" : "different"; Первый сообщает, что оба хеша содержат одинаковые данные, а второй — что нет. Что вы предпочитаете, предоставляется на усмотрение читателя.
Как найти первый элемент массива, для которого истинно условие?
Для поиска первого элемента массива, удовлетворяющего условию, можно использовать функцию first() в модуле List::Util, который входит в Perl 5.8. Этот пример находит первый элемент, содержащий "Perl".
use List::Util qw(first);
my $element = first { /Perl/ } @array; Если вы не можете использовать List::Util, вы можете создать свой собственный цикл для выполнения того же действия. Найдя элемент, вы прекращаете цикл с помощью last.
my $found;
foreach ( @array ) {
if( /Perl/ ) { $found = $_; last }
} Если вам нужен индекс массива, используйте функцию firstidx() из модуля List::MoreUtils:
use List::MoreUtils qw(firstidx);
my $index = firstidx { /Perl/ } @array; Или напишите её самостоятельно, перебирая индексы и проверяя элемент массива в каждом индексе, пока не найдёте элемент, удовлетворяющий условию:
my( $found, $index ) = ( undef, -1 );
for( $i = 0; $i < @array; $i++ ) {
if( $array[$i] =~ /Perl/ ) {
$found = $array[$i];
$index = $i;
last;
}
} Как обрабатывать связанные списки?
(внёс brian d foy)
Массивы Perl не имеют фиксированного размера, поэтому вам не нужны связанные списки, если вы просто хотите добавлять или удалять элементы. Вы можете использовать операции с массивом, такие как push, pop, shift, unshift или splice, чтобы это сделать.
Однако иногда связанные списки могут быть полезны в ситуациях, когда вы хотите «разбить» массив на множество небольших массивов вместо одного большого массива. Вы можете хранить массивы, превышающие максимальный индекс массива Perl, отдельно блокировать меньшие массивы в потоковых программах, перераспределять меньше памяти или быстро вставлять элементы в середину цепочки.
Подробно об этом рассказывает Стив Лемарк в своём выступлении на YAPC::NA 2009 «Связанные списки Perl» ( http://www.slideshare.net/lembark/perly-linked-lists ), хотя вы можете просто использовать его модуль LinkedList::Single.
Как обращаться с циклическими списками?
(внёс brian d foy)
Если вы хотите бесконечно циклировать массив, вы можете увеличивать индекс по модулю количества элементов в массиве:
my @array = qw( a b c );
my $i = 0;
while( 1 ) {
print $array[ $i++ % @array ], "\n";
last if $i > 20;
} Также можно использовать Tie::Cycle для использования скаляра, который всегда содержит следующий элемент кругового массива:
use Tie::Cycle;
tie my $cycle, 'Tie::Cycle', [ qw( FFFFFF 000000 FFFF00 ) ];
print $cycle; # FFFFFF
print $cycle; # 000000
print $cycle; # FFFF00 Класс Array::Iterator::Circular создаёт объект-итератор для круговых массивов:
use Array::Iterator::Circular;
my $color_iterator = Array::Iterator::Circular->new(
qw(red green blue orange)
);
foreach ( 1 .. 20 ) {
print $color_iterator->next, "\n";
} Как случайным образом перемешать массив?
Если у вас установлена Perl 5.8.0 или более поздняя версия, или у вас установлен Scalar-List-Utils 1.03 или более поздняя версия, вы можете сделать следующее:
use List::Util 'shuffle';
@shuffled = shuffle(@list); В противном случае можно использовать алгоритм Фишера–Йейтса.
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
return unless @$deck; # must not be empty!
my $i = @$deck;
while (--$i) {
my $j = int rand ($i+1);
@$deck[$i,$j] = @$deck[$j,$i];
}
}
# shuffle my mpeg collection
#
my @mpeg = <audio/*/*.mp3>;
fisher_yates_shuffle( \@mpeg ); # randomize @mpeg in place
print @mpeg; Обратите внимание, что вышеприведенная реализация перемешивает массив на месте, в отличие от List::Util::shuffle(), которая принимает список и возвращает новый перемешанный список.
Вы, вероятно, видели алгоритмы перемешивания, которые работают с помощью splice, случайно выбирая другой элемент для обмена с текущим элементом
srand;
@new = ();
@old = 1 .. 10; # just a demo
while (@old) {
push(@new, splice(@old, rand @old, 1));
} Это плохо, потому что splice уже имеет сложность O(N), а поскольку вы делаете это N раз, вы только что придумали квадратичный алгоритм; то есть O(N**2). Это не масштабируется, хотя Perl настолько эффективен, что вы, вероятно, не заметите этого, пока у вас не будет довольно больших массивов.
Как обработать/изменить каждый элемент массива?
Используйте for/foreach:
for (@lines) {
s/foo/bar/; # change that word
tr/XZ/ZX/; # swap those letters
} Вот ещё пример; вычислим объёмы сфер:
my @volumes = @radii;
for (@volumes) { # @volumes has changed parts
$_ **= 3;
$_ *= (4/3) * 3.14159; # this will be constant folded
} что также можно сделать с помощью map(), который предназначен для преобразования одного списка в другой:
my @volumes = map {$_ ** 3 * (4/3) * 3.14159} @radii; Если вы хотите сделать то же самое для изменения значений хеша, вы можете использовать функцию values. Начиная с Perl 5.6, значения не копируются, поэтому если вы изменяете $orbit (в данном случае), вы изменяете значение.
for my $orbit ( values %orbits ) {
($orbit **= 3) *= (4/3) * 3.14159;
} До версии perl 5.6 values возвращала копии значений, поэтому старый код perl часто содержит конструкции вроде @orbits{keys %orbits} вместо values %orbits, где хеш должен быть изменён.
Как выбрать случайный элемент из массива?
Используйте функцию rand() (см. "rand" в perlfunc):
my $index = rand @array;
my $element = $array[$index]; Или просто:
my $element = $array[ rand @array ]; Как переставить N элементов списка?
Используйте модуль List::Permutor из CPAN. Если список фактически является массивом, попробуйте модуль Algorithm::Permute (также из CPAN). Он написан на языке XS и очень эффективен:
use Algorithm::Permute;
my @array = 'a'..'d';
my $p_iterator = Algorithm::Permute->new ( \@array );
while (my @perm = $p_iterator->next) {
print "next permutation: (@perm)\n";
} Для ещё более быстрой работы можно сделать так:
use Algorithm::Permute;
my @array = 'a'..'d';
Algorithm::Permute::permute {
print "next permutation: (@array)\n";
} @array; Вот небольшая программа, которая генерирует все перестановки всех слов на каждой строке входных данных. Алгоритм, воплощённый в функции permute(), обсуждается в 4 томе (ещё не опубликован) книги Кнута «Искусство программирования» и будет работать с любым списком:
#!/usr/bin/perl -n
# Fischer-Krause ordered permutation generator
sub permute (&@) {
my $code = shift;
my @idx = 0..$#_;
while ( $code->(@_[@idx]) ) {
my $p = $#idx;
--$p while $idx[$p-1] > $idx[$p];
my $q = $p or return;
push @idx, reverse splice @idx, $p;
++$q while $idx[$p-1] > $idx[$q];
@idx[$p-1,$q]=@idx[$q,$p-1];
}
}
permute { print "@_\n" } split; Модуль Algorithm::Loops также предоставляет функции NextPermute и NextPermuteNum, которые эффективно находят все уникальные перестановки массива, даже если он содержит дубликаты, изменяя его на месте: если его элементы отсортированы в обратном порядке, то массив переворачивается, становясь отсортированным, и возвращается false; в противном случае возвращается следующая перестановка.
NextPermute использует строковый порядок, а NextPermuteNum — числовой порядок, поэтому вы можете перечислить все перестановки 0..9 следующим образом:
use Algorithm::Loops qw(NextPermuteNum);
my @list= 0..9;
do { print "@list\n" } while NextPermuteNum @list; Как отсортировать массив по (любому) критерию?
Укажите функцию сравнения для sort() (описана в "sort" в perlfunc):
@list = sort { $a <=> $b } @list; По умолчанию функция сравнения — cmp, сравнение строк, которое отсортирует (1, 2, 10) в (1, 10, 2). <=>, использованная выше, — оператор числового сравнения.
Если вам нужна сложная функция для извлечения части, по которой вы хотите сортировать, не встраивайте её в функцию сортировки. Сначала извлеките её, потому что блок сортировки может вызываться много раз для одного и того же элемента. Вот пример того, как извлечь первое слово после первой цифры в каждом элементе и затем отсортировать эти слова без учёта регистра.
my @idx;
for (@data) {
my $item;
($item) = /\d+\s*(\S+)/;
push @idx, uc($item);
}
my @sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ]; что также можно записать так, используя трюк, который стал известен как преобразование Шварца:
my @sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data; Если вам нужно сортировать по нескольким полям, полезна следующая парадигма.
my @sorted = sort {
field1($a) <=> field1($b) ||
field2($a) cmp field2($b) ||
field3($a) cmp field3($b)
} @data; Это удобно комбинировать с предварительным вычислением ключей, как указано выше.
Дополнительную информацию об этом подходе см. в статье «sort» в коллекции «Гораздо больше, чем вы когда-либо хотели знать» в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz.
См. также вопрос о сортировке хешей в perlfaq4 позднее в этой статье.
Как манипулировать массивами битов?
Используйте pack() и unpack(), или же vec() и побитовые операции.
Например, не нужно хранить отдельные биты в массиве (это означало бы, что вы тратите много места). Для преобразования массива битов в строку используйте vec() для установки нужных битов. Это устанавливает $vec, чтобы бит N был установлен только в том случае, если $ints[N] был установлен:
my @ints = (...); # array of bits, e.g. ( 1, 0, 0, 1, 1, 0 ... )
my $vec = '';
foreach( 0 .. $#ints ) {
vec($vec,$_,1) = 1 if $ints[$_];
} Строка $vec занимает только столько битов, сколько ей нужно. Например, если у вас было 16 элементов в @ints, $vec нужно только два байта для их хранения (не считая накладных расходов скалярной переменной).
Вот как, имея вектор в $vec, вы можете поместить эти биты в массив @ints:
sub bitvec_to_list {
my $vec = shift;
my @ints;
# Find null-byte density then select best algorithm
if ($vec =~ tr/\0// / length $vec > 0.95) {
use integer;
my $i;
# This method is faster with mostly null-bytes
while($vec =~ /[^\0]/g ) {
$i = -9 + 8 * pos $vec;
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
}
}
else {
# This method is a fast general algorithm
use integer;
my $bits = unpack "b*", $vec;
push @ints, 0 if $bits =~ s/^(\d)// && $1;
push @ints, pos $bits while($bits =~ /1/g);
}
return \@ints;
} Этот метод становится быстрее, чем больше бит в векторе.
(Вежливость Тима Банса и Винфрида Кёнига.)
Вы можете сделать цикл while намного короче с помощью этого предложения от Бенджамина Голдберга:
while($vec =~ /[^\0]+/g ) {
push @ints, grep vec($vec, $_, 1), $-[0] * 8 .. $+[0] * 8;
} Или используйте модуль CPAN Bit::Vector:
my $vector = Bit::Vector->new($num_of_bits);
$vector->Index_List_Store(@ints);
my @ints = $vector->Index_List_Read(); Bit::Vector предоставляет эффективные методы для векторов битов, множеств малых целых чисел и операций с большими целыми числами.
Вот более подробная иллюстрация с использованием vec():
# vec demo
my $vector = "\xff\x0f\xef\xfe";
print "Ilya's string \\xff\\x0f\\xef\\xfe represents the number ",
unpack("N", $vector), "\n";
my $is_set = vec($vector, 23, 1);
print "Its 23rd bit is ", $is_set ? "set" : "clear", ".\n";
pvec($vector);
set_vec(1,1,1);
set_vec(3,1,1);
set_vec(23,1,1);
set_vec(3,1,3);
set_vec(3,2,3);
set_vec(3,4,3);
set_vec(3,4,7);
set_vec(3,8,3);
set_vec(3,8,7);
set_vec(0,32,17);
set_vec(1,32,17);
sub set_vec {
my ($offset, $width, $value) = @_;
my $vector = '';
vec($vector, $offset, $width) = $value;
print "offset=$offset width=$width value=$value\n";
pvec($vector);
}
sub pvec {
my $vector = shift;
my $bits = unpack("b*", $vector);
my $i = 0;
my $BASE = 8;
print "vector length in bytes: ", length($vector), "\n";
@bytes = unpack("A8" x length($vector), $bits);
print "bits are: @bytes\n\n";
} Почему defined() возвращает true для пустых массивов и хешей?
Короче говоря, вам, вероятно, следует использовать defined только для скаляров или функций, а не для агрегатов (массивов и хешей). Подробности см. в "defined" в perlfunc в версии Perl 5.004 или более поздней.
Данные: Хеши (Ассоциативные массивы)
Как обработать весь хеш?
(внесён brian d foy)
Есть несколько способов обработки всего хеша. Вы можете получить список ключей, а затем пройти по каждому ключу или взять одну пару ключ-значение за раз.
Для перебора всех ключей используйте функцию keys. Это извлекает все ключи хеша и возвращает их как список. Затем вы можете получить значение по конкретному ключу, который обрабатываете:
foreach my $key ( keys %hash ) {
my $value = $hash{$key}
...
} После получения списка ключей вы можете обработать этот список перед обработкой элементов хеша. Например, вы можете отсортировать ключи, чтобы обработать их в лексикографическом порядке:
foreach my $key ( sort keys %hash ) {
my $value = $hash{$key}
...
} Или, возможно, вы хотите обработать только некоторые элементы. Если вам нужны только ключи, начинающиеся с text:, вы можете выбрать только их с помощью grep:
foreach my $key ( grep /^text:/, keys %hash ) {
my $value = $hash{$key}
...
} Если хеш очень большой, вы, возможно, не захотите создавать длинный список ключей. Чтобы сохранить память, можно взять одну пару ключ-значение за раз, используя each(), которая возвращает пару, которую вы ещё не видели:
while( my( $key, $value ) = each( %hash ) ) {
...
} Оператор each возвращает пары в, по-видимому, случайном порядке, поэтому если порядок для вас важен, вам придётся использовать метод keys.
Оператор each() может быть немного сложным. Вы не можете добавлять или удалять ключи хеша во время его использования, не рискуя пропустить или повторно обработать некоторые пары после того, как Perl внутренне перестроит все элементы. Кроме того, у хеша есть только один итератор, поэтому если вы смешаете keys, values или each для одного и того же хеша, вы рискуете сбросить итератор и нарушить обработку. Подробности см. в записи each в perlfunc.
Как объединить два хеша?
(внесён brian d foy)
Перед объединением двух хешей вам нужно решить, что делать, если оба хеша содержат одинаковые ключи, и хотите ли вы оставить исходные хеши неизменными.
Если вы хотите сохранить исходные хеши, скопируйте один хеш (%hash1) в новый хеш (%new_hash), а затем добавьте ключи из другого хеша (%hash2) в новый хеш. Проверка того, что ключ уже существует в %new_hash, даёт вам возможность решить, что делать с дубликатами:
my %new_hash = %hash1; # make a copy; leave %hash1 alone
foreach my $key2 ( keys %hash2 ) {
if( exists $new_hash{$key2} ) {
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else {
$new_hash{$key2} = $hash2{$key2};
}
} Если вы не хотите создавать новый хеш, вы всё равно можете использовать этот метод цикла; просто замените %new_hash на %hash1.
foreach my $key2 ( keys %hash2 ) {
if( exists $hash1{$key2} ) {
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else {
$hash1{$key2} = $hash2{$key2};
}
} Если вас не волнует, что один хеш перезаписывает ключи и значения из другого, вы можете просто использовать срез хеша для добавления одного хеша к другому. В этом случае значения из %hash2 замещают значения из %hash1, когда у них есть общие ключи:
@hash1{ keys %hash2 } = values %hash2; Что происходит, если я добавляю или удаляю ключи из хеша во время его перебора?
(внесён brian d foy)
Простой ответ: «Не делайте этого!»
Если вы перебираете хеш с помощью each(), вы можете удалить ключ, который был возвращён последним, не беспокоясь о нём. Если вы удаляете или добавляете другие ключи, итератор может пропустить или дублировать их, так как Perl может переупорядочить таблицу хеширования. См. запись each() в perlfunc.
Как найти элемент хеша по значению?
Создайте обратный хеш:
my %by_value = reverse %by_key;
my $key = $by_value{$value}; Это не очень эффективно. Более эффективным с точки зрения памяти было бы:
while (my ($key, $value) = each %by_key) {
$by_value{$value} = $key;
} Если ваш хеш может иметь повторяющиеся значения, описанные выше методы найдут только один из связанных ключей. Это может или не может вас беспокоить. Если это вас беспокоит, вы всегда можете преобразовать хеш в хеш массивов вместо этого:
while (my ($key, $value) = each %by_key) {
push @{$key_list_by_value{$value}}, $key;
} Как узнать количество элементов в хеше?
(внесён brian d foy)
Это очень похоже на «Как обработать весь хеш?», тоже в perlfaq4, но проще в распространённых случаях.
Можно использовать встроенную функцию keys() в скалярном контексте, чтобы узнать, сколько элементов есть в хеше:
my $key_count = keys %hash; # must be scalar context! Если вы хотите узнать, сколько записей имеют определенное значение, это немного отличается. Вам нужно проверить каждое значение. Удобен grep:
my $defined_value_count = grep { defined } values %hash; Вы можете использовать ту же структуру для подсчета записей любым удобным способом. Если вам нужен подсчёт ключей, содержащих гласные, просто проверьте это вместо этого:
my $vowel_count = grep { /[aeiou]/ } keys %hash; grep в скалярном контексте возвращает количество. Если вам нужен список соответствующих элементов, просто используйте его в списочном контексте:
my @defined_values = grep { defined } values %hash; Функция keys() также сбрасывает итератор, что означает, что вы можете увидеть странные результаты, если вы используете её между вызовами других операторов хеша, таких как each().
Как отсортировать хеш (необязательно по значению, а не по ключу)?
(внесено brian d foy)
Чтобы отсортировать хеш, начните с ключей. В этом примере мы передаём список ключей в функцию sort, которая затем сравнивает их по ASCII (что может быть повлияно настройками вашей локали). Список вывода содержит ключи в ASCII-порядке. После получения ключей мы можем пройти по ним, чтобы создать отчёт, в котором ключи будут упорядочены по ASCII.
my @keys = sort { $a cmp $b } keys %hash;
foreach my $key ( @keys ) {
printf "%-20s %6d\n", $key, $hash{$key};
} Мы можем сделать это более сложно в блоке sort(). Вместо сравнения ключей мы можем вычислить значение с ними и использовать это значение в качестве сравнения.
Например, чтобы сделать наш отчёт с игнорированием регистра, мы используем lc для приведения ключей к нижнему регистру перед их сравнением:
my @keys = sort { lc $a cmp lc $b } keys %hash; Примечание: если вычисление дорогостоящее или хеш содержит много элементов, вы можете использовать трансформацию Шварца для кэширования результатов вычислений.
Если мы хотим отсортировать по значению хеша, мы используем ключ хеша для его поиска. Мы всё равно получаем список ключей, но на этот раз они упорядочены по их значениям.
my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash; Оттуда мы можем сделать это сложнее. Если значения хешей одинаковые, мы можем обеспечить вторичную сортировку по ключу хеша.
my @keys = sort {
$hash{$a} <=> $hash{$b}
or
"\L$a" cmp "\L$b"
} keys %hash; Как всегда поддерживать отсортированный хеш?
Вы можете изучить использование модуля DB_File и tie(), используя привязки хеша $DB_BTREE, как описано в "Встроенные базы данных" в DB_File. Модуль Tie::IxHash из CPAN также может быть полезным. Хотя это сохраняет отсортированный хеш, вы можете не оценить снижение производительности из-за интерфейса привязки. Вы уверены, что вам это нужно? :)
В чём разница между "delete" и "undef" в хешах?
Хеши содержат пары скаляров: первый — ключ, второй — значение. Ключ будет преобразован в строку, хотя значение может быть любого типа скаляра: строка, число или ссылка. Если ключ $key присутствует в %hash, exists($hash{$key}) вернёт true. Значение для данного ключа может быть undef, в этом случае $hash{$key} будет undef, а exists $hash{$key} вернёт true. Это соответствует тому, что ($key, undef) находится в хеше.
Изображения помогают... Вот таблица %hash:
keys values
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+ И эти условия выполняются
$hash{'a'} is true
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is true
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true Если вы теперь скажете
undef $hash{'a'} ваша таблица теперь выглядит так:
keys values
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+ и эти условия теперь выполняются; изменения в заглавных буквах:
$hash{'a'} is FALSE
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is FALSE
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true Обратите внимание на две последние строки: у вас есть значение undef, но определённый ключ!
Теперь рассмотрите это:
delete $hash{'a'} ваша таблица теперь выглядит так:
keys values
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+ и эти условия теперь выполняются; изменения в заглавных буквах:
$hash{'a'} is false
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is false
exists $hash{'a'} is FALSE (Perl 5 only)
grep ($_ eq 'a', keys %hash) is FALSE Видите, вся запись исчезла!
Почему мои привязанные хеши не делают различия между defined/exists?
Это зависит от реализации EXISTS() привязанным хешем. Например, нет понятия undef для хешей, привязанных к файлам DBM*. Это также означает, что exists() и defined() выполняют одинаковую работу с файлом DBM*, и то, что они в итоге делают, не соответствует тому, что они делают с обычными хешами.
Как перезапустить операцию each() на части пути?
(внесено brian d foy)
Вы можете использовать функции keys или values для сброса each. Чтобы просто перезапустить итератор, используемый each, не выполняя ничего другого, используйте один из них в контексте void:
keys %hash; # resets iterator, nothing else.
values %hash; # resets iterator, nothing else. См. документацию для each в perlfunc.
Как получить уникальные ключи из двух хешей?
Сначала извлеките ключи из хешей в списки, затем решите проблему "удаления дубликатов", описанную выше. Например:
my %seen = ();
for my $element (keys(%foo), keys(%bar)) {
$seen{$element}++;
}
my @uniq = keys %seen; Или более кратко:
my @uniq = keys %{{%foo,%bar}}; Или, если вы действительно хотите сохранить место:
my %seen = ();
while (defined ($key = each %foo)) {
$seen{$key}++;
}
while (defined ($key = each %bar)) {
$seen{$key}++;
}
my @uniq = keys %seen; Как хранить многомерный массив в файле DBM?
Либо сами стройте структуру (неприятно), либо получите модуль MLDBM (который использует Data::Dumper) из CPAN и положите его поверх DB_File или GDBM_File. Вы также можете попробовать DBM::Deep, но он может быть немного медленным.
Как заставить хеш запомнить порядок добавления элементов?
Используйте Tie::IxHash из CPAN.
use Tie::IxHash;
tie my %myhash, 'Tie::IxHash';
for (my $i=0; $i<20; $i++) {
$myhash{$i} = 2*$i;
}
my @keys = keys %myhash;
# @keys = (0,1,2,3,...) Почему передача подпрограмме неопределённого элемента в хеше создаёт его?
(внесено brian d foy)
Вы используете очень старую версию Perl?
Обычно доступ к значению ключа хеша для несуществующего ключа не создаёт ключ.
my %hash = ();
my $value = $hash{ 'foo' };
print "This won't print\n" if exists $hash{ 'foo' }; Передача $hash{ 'foo' } подпрограмме раньше была специальным случаем. Поскольку вы могли напрямую присваивать $_[0], Perl должен был быть готов к этому присвоению, поэтому он создавал ключ хеша заранее:
my_sub( $hash{ 'foo' } );
print "This will print before 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
# $_[0] = 'bar'; # create hash key in case you do this
1;
} Однако, начиная с Perl 5.004, эта ситуация является специальным случаем, и Perl создаёт ключ хеша только при присвоении:
my_sub( $hash{ 'foo' } );
print "This will print, even after 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
$_[0] = 'bar';
} Однако, если вы хотите старое поведение (и хорошо подумайте об этом, потому что это странный побочный эффект), вы можете передать срез хеша вместо этого. Perl 5.004 не сделал это специальным случаем:
my_sub( @hash{ qw/foo/ } ); Как создать Perl-аналог структуры C/класса C++/хеша или массива хешей или массивов?
Обычно ссылка на хеш, возможно, так:
$record = {
NAME => "Jason",
EMPNO => 132,
TITLE => "deputy peon",
AGE => 23,
SALARY => 37_000,
PALS => [ "Norbert", "Rhys", "Phineas"],
}; Ссылки документированы в perlref и perlreftut. Примеры сложных структур данных приведены в perldsc и perllol. Примеры структур и классов объектно-ориентированного программирования находятся в perlootut.
Как использовать ссылку в качестве ключа хеша?
(внесено brian d foy и Ben Morrow)
Ключи хешей — это строки, поэтому вы не можете использовать ссылку в качестве ключа. Когда вы пытаетесь это сделать, Perl преобразует ссылку в её строковое представление (например, HASH(0xDEADBEEF)). Оттуда вы не можете получить ссылку из строкового представления, по крайней мере, не выполнив дополнительные действия самостоятельно.
Помните, что запись в хеше всё ещё будет там, даже если ссылка на переменную выходит из области видимости, и вполне возможно, что Perl впоследствии выделит другую переменную по тому же адресу. Это означает, что новая переменная может случайно быть связана со значением старой.
Если у вас Perl 5.10 или более поздней версии, и вам нужно просто сохранить значение по ссылке для последующего поиска, вы можете использовать модуль Hash::Util::Fieldhash. Это также обработает переименование ключей, если вы используете несколько потоков (что вызывает перераспределение всех переменных по новым адресам, изменяя их строковое представление), и удаление записей, когда ссылка на переменную выходит из области видимости.
Если вам действительно нужно получить реальную ссылку из каждой записи хеша, вы можете использовать модуль Tie::RefHash, который выполнит необходимые действия за вас.
Как проверить, существует ли ключ в многоуровневом хеше?
(внесено brian d foy)
Секрет решения этой проблемы заключается в том, чтобы избежать случайной автовивификации. Если вы хотите проверить ключ на глубине три, вы можете наивно попробовать так:
my %hash;
if( exists $hash{key1}{key2}{key3} ) {
...;
} Даже если вы начинали с полностью пустого хеша, после вызова exists вы создали структуру, необходимую для проверки на key3:
%hash = (
'key1' => {
'key2' => {}
}
); Это автовивификация. Вы можете обойти это несколькими способами. Самый простой способ — просто отключить его. Лексический autovivification pragma доступен в CPAN. Теперь вы не добавляете в хеш:
{
no autovivification;
my %hash;
if( exists $hash{key1}{key2}{key3} ) {
...;
}
} Модуль Data::Diver на CPAN также может это сделать. Его процедура Dive может не только сказать, существуют ли ключи, но и получить значение:
use Data::Diver qw(Dive);
my @exists = Dive( \%hash, qw(key1 key2 key3) );
if( ! @exists ) {
...; # keys do not exist
}
elsif( ! defined $exists[0] ) {
...; # keys exist but value is undef
} Вы также легко можете сделать это самостоятельно, проверив каждый уровень хеша, прежде чем перейти к следующему. Это по сути то, что делает Data::Diver за вас:
if( check_hash( \%hash, qw(key1 key2 key3) ) ) {
...;
}
sub check_hash {
my( $hash, @keys ) = @_;
return unless @keys;
foreach my $key ( @keys ) {
return unless eval { exists $hash->{$key} };
$hash = $hash->{$key};
}
return 1;
} Как предотвратить добавление нежелательных ключей в хеш?
Начиная с версии 5.8.0, хеши могут быть ограничены фиксированным числом заданных ключей. Методы создания и работы с ограниченными хешами экспортируются модулем Hash::Util.
Данные: Разное
Как правильно обработать двоичные данные?
Perl корректно обрабатывает двоичные данные. Однако в Windows или DOS необходимо использовать binmode для двоичных файлов, чтобы избежать преобразования конечных символов строк. В общем случае, вы должны использовать binmode всякий раз, когда работаете с двоичными данными.
Также см. "binmode" в perlfunc или perlopentut.
Если вас беспокоят 8-битные текстовые данные, см. perllocale. Однако если вы хотите работать с многобайтовыми символами, есть некоторые уловки. См. раздел "Регулярные выражения".
Как определить, является ли скаляр числом/целым/целым/вещественным?
Предполагая, что вас не интересуют записи IEEE, такие как "NaN" или "Бесконечность", вы, вероятно, просто захотите использовать регулярное выражение (см. также perlretut и perlre):
use 5.010;
if ( /\D/ )
{ say "\thas nondigits"; }
if ( /^\d+\z/ )
{ say "\tis a whole number"; }
if ( /^-?\d+\z/ )
{ say "\tis an integer"; }
if ( /^[+-]?\d+\z/ )
{ say "\tis a +/- integer"; }
if ( /^-?(?:\d+\.?|\.\d)\d*\z/ )
{ say "\tis a real number"; }
if ( /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?\z/i )
{ say "\tis a C float" } Также существуют некоторые часто используемые модули для этой задачи. Scalar::Util (распространяется с версией 5.8) предоставляет доступ к внутренней функции Perl looks_like_number для определения, похожа ли переменная на число. Data::Types экспортирует функции, которые проверяют типы данных, используя вышеуказанные и другие регулярные выражения. В-третьих, есть Regexp::Common, который содержит регулярные выражения для соответствия различным типам чисел. Эти три модуля доступны на CPAN.
Если вы работаете на системе POSIX, Perl поддерживает функцию POSIX::strtod для преобразования строк в числа с плавающей точкой (а также POSIX::strtol для целых чисел). Ее семантика немного сложна, поэтому здесь представлена функция-обёртка getnum для более удобного доступа. Эта функция принимает строку и возвращает найденное число или undef для входных данных, которые не являются числом с плавающей точкой C. Функция is_numeric является интерфейсом к getnum, если вам нужно просто сказать: «Это число с плавающей точкой?»
sub getnum {
use POSIX qw(strtod);
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
$! = 0;
my($num, $unparsed) = strtod($str);
if (($str eq '') || ($unparsed != 0) || $!) {
return undef;
}
else {
return $num;
}
}
sub is_numeric { defined getnum($_[0]) } Или вы можете изучить модуль String::Scanf на CPAN.
Как сохранить постоянные данные между вызовами программы?
Для некоторых конкретных приложений вы можете использовать один из модулей DBM. См. AnyDBM_File. Более общим способом является обращение к модулям FreezeThaw или Storable с CPAN. Начиная с Perl 5.8, Storable входит в стандартную поставку. Вот пример использования функций Storable's store и retrieve:
use Storable;
store(\%hash, "filename");
# later on...
$href = retrieve("filename"); # by ref
%hash = %{ retrieve("filename") }; # direct to hash Как вывести или скопировать рекурсивную структуру данных?
Модуль Data::Dumper на CPAN (или версия 5.005 Perl) отлично подходит для вывода структур данных. Модуль Storable на CPAN (или версия 5.8 Perl) предоставляет функцию под названием dclone, которая рекурсивно копирует свой аргумент.
use Storable qw(dclone);
$r2 = dclone($r1); Где $r1 может быть ссылкой на любой тип структуры данных, которую вы хотите. Она будет глубоко скопирована. Поскольку dclone принимает и возвращает ссылки, вам придётся добавить дополнительные знаки пунктуации, если у вас есть массив хэшей, который вы хотите скопировать.
%newhash = %{ dclone(\%oldhash) }; Как определить методы для каждого класса/объекта?
(представлено Бен Мороу)
Вы можете использовать класс UNIVERSAL (см. UNIVERSAL). Однако, будьте очень осторожны, учитывая возможные последствия: добавление методов ко всем объектам, скорее всего, приведёт к непредвиденным последствиям. Если это возможно, лучше сделать так, чтобы все ваши объекты наследовали от некоторого общего базового класса, или использовать систему объектов, такую как Moose, которая поддерживает роли.
Как проверить контрольную сумму кредитной карты?
Получите модуль Business::CreditCard с CPAN.
Как упаковать массивы чисел с плавающей точкой двойной или одинарной точности для кода XS?
Код arrays.h/arrays.c в модуле PGPLOT на CPAN делает именно это. Если вы выполняете много вычислений с числами с плавающей точкой или двойной точности, рассмотрите использование модуля PDL с CPAN — он упрощает работу с вычислениями.
См. https://metacpan.org/release/PGPLOT для кода.
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (с) 1997-2010 Том Кристиансен, Нейтан Торкингтон и другие авторы, как указано. Все права защищены.
Эта документация бесплатна; вы можете распространять и/или изменять ее в соответствии с теми же условиями, что и Perl сам по себе.
Независимо от его распространения, все примеры кода в этом файле объявляются публичным достоянием. Вам разрешается и рекомендуется использовать этот код в своих программах для развлечения или с выгодой для себя, как вам заблагорассудится. Простой комментарий в коде, приносящий признательность, будет вежливым, но не является обязательным.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.30.3/perlfaq4