Spec-Zone.ru › Perl 5.38

perlfaq4

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ВЕРСИЯ
  • ОПИСАНИЕ
  • Данные: Числа
    • Почему я получаю длинные десятичные дроби (например, 19.9499999999999) вместо чисел, которые я должен получать (например, 19.95)?
    • Почему int() сломан?
    • Почему мои данные в восьмеричном формате не интерпретируются правильно?
    • Есть ли в Perl функция round()? Что насчет ceil() и floor()? Тригонометрические функции?
    • Как преобразовать между числовыми представлениями/основаниями/системами счисления?
    • Почему & работает не так, как я хочу?
    • Как умножить матрицы?
    • Как выполнить операцию над рядом целых чисел?
    • Как вывести римские цифры?
    • Почему мои случайные числа не случайны?
    • Как получить случайное число между X и Y?
  • Данные: Даты
    • Как найти день или неделю года?
    • Как найти текущий век или тысячелетие?
    • Как сравнить две даты и найти разницу?
    • Как преобразовать строку в количество секунд с эпохи?
    • Как найти юлианский день?
    • Как найти вчерашнюю дату?
    • Есть ли у Perl проблема 2000 или 2038 года? Соответствует ли Perl стандарту Y2K?
  • Данные: Строки
    • Как валидировать входные данные?
    • Как разэкранировать строку?
    • Как удалить последовательные пары символов?
    • Как расширить вызовы функций в строке?
    • Как найти соответствия/вложенности чего-либо?
    • Как перевернуть строку?
    • Как расширить табуляции в строке?
    • Как переформатировать абзац?
    • Как получить доступ к N символам строки или изменить их?
    • Как изменить N-е вхождение чего-либо?
    • Как посчитать количество вхождений подстроки в строке?
    • Как сделать заглавными все слова в строке?
    • Как разбить строку, ограниченную символом [символ], за исключением случаев, когда он находится внутри [символ]?
    • Как удалить пробелы в начале/конце строки?
    • Как дополнить строку пробелами или число нулями?
    • Как извлечь выбранные столбцы из строки?
    • Как найти значение Soundex строки?
    • Как расширить переменные в текстовых строках?
    • Есть ли в Perl что-то подобное Ruby #{} или Python f-строкам?
    • В чём проблема всегда ставить кавычки вокруг "$vars"?
    • Почему мои документы <<HERE не работают?
  • Данные: Массивы
    • В чём разница между списком и массивом?
    • В чём разница между $array[1] и @array[1]?
    • Как удалить дубликаты элементов из списка или массива?
    • Как узнать, содержится ли определённый элемент в списке или массиве?
    • Как вычислить разность двух массивов? Как вычислить пересечение двух массивов?
    • Как проверить, равны ли два массива или два хэша?
    • Как найти первый элемент массива, для которого выполняется условие?
    • Как обращаться со связанными списками?
    • Как обращаться с циклическими списками?
    • Как случайным образом перемешать массив?
    • Как обработать/изменить каждый элемент массива?
    • Как выбрать случайный элемент из массива?
    • Как переставить N элементов списка?
    • Как отсортировать массив по (любому) критерию?
    • Как манипулировать массивами битов?
    • Почему defined() возвращает true для пустых массивов и хэшей?
  • Данные: Хэши (Ассоциативные массивы)
    • Как обработать весь хэш?
    • Как объединить два хэша?
    • Что происходит, если я добавляю или удаляю ключи из хэша во время итерирования по нему?
    • Как найти элемент хэша по значению?
    • Как узнать, сколько элементов в хэше?
    • Как отсортировать хэш (по значению вместо ключа)?
    • Как всегда держать хэш отсортированным?
    • В чем разница между "delete" и "undef" для хэшей?
    • Почему мои привязанные хэши не соблюдают различие определено/существует?
    • Как сбросить операцию each() на середине?
    • Как получить уникальные ключи из двух хэшей?
    • Как сохранить многомерный массив в файле DBM?
    • Как сделать так, чтобы хэш запоминал порядок добавления элементов?
    • Почему передача подпрограмме неопределенного элемента в хэше создаёт его?
    • Как создать в Perl эквивалент структуры C/класса C++/хэша или массива хэшей или массивов?
    • Как использовать ссылку в качестве ключа хэша?
    • Как проверить, существует ли ключ в многоуровневом хэше?
    • Как предотвратить добавление нежелательных ключей в хэш?
  • Данные: Разное
    • Как правильно обрабатывать двоичные данные?
    • Как определить, является ли скаляр числом/целым/целым числом/вещественным числом?
    • Как сохранить постоянные данные между вызовами программы?
    • Как вывести или скопировать рекурсивную структуру данных?
    • Как определить методы для каждого класса/объекта?
    • Как проверить контрольную сумму кредитной карты?
    • Как упаковать массивы двойных или плавающих чисел для кода XS?
  • АВТОР И АВТОРСКИЕ ПРАВА

