perlfaq4
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Данные: Числа
- Почему я получаю длинные десятичные дроби (например, 19.9499999999999) вместо ожидаемых чисел (например, 19.95)?
- Почему int() не работает?
- Почему мои данные в восьмеричной системе не интерпретируются корректно?
- Есть ли в Perl функция round()? А также ceil() и floor()? Тригонометрические функции?
- Как преобразовать между числовыми представлениями/основаниями/системами счисления?
- Почему оператор & работает не так, как я ожидаю?
- Как умножить матрицы?
- Как выполнить операцию над рядом целых чисел?
- Как вывести римские цифры?
- Почему мои случайные числа не случайны?
- Как получить случайное число между X и Y?
- Данные: Даты
- Данные: Строки
- Как валидировать ввод?
- Как обработать строку с обратными слешами?
- Как удалить последовательные пары символов?
- Как расширить вызовы функций в строке?
- Как найти совпадения/вложенность чего-либо?
- Как перевернуть строку?
- Как расширить табуляцию в строке?
- Как переформатировать абзац?
- Как получить доступ к N символам строки или изменить их?
- Как изменить N-е вхождение чего-либо?
- Как посчитать количество вхождений подстроки в строке?
- Как сделать заглавными все слова в строке?
- Как разделить строку, ограниченную символом [символ], за исключением случаев, когда он находится внутри [символ]?
- Как убрать пробелы с начала и конца строки?
- Как дополнить строку пробелами или число нулями?
- Как извлечь выбранные столбцы из строки?
- Как найти значение Soundex строки?
- Как расширить переменные в текстовых строках?
- Есть ли в Perl что-то подобное Ruby's #{} или Python's f-строкам?
- В чём проблема всегда указывать двойные кавычки для "$vars"?
- Почему мои <<HERE документы не работают?
- Данные: Массивы
- В чём разница между списком и массивом?
- В чём разница между $array[1] и @array[1]?
- Как удалить дубликаты из списка или массива?
- Как проверить, содержится ли определённый элемент в списке или массиве?
- Как вычислить разность двух массивов? Как вычислить пересечение двух массивов?
- Как проверить, равны ли два массива или два хэша?
- Как найти первый элемент массива, для которого выполняется условие?
- Как обработать связанные списки?
- Как обработать циклические списки?
- Как случайным образом перемешать массив?
- Как обработать/изменить каждый элемент массива?
- Как выбрать случайный элемент из массива?
- Как выполнить перестановку N элементов списка?
- Как отсортировать массив по (любому) критерию?
- Как манипулировать массивами битов?
- Почему defined() возвращает true для пустых массивов и хэшей?
- Данные: Хеши (Ассоциативные массивы)
- Как обработать весь хеш?
- Как объединить два хеша?
- Что происходит, если я добавляю или удаляю ключи из хеша во время итерации по нему?
- Как найти элемент хеша по значению?
- Как узнать, сколько элементов в хеше?
- Как отсортировать хеш (по значению, а не по ключу)?
- Как всегда хранить хеш отсортированным?
- В чем разница между "delete" и "undef" в отношении хешей?
- Почему мои связанные хеши не различают defined/exists?
- Как сбросить операцию each() на половине процесса?
- Как получить уникальные ключи из двух хешей?
- Как сохранить многомерный массив в файле DBM?
- Как заставить хеш запоминать порядок добавления элементов?
- Почему передача подпрограмме неопределенного элемента в хеше создает его?
- Как создать Perl-эквивалент структуры C/класса C++/хеша или массива хешей или массивов?
- Как использовать ссылку в качестве ключа хеша?
- Как проверить, существует ли ключ в многоуровневом хеше?
- Как предотвратить добавление нежелательных ключей в хеш?
- Данные: Разное
- Как правильно обрабатывать двоичные данные?
- Как определить, является ли скаляр числом/целым/целым/вещественным?
- Как сохранить постоянные данные между вызовами программы?
- Как вывести или скопировать рекурсивную структуру данных?
- Как определить методы для каждого класса/объекта?
- Как проверить контрольную сумму кредитной карты?
- Как упаковать массивы двойных или плавающей точки для кода XS?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq4 - Обработка данных
ВЕРСИЯ
версия 5.20210411
ОПИСАНИЕ
Этот раздел 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() просто обрезает значение к 0. Для округления до определённого количества знаков, 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 (использует несовершенства таймера системы для генерации случайных чисел, что довольно медленно). Более подробные алгоритмы для случайных чисел описаны в «Численных рецептах на 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 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; А теперь, что если вам не нужно делать заглавными "и"? Просто используйте 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; Есть ли в Perl что-то подобное Ruby #{} или Python f-строке?
В отличие от других языков, Perl позволяет встраивать переменную в строку с двойными кавычками, например, "variable $variable". Когда после имени переменной нет пробелов или других символов, не являющихся словами, вы можете добавить фигурные скобки (например, "foo ${foo}bar"), чтобы гарантировать правильный разбор.
Массив также может быть встроен непосредственно в строку и будет расширен по умолчанию пробелами между элементами. По умолчанию LIST_SEPARATOR можно изменить, присвоив другую строку специальной переменной $", например, local $" = ', ';.
Perl также поддерживает ссылки внутри строки, предоставляя эквивалент функций в других двух языках.
${\ ... } внутри строки будет работать для большинства простых инструкций, таких как вызов метода объекта. Более сложный код может быть заключен в блок do ${\ do{...} }.
Когда вы хотите, чтобы список расширялся, как в $", используйте @{[ ... ]}.
use Time::Piece;
use Time::Seconds;
my $scalar = 'STRING';
my @array = ( 'zorro', 'a', 1, 'B', 3 );
# Print the current date and time and then Tommorrow
my $t = Time::Piece->new;
say "Now is: ${\ $t->cdate() }";
say "Tomorrow: ${\ do{ my $T=Time::Piece->new + ONE_DAY ; $T->fullday }}";
# some variables in strings
say "This is some scalar I have $scalar, this is an array @array.";
say "You can also write it like this ${scalar} @{array}.";
# Change the $LIST_SEPARATOR
local $" = ':';
say "Set \$\" to delimit with ':' and sort the Array @{[ sort @array ]}"; Вы также можете ознакомиться с модулем Quote::Code и инструментами для шаблонов, такими как Template::Toolkit и Mojo::Template.
См. также: "Как расширить переменные в строках текста?" и "Как расширить вызовы функций в строке?" в этом FAQ.
В чём проблема с постоянным использованием кавычек «$переменные»?
Проблема в том, что эти двойные кавычки принуждают к преобразованию в строку — превращают числа и ссылки в строки — даже тогда, когда вы этого не хотите. Представьте это так: расширение двойных кавычек используется для создания новых строк. Если у вас уже есть строка, зачем вам нужна ещё одна?
Если вы привыкнете писать странные вещи, такие как:
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. Проверьте эти три вещи:
- Требуется отсутствие пробела после части <<.
- Вероятно, должен быть символ точка с запятой в конце открывающего токена.
- Вы не можете (легко) иметь пробелы перед тегом.
- После завершающего токена должен быть хотя бы разделитель строк.
Если вы хотите отступать текст в документе 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 в качестве аргумента. Она проверяет, начинается ли каждая строка с общей подстроки, и если да, удаляет эту подстроку. В противном случае она берёт количество начальных пробелов, найденных в первой строке, и удаляет такое же количество с каждой последующей строки.
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 Начиная с версии 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 Поскольку вы присваиваете скаляру, правая часть находится в скалярном контексте. Оператор запятой (да, это оператор!) в скалярном контексте оценивает свою левую часть, отбрасывает результат и оценивает свою правую часть, возвращая результат. По сути, этот спископодобный элемент присваивает $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)
Слово «в» предполагает, что вы, вероятно, должны были использовать хэш, а не список или массив, для хранения данных. Хэши предназначены для быстрого и эффективного ответа на этот вопрос. Массивы — нет.
Тем не менее, есть несколько способов подойти к этому. Если вы собираетесь многократно запрашивать произвольные строковые значения, самым быстрым способом, вероятно, является инвертирование исходного массива и поддержание хэша, ключами которого являются значения из исходного массива:
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 экспортирует функцию any для этой цели. Она работает, останавливаясь, как только находит элемент. Она написана на C для скорости, а её перловский эквивалент выглядит так:
sub any (&@) {
my $code = shift;
foreach (@_) {
return 1 if $code->();
}
return 0;
} Если скорость не имеет большого значения, обычный подход использует 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.
Как проверить, равны ли два массива или хэша?
Следующий код работает для массивов с одним уровнем. Он использует сравнение строк и не различает определённые и неопределённые пустые строки. Измените, если у вас есть другие потребности.
$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 «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(), обсуждается в четвёртом томе (ещё не опубликован) «Искусства программирования» Кнута и будет работать с любым списком:
#!/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). <=>, используемая выше, — это оператор численного сравнения.
Если вам нужна сложная функция для извлечения части, по которой вы хотите отсортировать, не делайте это внутри функции сортировки. Сначала извлеките её, потому что блок 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» в сборнике «Far More Than You Ever Wanted To Know» по адресу 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)
Чтобы отсортировать хеш, начните с ключей. В этом примере мы передаём список ключей в функцию сортировки, которая затем сравнивает их по 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, как описано в "In Memory Databases" в 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 Смотрите, вся запись исчезла!
Почему связанные хеши не делают различия между определённым/существующим?
Это зависит от реализации EXISTS() связанного хеша. Например, с хешами, привязанными к файлам DBM*, нет понятия undef. Это также означает, что 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 или более поздней версии, и вам просто нужно сохранить значение по отношению к ссылке для последующего поиска, вы можете использовать модуль core 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 store и retrieve:
use Storable;
store(\%hash, "filename");
# later on...
$href = retrieve("filename"); # by ref
%hash = %{ retrieve("filename") }; # direct to hash Как вывести или скопировать рекурсивную структуру данных?
Модуль Data::Dumper на CPAN (или версия Perl 5.005) отлично подходит для вывода структур данных. Модуль Storable на CPAN (или версия Perl 5.8) предоставляет функцию под названием 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 для кода.
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 1997-2010 Тома Кристиана, Нейтана Торкинтона и других авторов, как указано. Все права защищены.
Данная документация распространяется свободно; вы можете перераспределять и/или изменять ее на тех же условиях, что и Perl сам по себе.
Независимо от его распространения, все примеры кода в этом файле помещаются в общественное достояние. Вам разрешается и рекомендуется использовать этот код в своих программах для развлечения или с выгодой, как вам заблагорассудится. Простой комментарий в коде, приносящий дань уважения, был бы вежливым, но не является обязательным.
© 1993–2021 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.34.0/perlfaq4