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?
- Как заставить хеш запомнить порядок, в котором я вставлял элементы?
- Почему передача подпрограмме неопределенного элемента в хеше создает его?
- Как создать эквивалент C-структуры/C++-класса/хеша или массива хешей или массивов в Perl?
- Как использовать ссылку в качестве ключа хеша?
- Как проверить, существует ли ключ в многоуровневом хеше?
- Как предотвратить добавление нежелательных ключей в хеш?
- Данные: Разное
- Как правильно обработать двоичные данные?
- Как определить, является ли скаляр числом/целым/целым/вещественным?
- Как сохранить постоянные данные между вызовами программы?
- Как распечатать или скопировать рекурсивную структуру данных?
- Как определить методы для каждого класса/объекта?
- Как проверить контрольную сумму кредитной карты?
- Как упаковать массивы двойных или чисел с плавающей точкой для кода XS?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq4 - Обработка данных
ВЕРСИЯ
версия 5.021011
ОПИСАНИЕ
Этот раздел 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 пропускает ведущие пробелы и нули, а затем предполагает, что остальные цифры в десятичной системе счисления:
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. В Perl 5.004 модуль Math::Trig (входит в стандартную поставку Perl) реализует тригонометрические функции. Внутренне он использует модуль Math::Complex, и некоторые функции могут выходить за пределы действительной оси в комплексную плоскость, например, обратный синус числа 2.
Округление в финансовых приложениях может иметь серьезные последствия, и используемый метод округления должен быть чётко определён. В таких случаях, вероятно, лучше не полагаться на встроенное округление Perl, а реализовать необходимую функцию округления самостоятельно.
Чтобы понять почему, обратите внимание, что всё ещё будет проблема с чередованием на середине:
for (my $i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0 Не вините 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();Остальные преобразования (например, шестнадцатеричное в восьмеричное, двоичное в шестнадцатеричное и т. д.) оставлены в качестве упражнения для заинтересованного читателя.
Почему & не работает так, как я хочу?
Поведение бинарных арифметических операторов зависит от того, используются ли они с числами или строками. Операторы рассматривают строку как последовательность битов и работают с ней (строка "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 ("Mersenne Twister", быстрый) или 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 -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 Как найти дату вчерашнего дня?
(предоставлено 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" Из-за ограничений кавычек эта проблема довольно сложна. К счастью, у нас есть Джеффри Фридл, автор книги Мастерство регулярных выражений, который может справиться с этим. Он предлагает (предполагая, что ваша строка содержится в $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-документы не работают?
Документы HERE находятся в perlop. Проверьте эти три вещи:
- Не должно быть пробелов после части <<.
- Вероятно, должен быть символ точки с запятой в конце открывающего токена.
- Вы не можете (легко) иметь пробелы перед тегом.
- Должен быть хотя бы разделитель строк после завершающего токена.
Если вы хотите отступать текст в документе HERE, вы можете сделать это:
# 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--/; Следующая универсальная функция для исправления отступов в документах HERE. Она принимает документ HERE в качестве аргумента. Она проверяет, начинается ли каждая строка с общей подстроки, и если да, удаляет эту подстроку. В противном случае она принимает количество начальных пробелов, обнаруженное в первой строке, и удаляет это количество из каждой последующей строки.
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 Данные: Массивы
В чём разница между списком и массивом?
(внёс вклад 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 Поскольку вы присваиваете скаляру, правая часть находится в скалярном контексте. Оператор запятой (да, это оператор!) в скалярном контексте оценивает свою левую часть, отбрасывает результат и оценивает свою правую часть и возвращает результат. По сути, такой псевдосписок присваивает своё правое значение.
$scalar Многие люди ошибаются в этом, потому что выбирают псевдосписок, последний элемент которого также является ожидаемым подсчётом:
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 для скорости, а её перловский эквивалент выглядит так:
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;
} Обратите внимание, что это симметричная разность, то есть все элементы в А или в В, но не в обоих. Представьте это как операцию 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, блокировать меньшие массивы в многопоточных программах, перераспределять меньше памяти или быстро вставлять элементы в середину цепочки.
Steve Lembark подробно рассматривает это в своей презентации YAPC::NA 2009 "Perly Linked Lists" ( 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; По умолчанию функция sort - это cmp, сравнение строк, которое отсортирует (1, 2, 10) в (1, 10, 2). <=>, используемая выше, является оператором числового сравнения.
Если вам нужна сложная функция для выделения части, по которой вы хотите отсортировать, то не делайте это внутри функции sort. Сначала выделите её, потому что блок sort может вызываться много раз для одного и того же элемента. Вот пример того, как выделить первое слово после первой цифры в каждом элементе, а затем отсортировать эти слова без учёта регистра.
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)
Для сортировки хеша начните с ключей. В этом примере мы передаём список ключей в функцию сортировки, которая сравнивает их в лексикографическом порядке (что может зависеть от настроек вашего языка). Результирующий список содержит ключи в лексикографическом порядке. После получения ключей, мы можем пройтись по ним, чтобы создать отчёт, упорядоченный по лексикографическому порядку ключей.
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() привязанного хеша. Например, в хешах, привязанных к файлам DBM*, нет понятия undef. Это также означает, что exists() и defined() делают то же самое с файлом DBM*, и то, что они в итоге делают, отличается от их поведения с обычными хешами.
Как сбросить операцию each() на частично обработанном этапе?
(предложено brian d foy)
Вы можете использовать функции keys или values для сброса each. Чтобы просто сбросить итератор, используемый each, без дополнительных действий, используйте одну из них в пустом контексте:
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/ } ); Как создать аналог C-структуры/C++-класса/хеша или массива хешей/массивов в Perl?
Обычно это ссылка на хеш, например:
$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 доступна в 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" или "Infinity", вы, вероятно, захотите использовать регулярное выражение (см. также 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 — он упрощает обработку чисел.
Смотрите http://search.cpan.org/dist/PGPLOT для кода.
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.
Эта документация бесплатна; вы можете перераспределять ее и/или изменять ее на тех же условиях, что и 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.28.3/perlfaq4