НАЗВАНИЕ

perlfaq4 - Обработка данных

ВЕРСИЯ

версия 5.20210520

ОПИСАНИЕ

Этот раздел 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() просто усекает к 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();

Остальные преобразования (например, шестнадцатеричное -> восьмеричное, двоичное -> шестнадцатеричное и т. д.) оставляются в качестве упражнения для заинтересованного читателя.

Почему & не работает так, как я хочу?

Поведение бинарных операторов зависит от того, используются ли они с числами или строками. Операторы рассматривают строку как последовательность битов и работают с ней (строка "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 более одного раза — это сделает ваши числа менее случайными, а не более.

Компьютеры хорошо предсказуемы и плохо случайны (несмотря на видимость, вызванную ошибками в ваших программах :-). Статья "random" в сборнике "Far More Than You Ever Wanted To Know" в 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() как левостороннее значение.

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;

Есть ли в 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.

В чём проблема с постоянным использованием двойных кавычек для "$vars"?

Проблема в том, что эти двойные кавычки заставляют строковое представление – преобразуют числа и ссылки в строки – даже когда вы этого не хотите. Представьте это так: расширение двойных кавычек используется для создания новых строк. Если у вас уже есть строка, зачем вам ещё одна?

Если вы привыкли писать странные вещи, подобные этим:

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-документах. Она ожидает в качестве аргумента здесь-документ. Она проверяет, начинается ли каждая строка с общей подстроки, и если да, то удаляет эту подстроку. В противном случае она берёт количество начальных пробелов, обнаруженных в первой строке, и удаляет такое же количество пробелов из каждой последующей строки.

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

Поскольку вы присваиваете скаляру, правая часть находится в скалярном контексте. Оператор запятой (да, это оператор!) в скалярном контексте оценивает свою левую часть, отбрасывает результат и оценивает свою правую часть, возвращая результат. По сути, этот похожий на список присваивает своё правое крайнее значение.

Многие люди ошибаются, потому что выбирают похожий на список, последний элемент которого также является ожидаемым счётом:

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(), обсуждается в томе 4 (ещё не опубликованном) книги Кнута «Искусство программирования» и будет работать с любым списком:

#!/usr/bin/perl -n
# Fischer-Krause ordered permutation generator

sub permute (&@) {
    my $code = shift;
    my @idx = 0..$#_;
    while ( $code->(@_[@idx]) ) {
        my $p = $#idx;
        --$p while $idx[$p-1] > $idx[$p];
        my $q = $p or return;
        push @idx, reverse splice @idx, $p;
        ++$q while $idx[$p-1] > $idx[$q];
        @idx[$p-1,$q]=@idx[$q,$p-1];
    }
}

permute { print "@_\n" } split;

Модуль Algorithm::Loops также предоставляет функции NextPermute и NextPermuteNum, которые эффективно находят все уникальные перестановки массива, даже если он содержит дублированные значения, изменяя его на месте: если его элементы упорядочены в обратном порядке, то массив обращается, делая его отсортированным, и возвращает false; в противном случае возвращается следующая перестановка.

NextPermute использует строковый порядок, а NextPermuteNum - числовой порядок, поэтому вы можете перечислить все перестановки 0..9 следующим образом:

use Algorithm::Loops qw(NextPermuteNum);

my @list= 0..9;
do { print "@list\n" } while NextPermuteNum @list;

Как отсортировать массив по (чему угодно)?

Укажите функцию сравнения для sort() (описана в "sort" в perlfunc):

@list = sort { $a <=> $b } @list;

Функция сортировки по умолчанию - cmp, сравнение строк, которая отсортирует (1, 2, 10) в (1, 10, 2). <=>, используемая выше, является оператором числового сравнения.

Если вам нужна сложная функция для извлечения части, по которой вы хотите отсортировать, то не делайте это внутри функции 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)

