Spec-Zone.ru › Perl 5.36

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» в отношении хешей?
    • Почему мои связанные хеши не соблюдают различие между defined/exists?
    • Как сбросить операцию 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() просто отбрасывает дробную часть к нулю. Для округления до определённого количества десятичных знаков 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 более одного раза — это сделает ваши числа менее случайными, а не более.

Компьютеры хорошо предсказуемы и плохо генерируют случайные числа (несмотря на кажущиеся ошибки в ваших программах :-). Статья "Случайные числа" в коллекции "Множество вещей, которые вы хотели бы узнать" в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz, предоставленная Томом Фениксом, более подробно обсуждает это. Джон фон Нейман говорил: "Любой, кто пытается генерировать случайные числа детерминированными методами, конечно, живёт в состоянии греха".

Perl полагается на базовую систему для реализации rand и srand; на некоторых системах сгенерированные числа не достаточно случайны (особенно на Windows : см. http://www.perlmonks.org/?node_id=803632). Несколько модулей CPAN в пространстве имён Math реализуют лучшие псевдослучайные генераторы; например, Math::Random::MT ("Мерсенн Твистер", быстрый) или Math::TrulyRandom (использует несовершенства таймера системы для генерации случайных чисел, что довольно медленно). Более подробные алгоритмы для генерации случайных чисел описаны в "Numerical Recipes in C" по адресу http://www.nr.com/

Как получить случайное число между X и Y?

Чтобы получить случайное число между двумя значениями, вы можете использовать rand() для получения случайного числа от 0 до 1. Затем вы сдвигаете это значение в желаемый диапазон.

rand($x) возвращает число такое, что 0 <= rand($x) < $x. Таким образом, то, что вы хотите, чтобы Perl рассчитал, это случайное число в диапазоне от 0 до разницы между вашими X и Y.

То есть, чтобы получить число между 10 и 15 включительно, вам нужно случайное число от 0 до 5, которое вы затем можете добавить к 10.

my $number = 10 + int rand( 15-10+1 ); # ( 10,11,12,13,14, or 15 )

Поэтому вы можете создать следующую простую функцию, чтобы абстрагировать это. Она выбирает случайное целое число между двумя заданными целыми числами (включительно). Например: random_int_between(50,120).

sub random_int_between {
    my($min, $max) = @_;
    # Assumes that the two arguments are integers themselves!
    return $min if $min == $max;
    ($min, $max) = ($max, $min)  if  $min > $max;
    return $min + int rand(1 + $max - $min);
}

Данные: Даты

Как найти день или неделю года?

День года находится в списке, возвращаемом функцией localtime. Без аргумента localtime используется текущее время.

my $day_of_year = (localtime)[7];

Модуль POSIX также может форматировать дату как день или неделю года.

use POSIX qw/strftime/;
my $day_of_year  = strftime "%j", localtime;
my $week_of_year = strftime "%W", localtime;

Чтобы получить день года для любой даты, используйте mktime модуля POSIX, чтобы получить время в эпохальных секундах для аргумента функции localtime.

use POSIX qw/mktime strftime/;
my $week_of_year = strftime "%W",
    localtime( mktime( 0, 0, 0, 18, 11, 87 ) );

Вы также можете использовать Time::Piece, который входит в состав Perl и предоставляет функцию localtime , возвращающую объект:

use Time::Piece;
my $day_of_year  = localtime->yday;
my $week_of_year = localtime->week;

Модуль Date::Calc также предоставляет две функции для вычисления этих значений:

use Date::Calc;
my $day_of_year  = Day_of_Year(  1987, 12, 18 );
my $week_of_year = Week_of_Year( 1987, 12, 18 );

Как найти текущий век или тысячелетие?

Используйте следующие простые функции:

sub get_century    {
    return int((((localtime(shift || time))[5] + 1999))/100);
}

sub get_millennium {
    return 1+int((((localtime(shift || time))[5] + 1899))/1000);
}

На некоторых системах функция strftime() модуля POSIX была расширена нестандартным образом для использования формата %C , который они иногда называют "веком". Это неверно, потому что на большинстве таких систем это всего лишь первые две цифры четырёхзначного года, и поэтому нельзя надёжно определить текущий век или тысячелетие.

Как сравнить две даты и найти разницу?

(представлено brian d foy)

Вы можете просто хранить все даты как число, а затем вычитать. Однако жизнь не всегда так проста.

Модуль Time::Piece, который входит в Perl, заменяет localtime на версию, возвращающую объект. Он также перегружает операторы сравнения, так что вы можете сравнивать их напрямую:

use Time::Piece;
my $date1 = localtime( $some_time );
my $date2 = localtime( $some_other_time );

if( $date1 < $date2 ) {
    print "The date was in the past\n";
}

Вы также можете получить разницу с помощью вычитания, что возвращает объект Time::Seconds:

my $date_diff = $date1 - $date2;
print "The difference is ", $date_diff->days, " days\n";

Если вы хотите работать с отформатированными датами, модули Date::Manip, Date::Calc или DateTime могут вам помочь.

Как преобразовать строку в эпохальные секунды?

Если это достаточно стандартная строка с постоянным форматом, вы можете разбить её и передать части в timelocal в стандартном модуле Time::Local. В противном случае, вы должны рассмотреть модули Date::Calc, Date::Parse и Date::Manip из CPAN.

Как найти юлианский день?

(представлено brian d foy и Dave Cross)

Вы можете использовать модуль Time::Piece, входящий в стандартную библиотеку, для преобразования даты/времени в юлианский день:

$ perl -MTime::Piece -le 'print localtime->julian_day'
2455607.7959375

Или модифицированный юлианский день:

$ perl -MTime::Piece -le 'print localtime->mjd'
55607.2961226851

Или даже день года (который некоторые считают юлианским днём):

$ perl -MTime::Piece -le 'print localtime->yday'
45

Вы также можете сделать то же самое с модулем DateTime:

$ perl -MDateTime -le'print DateTime->today->jd'
2453401.5
$ perl -MDateTime -le'print DateTime->today->mjd'
53401
$ perl -MDateTime -le'print DateTime->today->doy'
31

Вы можете использовать модуль Time::JulianDay, доступный в CPAN. Однако убедитесь, что вы действительно хотите найти юлианский день, так как у многих есть разные представления о юлианском дне (см., например, http://www.hermetic.ch/cal_stud/jdn.htm):

$  perl -MTime::JulianDay -le 'print local_julian_day( time )'
55608

Как найти вчерашнюю дату?

(представлено brian d foy)

Для правильного выполнения вы можете использовать один из модулей Date, так как они работают с календарём, а не со временем. Модуль DateTime упрощает задачу и даёт вам то же время, только на день раньше, несмотря на изменения летнего времени:

use DateTime;

my $yesterday = DateTime->now->subtract( days => 1 );

print "Yesterday was $yesterday\n";

Вы также можете использовать модуль Date::Calc с его функцией Today_and_Now.

use Date::Calc qw( Today_and_Now Add_Delta_DHMS );

my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );

print "@date_time\n";

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

Начиная с Perl 5.10, Time::Piece и Time::Seconds входят в стандартное распределение, поэтому вы могли бы подумать, что можете сделать что-то вроде этого:

use Time::Piece;
use Time::Seconds;

my $yesterday = localtime() - ONE_DAY; # WRONG
print "Yesterday was $yesterday\n";

Модуль Time::Piece экспортирует новый localtime, который возвращает объект, а Time::Seconds экспортирует константу ONE_DAY, которая представляет собой заданное количество секунд. Это означает, что он всегда даёт время 24 часа назад, что не всегда вчера. Это может вызвать проблемы в конце летнего времени, когда есть один день, который длится 25 часов.

У вас та же проблема с Time::Local, которая даст неверный ответ в тех же особых случаях:

# contributed by Gunnar Hjalmarsson
 use Time::Local;
 my $today = timelocal 0, 0, 12, ( localtime )[3..5];
 my ($d, $m, $y) = ( localtime $today-86400 )[3..5]; # WRONG
 printf "Yesterday: %d-%02d-%02d\n", $y+1900, $m+1, $d;

Проблема Perl с годом 2000 или 2038? Perl совместим с Y2K?

(внесено brian d foy)

Сам Perl никогда не имел проблемы с Y2K, хотя это не мешало людям создавать проблемы Y2K самостоятельно. См. документацию для localtime для правильного использования.

Начиная с Perl 5.12, localtime и gmtime могут обрабатывать даты после 03:14:08 19 января 2038 года, когда время на основе 32-битного представления переполнится. Вы всё равно можете получить предупреждение на 32-битном perl:

% perl5.12 -E 'say scalar localtime( 0x9FFF_FFFFFFFF )'
Integer overflow in hexadecimal number at -e line 1.
Wed Nov  1 19:42:39 5576711

На 64-битном perl, вы можете получить ещё более большие даты для действительно длительных проектов:

% perl5.12 -E 'say scalar gmtime( 0x9FFF_FFFFFFFF )'
Thu Nov  2 00:42:39 5576711

Но если вам нужно отслеживать распадающиеся протоны, у вас всё ещё будут проблемы.

Данные: Строки

Как валидировать входные данные?

(внесено brian d foy)

Существует множество способов убедиться, что значения соответствуют вашим ожиданиям или тому, что вы хотите принять. Помимо конкретных примеров, которые мы рассматриваем в perlfaq, вы также можете изучить модули с «Assert» и «Validate» в их названиях, а также другие модули, такие как Regexp::Common.

Некоторые модули имеют валидацию для определённых типов входных данных, таких как Business::ISBN, Business::CreditCard, Email::Valid и Data::Validate::IP.

Как раскодировать строку?

Это зависит от того, что вы имеете в виду под «раскодированием». URL-раскодирование обрабатывается в perlfaq9. Раскодирование оболочек с символом обратного слэша (\) удаляется с помощью

s/\\(.)/$1/g;

Это не будет расширять "\n" или "\t" или какие-либо другие специальные коды.

Как удалить последовательные пары символов?

(внесено brian d foy)

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

s/(.)\g1/$1/g;

Мы также можем использовать оператор транслитерации, tr///. В этом примере, сторона поиска нашего tr/// содержит ничего, но c опция дополняет это, так что она содержит всё. Список замены также содержит ничего, так что транслитерация почти бесполезна, поскольку она не произведёт никаких замен (или точнее, заменит символ на себя). Однако, s опция сжимает дублирующиеся и последовательные символы в строке, так что символ не появляется рядом с собой.

my $str = 'Haarlem';   # in the Netherlands
$str =~ tr///cs;       # Now Harlem, like in New York

Как расширить вызовы функций в строке?

(внесено brian d foy)

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

print "The time values are @{ [localtime] }.\n";

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

print "The time is ${\(scalar localtime)}.\n"

print "The time is ${ my $x = localtime; \$x }.\n";

Если ваша функция уже возвращает ссылку, вам не нужно создавать ссылку самостоятельно.

sub timestamp { my $t = localtime; \$t }

print "The time is ${ timestamp() }.\n";

Модуль Interpolation также может сделать много волшебства за вас. Вы можете указать имя переменной, в данном случае E, чтобы настроить связанный массив, который выполнит интерполяцию за вас. У него есть и другие методы для этого.

use Interpolation E => 'eval';
print "The time values are $E{localtime()}.\n";

В большинстве случаев, вероятно, проще просто использовать конкатенацию строк, что также принудительно устанавливает скалярный контекст.

print "The time is " . localtime() . ".\n";

Как найти совпадающие/вложенные элементы?

Чтобы найти что-то между двумя одиночными символами, шаблон вроде /x([^x]*)x/ получит промежуточные фрагменты в $1. Для нескольких элементов потребуется что-то более сложное вроде /alpha(.*?)omega/. Для вложенных шаблонов и/или сбалансированных выражений см. так называемый конструкт (?PARNO) (доступен с Perl 5.10). Модуль CPAN Regexp::Common может помочь в создании таких регулярных выражений (см. особенно Regexp::Common::balanced и Regexp::Common::delimited).

Более сложные случаи потребуют написания парсера, вероятно, с использованием модуля парсинга из CPAN, такого как Regexp::Grammars, Parse::RecDescent, Parse::Yapp, Text::Balanced или Marpa::R2.

Как перевернуть строку?

Используйте reverse() в скалярном контексте, как описано в "reverse" в perlfunc.

my $reversed = reverse $string;

Как расширить табуляцию в строке?

Вы можете сделать это самостоятельно:

1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;

Или вы можете просто использовать модуль Text::Tabs (часть стандартного распределения Perl).

use Text::Tabs;
my @expanded_lines = expand(@lines_with_tabs);

Как переформатировать абзац?

Используйте Text::Wrap (часть стандартного распределения Perl):

use Text::Wrap;
print wrap("\t", '  ', @paragraphs);

Абзацы, которые вы передаёте Text::Wrap, не должны содержать вложенных символов перевода строки. Text::Wrap не выравнивает строки по правому краю.

Или используйте модуль CPAN Text::Autoformat. Форматирование файлов может быть легко выполнено с помощью алиаса оболочки, например:

alias fmt="perl -i -MText::Autoformat -n0777 \
    -e 'print autoformat $_, {all=>1}' $*"

См. документацию для Text::Autoformat, чтобы оценить его множество возможностей.

Как получить доступ к N символам строки или изменить их?

Вы можете получить доступ к первым символам строки с помощью substr(). Чтобы получить первый символ, например, начинайте с позиции 0 и получите строку длиной 1.

my $string = "Just another Perl Hacker";
my $first_char = substr( $string, 0, 1 );  #  'J'

Чтобы изменить часть строки, вы можете использовать необязательный четвёртый аргумент, который является строкой замены.

substr( $string, 13, 4, "Perl 5.8.0" );

Вы также можете использовать substr() как lvalue.

substr( $string, 13, 4 ) =  "Perl 5.8.0";

Как изменить N-й экземпляр чего-либо?

Вы должны отслеживать N самостоятельно. Например, предположим, что вы хотите заменить пятый экземпляр "whoever" или "whomever" на "whosoever" или "whomsoever", не учитывая регистр. Все это предполагает, что $_ содержит строку, которая должна быть изменена.

$count = 0;
s{((whom?)ever)}{
++$count == 5       # is it the 5th?
    ? "${2}soever"  # yes, swap
    : $1            # renege and leave it there
    }ige;

В более общем случае вы можете использовать модификатор /g в цикле while, отслеживая количество совпадений.

$WANT = 3;
$count = 0;
$_ = "One fish two fish red fish blue fish";
while (/(\w+)\s+fish\b/gi) {
    if (++$count == $WANT) {
        print "The third fish is a $1 one.\n";
    }
}

Это выводит: "The third fish is a red one." Вы также можете использовать счётчик повторений и повторяющийся шаблон, как в этом примере:

/(?:\w+\s+fish\s+){2}(\w+)\s+fish/i;

Как подсчитать количество вхождений подстроки в строке?

Есть несколько способов, с разной эффективностью. Если вам нужен счёт определённого одиночного символа (X) в строке, вы можете использовать функцию tr/// следующим образом:

my $string = "ThisXlineXhasXsomeXx'sXinXit";
my $count = ($string =~ tr/X//);
print "There are $count X characters in the string";

Это нормально, если вы ищете только один символ. Однако, если вы пытаетесь подсчитать несколько символов подстроки в большей строке, tr/// не сработает. Вы можете обернуть цикл while() вокруг глобального поиска по шаблону. Например, давайте посчитаем отрицательные целые числа:

my $string = "-9 55 48 -2 23 -76 4 14 -44";
my $count = 0;
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string";

Другая версия использует глобальный поиск в контексте списка, затем присваивает результат скаляру, получая счёт совпадений.

my $count = () = $string =~ /-\d+/g;

Как сделать заглавными все слова в одной строке?

(внесено brian d foy)

Damian Conway's Text::Autoformat обрабатывает всё за вас.

use Text::Autoformat;
my $x = "Dr. Strangelove or: How I Learned to Stop ".
  "Worrying and Love the Bomb";

print $x, "\n";
for my $style (qw( sentence title highlight )) {
    print autoformat($x, { case => $style }), "\n";
}

Как вы хотите сделать эти слова заглавными?

FRED AND BARNEY'S LODGE        # all uppercase
Fred And Barney's Lodge        # title case
Fred and Barney's Lodge        # highlight case

Это не такая простая задача, как кажется. Сколько, по-вашему, слов там? Подождите... подождите... Если вы ответили 5, вы правы. Слова в Perl — это группы \w+, но это не то, что вы хотите сделать заглавными. Как Perl должен знать, чтобы не делать заглавным то s после апострофа? Вы можете попробовать регулярное выражение:

$string =~ s/ (
             (^\w)    #at the beginning of the line
               |      # or
             (\s\w)   #preceded by whitespace
               )
            /\U$1/xg;

$string =~ s/([\w']+)/\u\L$1/g;

А теперь, что если вы не хотите делать заглавным «и»? Просто используйте 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"

Из-за ограничения кавычек это довольно сложная проблема. К счастью, у нас есть Jeffrey Friedl, автор книги Mastering Regular Expressions, который может справиться с этим. Он предлагает (предполагая, что ваша строка содержится в $text):

my @new = ();
push(@new, $+) while $text =~ m{
    "([^\"\\]*(?:\\.[^\"\\]*)*)",? # groups the phrase inside the quotes
   | ([^,]+),?
   | ,
}gx;
push(@new, undef) if substr($text,-1,1) eq ',';

Если вы хотите представить кавычки внутри поля, ограниченного кавычками, экранируйте их обратными косыми чертами (например, "like \"this\"").

В качестве альтернативы, модуль Text::ParseWords (часть стандартного дистрибутива Perl) позволяет вам сделать следующее:

use Text::ParseWords;
@new = quotewords(",", 0, $text);

Однако для парсинга или генерации CSV настоятельно рекомендуется использовать Text::CSV, а не реализовывать его самостоятельно; вы сэкономите себе от странных ошибок, которые могут появиться позже, просто используя код, который уже много лет успешно используется в производстве.

Как удалить пробелы в начале и конце строки?

(внесён brian d foy)

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

s/^\s+//;
s/\s+$//;

Вы также можете записать это как одну подстановку, хотя оказывается, что объединённое выражение медленнее отдельных.

s/^\s+|\s+$//g;

В этом регулярном выражении альтернация соответствует началу или концу строки, так как якоря имеют более низкий приоритет, чем альтернация. Флаг /g вызывает подстановку всех возможных совпадений, поэтому он получает оба. Помните, что конечная переводная строка соответствует \s+, а якорь $ может соответствовать абсолютному концу строки, поэтому переводная строка тоже исчезает. Просто добавьте переводную строку в вывод, что имеет дополнительное преимущество сохранения "пустых" (состоящих только из пробелов) строк, которые ^\s+ удалит сами по себе:

while( <> ) {
    s/^\s+|\s+$//g;
    print "$_\n";
}

Для многострочной строки вы можете применить регулярное выражение к каждой логической строке в строке, добавив флаг /m (для "многострочного" режима). С флагом /m якорь $ соответствует перед вложенной новой строкой, поэтому не удаляет её. Эта схема по-прежнему удаляет переводную строку в конце строки:

$string =~ s/^\s+|\s+$//gm;

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

$string =~ s/^[\t\f ]+|[\t\f ]+$//mg;

Как заполнить строку пробелами или заполнить число нулями?

В следующих примерах $pad_len — это длина, до которой вы хотите заполнить строку, $text или $num содержит строку, которую нужно заполнить, а $pad_char содержит символ заполнения. Вы можете использовать константу строки одного символа вместо переменной $pad_char, если вы знаете её заранее. Аналогичным образом вы можете использовать целое число вместо $pad_len, если заранее знаете длину заполнения.

Самый простой метод использует функцию sprintf. Она может заполнять слева или справа пробелами и слева нулями, и она не будет обрезать результат. Функция pack может заполнять строки справа только пробелами и обрезает результат до максимальной длины $pad_len.

# Left padding a string with blanks (no truncation):
my $padded = sprintf("%${pad_len}s", $text);
my $padded = sprintf("%*s", $pad_len, $text);  # same thing

# Right padding a string with blanks (no truncation):
my $padded = sprintf("%-${pad_len}s", $text);
my $padded = sprintf("%-*s", $pad_len, $text); # same thing

# Left padding a number with 0 (no truncation):
my $padded = sprintf("%0${pad_len}d", $num);
my $padded = sprintf("%0*d", $pad_len, $num); # same thing

# Right padding a string with blanks using pack (will truncate):
my $padded = pack("A$pad_len",$text);

Если вам нужно заполнить символом, отличным от пробела или нуля, вы можете использовать один из следующих методов. Все они генерируют строку заполнения с помощью оператора x и объединяют её с $text. Эти методы не обрезают $text.

Заполнение слева и справа любым символом, создавая новую строку:

my $padded = $pad_char x ( $pad_len - length( $text ) ) . $text;
my $padded = $text . $pad_char x ( $pad_len - length( $text ) );

Заполнение слева и справа любым символом, изменяя $text напрямую:

substr( $text, 0, 0 ) = $pad_char x ( $pad_len - length( $text ) );
$text .= $pad_char x ( $pad_len - length( $text ) );

Как извлечь выбранные столбцы из строки?

(внесён brian d foy)

Если вы знаете столбцы, содержащие данные, вы можете использовать substr для извлечения одного столбца.

my $column = substr( $line, $start_column, $length );

Вы можете использовать split если столбцы разделены пробелами или каким-либо другим разделителем, при условии, что пробелы или разделители не могут появляться как часть данных.

my $line    = ' fred barney   betty   ';
my @columns = split /\s+/, $line;
    # ( '', 'fred', 'barney', 'betty' );

my $line    = 'fred||barney||betty';
my @columns = split /\|/, $line;
    # ( 'fred', '', 'barney', '', 'betty' );

Если вы хотите работать со значениями, разделёнными запятыми, не делайте этого, поскольку этот формат немного сложнее. Используйте один из модулей, которые обрабатывают этот формат, например, Text::CSV, Text::CSV_XS или Text::CSV_PP.

Если вы хотите разбить целую строку фиксированных столбцов, вы можете использовать unpack с форматом A (ASCII). Используя число после спецификатора формата, вы можете указать ширину столбца. См. записи pack и unpack в perlfunc для получения дополнительной информации.

my @fields = unpack( $line, "A8 A8 A8 A16 A4" );

Обратите внимание, что пробелы в аргументе формата для unpack не обозначают литеральные пробелы. Если у вас данные, разделённые пробелами, вы можете использовать split.

Как найти значение Soundex строки?

(внесён brian d foy)

Вы можете использовать модуль Text::Soundex. Если вы хотите выполнить нечёткое или близкое соответствие, попробуйте также модули String::Approx, Text::Metaphone и Text::DoubleMetaphone.

Как можно расширить переменные в строках текста?

(внесён brian d foy)

Если это возможно, избегайте этого или, если вы можете использовать систему шаблонов, например, Text::Template или Template Toolkit, сделайте это вместо этого. Вы даже можете справиться с sprintf или printf:

my $string = sprintf 'Say hello to %s and %s', $foo, $bar;

Однако для разового простого случая, когда я не хочу использовать полную систему шаблонов, я буду использовать строку с двумя переменными Perl. В этом примере я хочу расширить $foo и $bar до значений их переменных:

my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar';

Один из способов сделать это — использовать оператор подстановки и флаг двойного /e. Первый /e оценивает $1 в части замены и преобразует его в $foo. Второй /e начинается с $foo и заменяет его на его значение. $foo, следовательно, преобразуется в 'Fred', и это, наконец, остаётся в строке:

$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney'

Флаг /e также будет игнорировать нарушения строгих правил, заменяя неопределённые имена переменных пустой строкой. Поскольку я использую флаг /e (дважды!), у меня есть все те же проблемы с безопасностью, что и у eval в его строковом виде. Если в $foo есть что-то странное, например, @{[ system "rm -rf /" ]}, я могу попасть в беду.

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

my $string = 'This has $foo and $bar';

my %Replacements = (
    foo  => 'Fred',
    );

# $string =~ s/\$(\w+)/$Replacements{$1}/g;
$string =~ s/\$(\w+)/
    exists $Replacements{$1} ? $Replacements{$1} : '???'
    /eg;

print $string;

Есть ли в 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 документы не работают?

Документы здесь описаны в perlop. Проверьте три вещи:

После << не должно быть пробелов.
После открывающего маркера (вероятно) должен стоять точка с запятой.
Перед маркером не должно быть пробелов.
После закрывающего маркера должна быть хотя бы одна переводная строка.

Если вы хотите отступы в документе здесь, вы можете сделать так:

# all in one
(my $VAR = <<HERE_TARGET) =~ s/^\s+//gm;
    your text
    goes here
HERE_TARGET

Но HERE_TARGET должен всё ещё быть выровнен по краю. Если вы хотите, чтобы он был также отступом, вам нужно будет процитировать отступ.

(my $quote = <<'    FINIS') =~ s/^\s+//gm;
        ...we will have peace, when you and all your works have
        perished--and the works of your dark master to whom you
        would deliver us. You are a liar, Saruman, and a corrupter
        of men's hearts. --Theoden in /usr/src/perl/taint.c
    FINIS
$quote =~ s/\s+--/\n--/;

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

sub fix {
    local $_ = shift;
    my ($white, $leader);  # common whitespace and common leading string
    if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\g1\g2?.*\n)+$/) {
        ($white, $leader) = ($2, quotemeta($1));
    } else {
        ($white, $leader) = (/^(\s+)/, '');
    }
    s/^\s*?$leader(?:$white)?//gm;
    return $_;
}

Это работает с ведущими специальными строками, динамически определяемыми:

my $remember_the_main = fix<<'    MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@     SAVEI32(runlevel);
@@@     runlevel++;
@@@     while ( op = (*op->op_ppaddr)() );
@@@     TAINT_NOT;
@@@     return 0;
@@@ }
MAIN_INTERPRETER_LOOP

Или с фиксированным количеством начальных пробелов, с правильным сохранением остаточного отступа:

my $poem = fix<<EVER_ON_AND_ON;
   Now far ahead the Road has gone,
  And I must follow, if I can,
   Pursuing it with eager feet,
  Until it joins some larger way
   Where many paths and errands meet.
  And whither then? I cannot say.
    --Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON

Начиная с версии 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

Either the use warnings pragma or the -w flag will warn you when you use an array slice with a single index.

Как удалить дублирующиеся элементы из списка или массива?

(предоставлено 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 для скорости, и её эквивалент на Perl выглядит так:

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, блокировать меньшие массивы отдельно в потоковых программах, перераспределять меньше памяти или быстро вставлять элементы в середину цепочки.

Steve Lembark подробно рассматривает этот вопрос в своём выступлении на YAPC::NA 2009 "Perly Linked Lists" ( http://www.slideshare.net/lembark/perly-linked-lists ), хотя вы можете просто использовать его модуль LinkedList::Single.

Как обрабатывать циклические списки?

(предоставлено brian d foy)

Если вы хотите бесконечно циклически проходить по массиву, вы можете увеличивать индекс по модулю числа элементов в массиве:

my @array = qw( a b c );
my $i = 0;

while( 1 ) {
    print $array[ $i++ % @array ], "\n";
    last if $i > 20;
}

Вы также можете использовать Tie::Cycle для использования скаляра, который всегда содержит следующий элемент циклического массива:

use Tie::Cycle;

tie my $cycle, 'Tie::Cycle', [ qw( FFFFFF 000000 FFFF00 ) ];

print $cycle; # FFFFFF
print $cycle; # 000000
print $cycle; # FFFF00

Модуль Array::Iterator::Circular создаёт объект-итератор для циклических массивов:

use Array::Iterator::Circular;

my $color_iterator = Array::Iterator::Circular->new(
    qw(red green blue orange)
    );

foreach ( 1 .. 20 ) {
    print $color_iterator->next, "\n";
}

Как случайным образом перемешать массив?

Если у вас установлена Perl 5.8.0 или более поздняя версия, или если у вас установлен Scalar-List-Utils 1.03 или более поздняя версия, вы можете сказать:

use List::Util 'shuffle';

@shuffled = shuffle(@list);

Если нет, вы можете использовать перемешивание Фишера–Йейтса.

sub fisher_yates_shuffle {
    my $deck = shift;  # $deck is a reference to an array
    return unless @$deck; # must not be empty!

    my $i = @$deck;
    while (--$i) {
        my $j = int rand ($i+1);
        @$deck[$i,$j] = @$deck[$j,$i];
    }
}

# shuffle my mpeg collection
#
my @mpeg = <audio/*/*.mp3>;
fisher_yates_shuffle( \@mpeg );    # randomize @mpeg in place
print @mpeg;

Обратите внимание, что приведенная выше реализация перемешивает массив на месте, в отличие от List::Util::shuffle(), которая принимает список и возвращает новый перемешанный список.

Вероятно, вы видели алгоритмы перемешивания, работающие с помощью splice, случайно выбирающие другой элемент для обмена с текущим элементом

srand;
@new = ();
@old = 1 .. 10;  # just a demo
while (@old) {
    push(@new, splice(@old, rand @old, 1));
}

Это плохо, потому что splice уже имеет сложность O(N), а поскольку вы делаете это N раз, вы только что изобрели алгоритм квадратичной сложности; то есть O(N**2). Это не масштабируется, хотя Perl настолько эффективен, что вы, вероятно, не заметите этого, пока у вас не будет довольно больших массивов.

Как обработать/изменить каждый элемент массива?

Используйте for/foreach:

for (@lines) {
    s/foo/bar/;    # change that word
    tr/XZ/ZX/;    # swap those letters
}

Вот еще один пример; давайте вычислим объёмы сфер:

my @volumes = @radii;
for (@volumes) {   # @volumes has changed parts
    $_ **= 3;
    $_ *= (4/3) * 3.14159;  # this will be constant folded
}

что также можно сделать с помощью map(), который предназначен для преобразования одного списка в другой:

my @volumes = map {$_ ** 3 * (4/3) * 3.14159} @radii;

Если вы хотите сделать то же самое, чтобы изменить значения хеша, вы можете использовать функцию values. Начиная с Perl 5.6 значения не копируются, поэтому, если вы изменяете $orbit (в данном случае), вы изменяете значение.

for my $orbit ( values %orbits ) {
    ($orbit **= 3) *= (4/3) * 3.14159;
}

До версии Perl 5.6 values возвращала копии значений, поэтому более старые коды Perl часто содержат конструкции, такие как @orbits{keys %orbits} вместо values %orbits, где хеш должен быть изменён.

Как выбрать случайный элемент из массива?

Используйте функцию rand() (см. "rand" в perlfunc):

my $index   = rand @array;
my $element = $array[$index];

Или, просто:

my $element = $array[ rand @array ];

Как переставить N элементов списка?

Используйте модуль List::Permutor на CPAN. Если список фактически является массивом, попробуйте модуль Algorithm::Permute (также на CPAN). Он написан на XS и очень эффективен:

use Algorithm::Permute;

my @array = 'a'..'d';
my $p_iterator = Algorithm::Permute->new ( \@array );

while (my @perm = $p_iterator->next) {
   print "next permutation: (@perm)\n";
}

Для ещё более быстрого выполнения можно сделать:

use Algorithm::Permute;

my @array = 'a'..'d';

Algorithm::Permute::permute {
    print "next permutation: (@array)\n";
} @array;

Вот небольшая программа, которая генерирует все перестановки всех слов на каждой строке входных данных. Алгоритм, воплощённый в функции permute(), описан в томе 4 (ещё не опубликован) книги Кнута «Искусство программирования» и будет работать с любым списком:

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

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

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

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

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

use Algorithm::Loops qw(NextPermuteNum);

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

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

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

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

По умолчанию функция сортировки — 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 в 1 для бита 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 или более поздних для получения более подробной информации.

Данные: Хеши (Ассоциативные массивы)

Как обработать весь хеш?

(представлено брайаном д фоем)

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

Чтобы пройти по всем ключам, используйте функцию 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 для получения дополнительной информации.

Как объединить два хеша?

(представлено брайаном д фоем)

Перед тем, как решить объединить два хеша, вы должны решить, что делать, если оба хеша содержат одинаковые ключи и хотите сохранить исходные хеши такими, какими они были.

Если вы хотите сохранить исходные хеши, скопируйте один хеш (%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;

Что произойдёт, если я добавлю или удалю ключи из хеша во время итерирования по нему?

(представлено брайаном д фоем)

Простой ответ: «Не делайте этого!»

Если вы итерируетесь по хешу с помощью 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;
}

Как узнать, сколько записей в хеше?

(представлено брайаном д фоем)

Это очень похоже на «Как обработать весь хеш?», также в 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().

Как отсортировать хеш (по значению вместо ключа, при необходимости)?

(представлено брайаном д фоем)

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

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

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

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

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

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

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

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

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

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

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

Как всегда поддерживать отсортированный хеш?

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

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

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

Картинки помогают... Вот таблица %hash:

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

И эти условия выполняются

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

Если теперь вы скажете

undef $hash{'a'}

Ваша таблица теперь выглядит следующим образом:

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

И эти условия теперь выполняются; изменения в верхнем регистре:

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

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

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

delete $hash{'a'}

Ваша таблица теперь выглядит следующим образом:

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

И эти условия теперь выполняются; изменения в верхнем регистре:

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

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

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

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

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

(представлено brian d foy)

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

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

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

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

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

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

Или более кратко:

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

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

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

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

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

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

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

use Tie::IxHash;

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

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

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

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

(представлено brian d foy)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(представлено brian d foy и Ben Morrow)

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

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

Если у вас Perl 5.10 или более поздняя версия, и вам нужно просто сохранить значение по ссылке для последующего поиска, вы можете использовать модуль Hash::Util::Fieldhash из ядра. Он также будет переименовывать ключи, если вы используете несколько потоков (что приводит к перераспределению всех переменных по новым адресам, изменяя их строковое представление), и удалять записи, когда переменная, на которую ссылаются, выходит из области видимости.

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

Как проверить, существует ли ключ в многоуровневом хеше?

(представлено brian d foy)

Ключ к этой проблеме — избежание случайной автовивификации. Если вы хотите проверить ключ на глубину три уровня, вы, возможно, наивно попробуете это:

my %hash;
if( exists $hash{key1}{key2}{key3} ) {
    ...;
}

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

%hash = (
          'key1' => {
                      'key2' => {}
                    }
        );

Это автовивификация. Вы можете обойти это несколькими способами. Самый простой способ — просто отключить его. Лексический autovivification-pragma доступен в CPAN. Теперь вы не добавляете в хеш:

{
    no autovivification;
    my %hash;
    if( exists $hash{key1}{key2}{key3} ) {
        ...;
    }
}

Модуль Data::Diver на CPAN также может это сделать. Его подпрограмма Dive может не только сказать, существуют ли ключи, но и получить значение:

use Data::Diver qw(Dive);

my @exists = Dive( \%hash, qw(key1 key2 key3) );
if(  ! @exists  ) {
    ...; # keys do not exist
}
elsif(  ! defined $exists[0]  ) {
    ...; # keys exist but value is undef
}

Вы также можете легко сделать это сами, проверяя каждый уровень хеша, прежде чем переходить к следующему уровню. Это в основном то, что делает Data::Diver за вас:

if( check_hash( \%hash, qw(key1 key2 key3) ) ) {
    ...;
}

sub check_hash {
   my( $hash, @keys ) = @_;

   return unless @keys;

   foreach my $key ( @keys ) {
       return unless eval { exists $hash->{$key} };
       $hash = $hash->{$key};
    }

   return 1;
}

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

Начиная с версии 5.8.0, хеши могут быть ограничены фиксированным числом заданных ключей. Методы создания и работы с ограниченными хешами экспортируются модулем Hash::Util.

Данные: Разное

Как правильно обрабатывать двоичные данные?

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

Также см. "binmode" в perlfunc или perlopentut.

Если вы обеспокоены 8-битовыми текстовыми данными, см. perllocale. Если вы хотите работать с многобайтовыми символами, однако, существуют некоторые нюансы. См. раздел по регулярным выражениям.

Как определить, является ли скаляр числом/целым/целым/вещественным?

Предполагая, что вас не интересуют обозначения IEEE, такие как "NaN" или "Бесконечность", вы, вероятно, просто хотите использовать регулярное выражение (см. также perlretut и perlre):

use 5.010;

if ( /\D/ )
    { say "\thas nondigits"; }
if ( /^\d+\z/ )
    { say "\tis a whole number"; }
if ( /^-?\d+\z/ )
    { say "\tis an integer"; }
if ( /^[+-]?\d+\z/ )
    { say "\tis a +/- integer"; }
if ( /^-?(?:\d+\.?|\.\d)\d*\z/ )
    { say "\tis a real number"; }
if ( /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?\z/i )
    { say "\tis a C float" }

Также существуют некоторые часто используемые модули для этой задачи. Scalar::Util (распространяется с 5.8) предоставляет доступ к внутренней функции Perl looks_like_number для определения того, выглядит ли переменная как число. Data::Types экспортирует функции, которые валидируют типы данных, используя вышеупомянутые и другие регулярные выражения. В-третьих, существует Regexp::Common, в котором есть регулярные выражения для сопоставления различных типов чисел. Эти три модуля доступны из CPAN.

Если вы работаете на системе POSIX, Perl поддерживает функцию POSIX::strtod для преобразования строк в числа с плавающей точкой (а также POSIX::strtol для целых чисел). Ее семантика несколько сложна, поэтому вот обёртка getnum-функции для более удобного доступа. Эта функция принимает строку и возвращает найденное число или undef для входных данных, которые не являются числами с плавающей точкой C. Функция is_numeric — это интерфейс к getnum, если вы просто хотите сказать: "Это число с плавающей точкой?".

sub getnum {
    use POSIX qw(strtod);
    my $str = shift;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//;
    $! = 0;
    my($num, $unparsed) = strtod($str);
    if (($str eq '') || ($unparsed != 0) || $!) {
            return undef;
    }
    else {
        return $num;
    }
}

sub is_numeric { defined getnum($_[0]) }

Или вы можете изучить модуль String::Scanf на CPAN.

Как сохранить постоянные данные между вызовами программы?

Для некоторых конкретных приложений можно использовать один из модулей DBM. См. AnyDBM_File. Более универсально, вы должны обратиться к модулям FreezeThaw или Storable с CPAN. Начиная с Perl 5.8, Storable входит в стандартную дистрибуцию. Вот один пример с использованием функций Storable's store и retrieve:

use Storable;
store(\%hash, "filename");

# later on...
$href = retrieve("filename");        # by ref
%hash = %{ retrieve("filename") };   # direct to hash

Как распечатать или скопировать рекурсивную структуру данных?

Модуль Data::Dumper на CPAN (или выпуск 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–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.36.0/perlfaq4

Spec-Zone.ru

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