Чтобы отсортировать хэш, начните с ключей. В этом примере мы передаём список ключей функции sort, которая затем сравнивает их по ASCII (что может быть повлияно настройками вашей локали). Выходной список содержит ключи в ASCII-порядке. После того, как мы получили ключи, мы можем пройти по ним, чтобы создать отчёт, в котором ключи перечислены в ASCII-порядке.

my @keys = sort { $a cmp $b } keys %hash;

foreach my $key ( @keys ) {
    printf "%-20s %6d\n", $key, $hash{$key};
}

Мы могли бы сделать блок sort() более сложным. Вместо сравнения ключей, мы можем вычислить значение с помощью этих ключей и использовать это значение для сравнения.

Например, чтобы сделать наш отчет нечувствительным к регистру, мы используем lc для приведения ключей к нижнему регистру перед их сравнением:

my @keys = sort { lc $a cmp lc $b } keys %hash;

Примечание: если вычисление дорогостоящее или хеш содержит много элементов, вам может потребоваться рассмотреть преобразование Шварца для кэширования результатов вычисления.

Если мы хотим сортировать по значению хеша, мы используем ключ хеша для его поиска. Мы по-прежнему получаем список ключей, но на этот раз они отсортированы по их значениям.

my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;

Отсюда мы можем перейти к более сложным задачам. Если значения хеша одинаковы, мы можем выполнить вторичную сортировку по ключу хеша.

my @keys = sort {
    $hash{$a} <=> $hash{$b}
        or
    "\L$a" cmp "\L$b"
} keys %hash;

Как всегда сохранять хеш отсортированным?

Вы можете рассмотреть использование модуля DB_File и tie() с использованием связываний хеша $DB_BTREE, как описано в "Базы данных в памяти" в DB_File. Модуль Tie::IxHash из CPAN также может быть полезен. Хотя это сохраняет ваш хеш отсортированным, вам может не понравиться замедление из-за интерфейса связывания. Вы уверены, что вам это нужно? :)

В чём разница между "delete" и "undef" с хешами?

Хеши содержат пары скаляров: первый - ключ, второй - значение. Ключ будет приведен к строковому типу, хотя значение может быть любого типа скаляр: строка, число или ссылка. Если ключ $key присутствует в %hash, exists($hash{$key}) вернет true. Значение для данного ключа может быть undef, в этом случае $hash{$key} будет undef, а exists $hash{$key} вернёт true. Это соответствует тому, что ($key, undef) находится в хеше.

Изображения помогут... Вот таблица %%%CODE_BLOCK_623%%:

  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

Обратите внимание на две последние строки: у вас есть неопределённое значение, но определённый ключ!

Теперь рассмотрим это:

delete $hash{'a'}

Ваша таблица теперь выглядит так:

  keys  values
+------+------+
|  x   |  7   |
|  d   |  0   |
|  e   |  2   |
+------+------+

и эти условия теперь соблюдаются; изменения заглавными буквами:

$hash{'a'}                       is false
$hash{'d'}                       is false
defined $hash{'d'}               is true
defined $hash{'a'}               is false
exists $hash{'a'}                is FALSE (Perl 5 only)
grep ($_ eq 'a', keys %hash)     is FALSE

Видите, вся запись исчезла!

Почему связанные хеши не делают различия между defined/exists?

Это зависит от реализации EXISTS() связанного хеша. Например, нет понятия undef для хешей, связанных с файлами DBM*. Это также означает, что exists() и defined() делают одно и то же с файлом DBM*, и то, что они в итоге делают, не соответствует тому, что они делают с обычными хешами.

Как сбросить операцию each() на половине пути?

(предложено brian d foy)

Вы можете использовать функции keys или values для сброса each. Чтобы просто сбросить итератор, используемый each без каких-либо дополнительных действий, используйте одну из них в контексте void:

keys %hash; # resets iterator, nothing else.
values %hash; # resets iterator, nothing else.

См. документацию по each в perlfunc.

Как получить уникальные ключи из двух хешей?

Сначала извлеките ключи из хешей в списки, а затем решите проблему удаления дубликатов, описанную выше. Например:

my %seen = ();
for my $element (keys(%foo), keys(%bar)) {
    $seen{$element}++;
}
my @uniq = keys %seen;

Или более лаконично:

my @uniq = keys %{{%foo,%bar}};

Или, если вы действительно хотите сэкономить место:

my %seen = ();
while (defined ($key = each %foo)) {
    $seen{$key}++;
}
while (defined ($key = each %bar)) {
    $seen{$key}++;
}
my @uniq = keys %seen;

Как сохранить многомерный массив в файле DBM?

Либо сами преобразуйте структуру в строку (не очень приятно), либо используйте модуль MLDBM (который использует Data::Dumper) из CPAN и используйте его поверх DB_File или GDBM_File. Вы также можете попробовать DBM::Deep, но он может быть немного медленным.

Как сделать так, чтобы хеш запоминал порядок, в котором я добавлял элементы?

Используйте Tie::IxHash из CPAN.

use Tie::IxHash;

tie my %myhash, 'Tie::IxHash';

for (my $i=0; $i<20; $i++) {
    $myhash{$i} = 2*$i;
}

my @keys = keys %myhash;
# @keys = (0,1,2,3,...)

Почему передача подпрограмме неопределённого элемента в хеше создаёт его?

(предложено brian d foy)

Вы используете очень старую версию Perl?

Обычно доступ к значению ключа хеша для несуществующего ключа не создаёт ключ.

my %hash  = ();
my $value = $hash{ 'foo' };
print "This won't print\n" if exists $hash{ 'foo' };

Передача $hash{ 'foo' } подпрограмме раньше была особым случаем. Поскольку вы могли напрямую присваивать значение $_[0], Perl должен был быть готов к этому присвоению, поэтому он создавал ключ хеша заранее:

my_sub( $hash{ 'foo' } );
print "This will print before 5.004\n" if exists $hash{ 'foo' };

sub my_sub {
    # $_[0] = 'bar'; # create hash key in case you do this
    1;
}

Однако, начиная с Perl 5.004, эта ситуация является особым случаем, и Perl создаёт ключ хеша только при присвоении значения:

my_sub( $hash{ 'foo' } );
print "This will print, even after 5.004\n" if exists $hash{ 'foo' };

sub my_sub {
    $_[0] = 'bar';
}

Однако, если вы хотите старое поведение (и тщательно обдумайте это, потому что это странный побочный эффект), вы можете передать срез хеша вместо этого. Perl 5.004 не сделал это особым случаем:

my_sub( @hash{ qw/foo/ } );

Как создать Perl-эквивалент структуры C/класса C++/хеша или массива хешей или массивов?

Обычно ссылка на хеш, возможно, так:

$record = {
    NAME   => "Jason",
    EMPNO  => 132,
    TITLE  => "deputy peon",
    AGE    => 23,
    SALARY => 37_000,
    PALS   => [ "Norbert", "Rhys", "Phineas"],
};

Ссылки документированы в perlref и perlreftut. Примеры сложных структур данных приведены в perldsc и perllol. Примеры структур и объектно-ориентированных классов приведены в perlootut.

Как использовать ссылку в качестве ключа хеша?

(предложено brian d foy и Ben Morrow)

Ключи хеша - это строки, поэтому вы не можете напрямую использовать ссылку в качестве ключа. Когда вы пытаетесь сделать это, Perl преобразует ссылку в её строковое представление (например, HASH(0xDEADBEEF)). Из этого строкового представления вы не можете получить обратно ссылку, по крайней мере, без дополнительных действий.

Помните, что запись в хеше останется, даже если ссылаемая переменная выходит из области видимости, и что Perl вполне может впоследствии выделить другую переменную по тому же адресу. Это означает, что новая переменная может случайно оказаться связанной со значением старой.

Если у вас Perl 5.10 или более поздней версии, и вы просто хотите сохранить значение по ссылке для последующего поиска, вы можете использовать модуль 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 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.

Данная документация бесплатна; вы можете распространять и/или изменять ее на тех же условиях, что и Perl сам по себе.

Независимо от его распространения, все примеры кода в этом файле помещаются в общественное достояние. Вы можете и должны использовать этот код в своих программах для развлечения или с целью получения прибыли, как вам заблагорассудится. Простая ссылка на код с указанием авторства будет вежливым жестом, но не является обязательной.

© 1993–2023 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.38.0/perlfaq4

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API