Spec-Zone.ru › Perl 5.32

perlfaq4

СОДЕРЖАНИЕ

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

НАЗВАНИЕ

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

ВЕРСИЯ

версия 5.20200523

ОПИСАНИЕ

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

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

Почему оператор & работает не так, как я ожидаю?

Поведение бинарных операторов зависит от того, используются ли они с числами или строками. Операторы обрабатывают строку как последовательность битов и работают с ней (строка "3" — это битовая последовательность 00110011). Операторы работают с двоичной формой числа (число 3 обрабатывается как битовая последовательность 00000011).

Таким образом, запись 11 & 3 выполняет операцию «И» над числами (результат 3). Запись "11" & "3" выполняет операцию «И» над строками (результат "1").

Большинство проблем с & и | возникают потому, что программист считает, что у него число, но на самом деле это строка, или наоборот. Чтобы этого избежать, явно преобразуйте аргументы в строки (используя "" или qq()) или явно преобразуйте их в числа (используя 0+$arg). Остальные проблемы возникают, потому что программист пишет:

if ("\020\020" & "\101\101") {
    # ...
}

но строка, состоящая из двух нулевых байтов (результат "\020\020" & "\101\101" ), не является ложным значением в Perl. Вам нужно:

if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
    # ...
}

Как умножить матрицы?

Используйте модули Math::Matrix или Math::MatrixReal (доступны в CPAN) или расширение PDL (также доступно в CPAN).

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

Чтобы вызвать функцию для каждого элемента в массиве и собрать результаты, используйте:

my @results = map { my_func($_) } @array;

Например:

my @triple = map { 3 * $_ } @single;

Чтобы вызвать функцию для каждого элемента массива, но игнорировать результаты:

foreach my $iterator (@array) {
    some_func($iterator);
}

Чтобы вызвать функцию для каждого целого числа в (малом) диапазоне, вы можете использовать:

my @results = map { some_func($_) } (5 .. 25);

но вы должны понимать, что в этом виде оператор .. создаёт список всех целых чисел в диапазоне, что может занимать много памяти для больших диапазонов. Однако проблема не возникает при использовании .. внутри цикла for, потому что в этом случае оператор диапазона оптимизирован для итерации по диапазону, без создания всего списка. Таким образом

my @results = ();
for my $i (5 .. 500_005) {
    push(@results, some_func($i));
}

или даже

push(@results, some_func($_)) for 5 .. 500_005;

не создаст промежуточного списка из 500 000 целых чисел.

Как вывести римские цифры?

Получите модуль http://www.cpan.org/modules/by-module/Roman.

Почему мои случайные числа не случайны?

Если вы используете версию Perl до 5.004, вы должны вызвать srand один раз в начале вашей программы для инициализации генератора случайных чисел.

BEGIN { srand() if $] < 5.004 }

Версии 5.004 и выше автоматически вызывают srand в начале. Не вызывайте srand более одного раза — вы сделаете числа менее случайными, а не более.

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

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

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

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

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

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

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

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

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

Данные: Даты

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

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

my $day_of_year = (localtime)[7];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как найти дату вчерашнего дня?

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

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

use DateTime;

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

print "Yesterday was $yesterday\n";

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

use Date::Calc qw( Today_and_Now Add_Delta_DHMS );

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

print "@date_time\n";

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

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

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

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

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

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

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

Есть ли у Perl проблемы с годами 2000 или 2038? Является ли Perl совместимым с Y2K?

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

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

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

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

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

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

Однако вы все равно не сможете отслеживать распад протонов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как найти соответствие/вложение чего-либо?

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

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

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

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

my $reversed = reverse $string;

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

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

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

Или можно просто использовать модуль Text::Tabs (входит в стандартную поставку Perl).

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

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

Используйте Text::Wrap (входит в стандартную поставку Perl):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Damian Conway's Text::Autoformat выполняет все необходимые действия за вас.

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

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

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

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

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

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

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

А теперь, что если вам не нужно делать заглавными "и"? Просто используйте Text::Autoformat и переходите к следующей задаче. :)

Как разделить строку, ограниченную [символом], за исключением случаев, когда [символ] находится внутри?

Несколько модулей могут обрабатывать этот вид разбора — Text::Balanced, Text::CSV, Text::CSV_XS и Text::ParseWords, среди прочих.

Рассмотрим пример со строкой, разделенной запятыми, которую нужно разбить на отдельные поля. Вы не можете использовать split(/,/), поскольку не следует разделять, если запятая находится внутри кавычек. Например, возьмем строку данных такого вида:

SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"

Из-за ограничения кавычек эта проблема довольно сложная. К счастью, у нас есть Джеффри Фридл, автор книги Мастерство регулярных выражений, который поможет нам с этим. Он предлагает (предполагая, что ваша строка содержится в $text):

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

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

Альтернативно, модуль Text::ParseWords (входит в стандартную поставку Perl) позволяет сделать следующее:

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

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

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

(внесён brian d foy)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Выравнивание слева и справа любым символом, непосредственное изменение $text:

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

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

(внесён brian d foy)

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

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

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

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

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

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

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

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

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

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

(внесён brian d foy)

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

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

(внесён brian d foy)

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

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

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

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

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

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

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

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

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

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

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

print $string;

В чём проблема с постоянным цитированием "$переменные"?

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

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

print "$var";       # BAD
my $new = "$old";       # BAD
somefunc("$var");    # BAD

Вам придётся столкнуться с проблемами. Это (в 99,8% случаев) должно быть проще и яснее:

print $var;
my $new = $old;
somefunc($var);

Иначе, помимо замедления, вы сломаете код, когда то, что находится в скаляре, на самом деле является ни строкой, ни числом, а ссылкой:

func(\@array);
sub func {
    my $aref = shift;
    my $oref = "$aref";  # WRONG
}

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

Строковое представление также разрушает массивы.

my @lines = `command`;
print "@lines";     # WRONG - extra blanks
print @lines;       # right

Почему мои документы <<HERE не работают?

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

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

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

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

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

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

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

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

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

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

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

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

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

Либо pragma use warnings, либо флаг -w предупредит вас, когда вы используете срез массива с одиночным индексом.

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

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

Используйте хэш. Когда вы видите слова «уникальные» или «дублирующиеся», думайте о «ключах хэша».

Если порядок элементов не важен, вы можете просто создать хэш, а затем извлечь ключи. Неважно, как вы создаёте этот хэш: важно, что вы используете keys для получения уникальных элементов.

my %hash   = map { $_, 1 } @array;
# or a hash slice: @hash{ @array } = ();
# or a foreach: $hash{$_} = 1 foreach ( @array );

my @unique = keys %hash;

Если вы хотите использовать модуль, попробуйте функцию uniq из List::MoreUtils. В контексте списка она возвращает уникальные элементы, сохраняя их порядок в списке. В скалярном контексте она возвращает количество уникальных элементов.

use List::MoreUtils qw(uniq);

my @unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 1,2,3,4,5,6,7
my $unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 7

Вы также можете пройтись по каждому элементу и пропустить те, которые вы уже видели. Используйте хэш для отслеживания. В первый раз, когда цикл видит элемент, у этого элемента нет ключа в %Seen. Оператор next создаёт ключ и немедленно использует его значение, которое является undef, поэтому цикл переходит к push и увеличивает значение для этого ключа. В следующий раз, когда цикл видит тот же элемент, его ключ существует в хэше и значение для этого ключа истинно (так как оно не равно 0 или undef), поэтому следующая итерация пропускается, и цикл переходит к следующему элементу.

my @unique = ();
my %seen   = ();

foreach my $elem ( @array ) {
    next if $seen{ $elem }++;
    push @unique, $elem;
}

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

my %seen = ();
my @unique = grep { ! $seen{ $_ }++ } @array;

Как проверить, содержится ли определённый элемент в списке или массиве?

(части этого ответа внесли Anno Siegel и brian d foy)

Слово «в» указывает на то, что, вероятно, вы должны были использовать хэш, а не список или массив, для хранения данных. Хэши предназначены для быстрого и эффективного ответа на этот вопрос. Массивы — нет.

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

my @blues = qw/azure cerulean teal turquoise lapis-lazuli/;
my %is_blue = ();
for (@blues) { $is_blue{$_} = 1 }

Теперь вы можете проверить, $is_blue{$some_color}. Возможно, было бы разумно сразу поместить синие значения в хэш.

Если значения — это небольшие целые числа, вы можете использовать простой индексированный массив. Этот тип массива займёт меньше места:

my @primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
my @is_tiny_prime = ();
for (@primes) { $is_tiny_prime[$_] = 1 }
# or simply  @istiny_prime[@primes] = (1) x @primes;

Теперь вы проверяете $is_tiny_prime[$some_number].

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

my @articles = ( 1..10, 150..2000, 2017 );
undef $read;
for (@articles) { vec($read,$_,1) = 1 }

Теперь проверьте, vec($read,$n,1) истина для некоторого $n.

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

Если вы проверяете только один раз, стандартный модуль List::Util экспортирует функцию any для этой цели. Она работает, останавливаясь, как только находит элемент. Она написана на C для скорости, и её 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). <=>, используемая выше, — оператор числового сравнения.

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

my @idx;
for (@data) {
    my $item;
    ($item) = /\d+\s*(\S+)/;
    push @idx, uc($item);
}
my @sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ];

что также можно записать так, используя трюк, который стал известен как преобразование Шварца:

my @sorted = map  { $_->[0] }
    sort { $a->[1] cmp $b->[1] }
    map  { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data;

Если вам нужно сортировать по нескольким полям, следующая парадигма полезна.

my @sorted = sort {
    field1($a) <=> field1($b) ||
    field2($a) cmp field2($b) ||
    field3($a) cmp field3($b)
} @data;

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

См. статью «sort» в коллекции «Гораздо больше, чем вы когда-либо хотели знать» в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz для получения дополнительной информации об этом подходе.

См. также вопрос в конце perlfaq4 о сортировке хэшей.

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

Используйте pack() и unpack(), или же vec() и побитовые операции.

Например, вам не нужно хранить отдельные биты в массиве (это означало бы, что вы тратите много места). Чтобы преобразовать массив битов в строку, используйте vec() для установки нужных битов. Это устанавливает $vec так, чтобы бит N был установлен только в том случае, если $ints[N] был установлен:

my @ints = (...); # array of bits, e.g. ( 1, 0, 0, 1, 1, 0 ... )
my $vec = '';
foreach( 0 .. $#ints ) {
    vec($vec,$_,1) = 1 if $ints[$_];
}

Строка $vec занимает только столько битов, сколько ей нужно. Например, если у вас было 16 записей в @ints, $vec нужно всего два байта для их хранения (без учета накладных расходов на скалярные переменные).

Вот как, имея вектор в $vec, вы можете получить эти биты в массив @ints:

sub bitvec_to_list {
    my $vec = shift;
    my @ints;
    # Find null-byte density then select best algorithm
    if ($vec =~ tr/\0// / length $vec > 0.95) {
        use integer;
        my $i;

        # This method is faster with mostly null-bytes
        while($vec =~ /[^\0]/g ) {
            $i = -9 + 8 * pos $vec;
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
            push @ints, $i if vec($vec, ++$i, 1);
        }
    }
    else {
        # This method is a fast general algorithm
        use integer;
        my $bits = unpack "b*", $vec;
        push @ints, 0 if $bits =~ s/^(\d)// && $1;
        push @ints, pos $bits while($bits =~ /1/g);
    }

    return \@ints;
}

Этот метод становится быстрее, чем больше разряженный битовый вектор. (Вежливость Тима Бэнса и Винфрида Кенига.)

Вы можете сделать цикл while намного короче, используя это предложение от Бенджамина Голдберга:

while($vec =~ /[^\0]+/g ) {
    push @ints, grep vec($vec, $_, 1), $-[0] * 8 .. $+[0] * 8;
}

Или используйте модуль CPAN Bit::Vector:

my $vector = Bit::Vector->new($num_of_bits);
$vector->Index_List_Store(@ints);
my @ints = $vector->Index_List_Read();

Bit::Vector предоставляет эффективные методы для битовых векторов, наборов малых целых чисел и математических операций с «большими целыми числами».

Вот более подробная иллюстрация с использованием vec():

# vec demo
my $vector = "\xff\x0f\xef\xfe";
print "Ilya's string \\xff\\x0f\\xef\\xfe represents the number ",
unpack("N", $vector), "\n";
my $is_set = vec($vector, 23, 1);
print "Its 23rd bit is ", $is_set ? "set" : "clear", ".\n";
pvec($vector);

set_vec(1,1,1);
set_vec(3,1,1);
set_vec(23,1,1);

set_vec(3,1,3);
set_vec(3,2,3);
set_vec(3,4,3);
set_vec(3,4,7);
set_vec(3,8,3);
set_vec(3,8,7);

set_vec(0,32,17);
set_vec(1,32,17);

sub set_vec {
    my ($offset, $width, $value) = @_;
    my $vector = '';
    vec($vector, $offset, $width) = $value;
    print "offset=$offset width=$width value=$value\n";
    pvec($vector);
}

sub pvec {
    my $vector = shift;
    my $bits = unpack("b*", $vector);
    my $i = 0;
    my $BASE = 8;

    print "vector length in bytes: ", length($vector), "\n";
    @bytes = unpack("A8" x length($vector), $bits);
    print "bits are: @bytes\n\n";
}

Почему defined() возвращает true для пустых массивов и хэшей?

Короче говоря, вы, вероятно, должны использовать defined только для скаляров или функций, а не для агрегатов (массивов и хэшей). См. "defined" в perlfunc в версии Perl 5.004 или более поздней для получения дополнительной информации.

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

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

(внесен brian d foy)

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

Чтобы пройти по всем ключам, используйте функцию keys. Это извлекает все ключи хэша и возвращает их вам как список. Затем вы можете получить значение по конкретному ключу, который обрабатываете:

foreach my $key ( keys %hash ) {
    my $value = $hash{$key}
    ...
}

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

foreach my $key ( sort keys %hash ) {
    my $value = $hash{$key}
    ...
}

Или, возможно, вы хотите обработать только некоторые элементы. Если вы хотите обработать только ключи, начинающиеся с text:, вы можете выбрать только эти, используя grep:

foreach my $key ( grep /^text:/, keys %hash ) {
    my $value = $hash{$key}
    ...
}

Если хэш очень большой, вам может не захотеться создавать длинный список ключей. Чтобы сохранить некоторое количество памяти, вы можете получить одну пару ключ-значение за раз, используя each(), которая возвращает пару, которую вы еще не видели:

while( my( $key, $value ) = each( %hash ) ) {
    ...
}

Оператор each возвращает пары в, по-видимому, случайном порядке, поэтому, если порядок для вас важен, вам придется использовать метод keys.

Оператор each() может быть немного сложным. Вы не можете добавлять или удалять ключи хэша во время его использования, не рискуя пропустить или повторно обработать некоторые пары после того, как Perl пересортирует все элементы внутри. Кроме того, у хэша есть только один итератор, поэтому если вы смешиваете keys, values, или each для одного и того же хэша, вы рискуете сбросить итератор и испортить свою обработку. См. запись each в perlfunc для получения дополнительной информации.

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

(внесен brian d foy)

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

Если вы хотите сохранить исходные хэши, скопируйте один хэш (%hash1) в новый хэш (%hash2), а затем добавьте ключи из другого хэша (%hash2 в новый хэш. Проверка того, что ключ уже существует в %new_hash, дает вам возможность решить, что делать с дубликатами:

my %new_hash = %hash1; # make a copy; leave %hash1 alone

foreach my $key2 ( keys %hash2 ) {
    if( exists $new_hash{$key2} ) {
        warn "Key [$key2] is in both hashes!";
        # handle the duplicate (perhaps only warning)
        ...
        next;
    }
    else {
        $new_hash{$key2} = $hash2{$key2};
    }
}

Если вам не нужно создавать новый хэш, вы все равно можете использовать эту технику циклирования; просто замените %new_hash на %hash1.

foreach my $key2 ( keys %hash2 ) {
    if( exists $hash1{$key2} ) {
        warn "Key [$key2] is in both hashes!";
        # handle the duplicate (perhaps only warning)
        ...
        next;
    }
    else {
        $hash1{$key2} = $hash2{$key2};
    }
  }

Если вам все равно, что один хэш перезаписывает ключи и значения из другого, вы можете просто использовать срез хэша, чтобы добавить один хэш к другому. В этом случае значения из %hash2 заменяют значения из %hash1, когда у них есть общие ключи:

@hash1{ keys %hash2 } = values %hash2;

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

(внесен brian d foy)

Легкий ответ: «Не делайте этого!»

Если вы итерируете по хэшу с помощью each(), вы можете удалить ключ, который был возвращен последним, не беспокоясь об этом. Если вы удаляете или добавляете другие ключи, итератор может пропустить или удвоить их, так как Perl может переупорядочить таблицу хэшей. См. запись each() в perlfunc.

Как найти элемент хэша по значению?

Создайте обратный хэш:

my %by_value = reverse %by_key;
my $key = $by_value{$value};

Это не очень эффективно. Было бы эффективнее с точки зрения использования памяти использовать:

while (my ($key, $value) = each %by_key) {
    $by_value{$value} = $key;
}

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

while (my ($key, $value) = each %by_key) {
     push @{$key_list_by_value{$value}}, $key;
}

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

(внесен brian d foy)

Это очень похоже на «Как обработать весь хэш?», также в perlfaq4, но в простых случаях немного проще.

Вы можете использовать встроенную функцию keys() в скалярном контексте, чтобы узнать, сколько записей у вас в хэше:

my $key_count = keys %hash; # must be scalar context!

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

my $defined_value_count = grep { defined } values %hash;

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

my $vowel_count = grep { /[aeiou]/ } keys %hash;
END_OF_DOCUMENT_MARKER

Функция grep в скалярном контексте возвращает счётчик. Если вам нужна коллекция совпадающих элементов, используйте её в списочном контексте:

my @defined_values = grep { defined } values %hash;

Функция keys() также сбрасывает итератор, что может привести к странным результатам, если вы её используете между вызовами других операторов с хешем, таких как each().

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

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

Чтобы отсортировать хеш, начните с ключей. В этом примере мы передаём список ключей в функцию сортировки, которая затем сравнивает их по алфавиту 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, как описано в "In Memory Databases" в DB_File. Модуль Tie::IxHash с CPAN также может быть полезным. Хотя это сохраняет сортировку хеша, вам может не понравиться замедление, вызванное интерфейсом привязки. Вы уверены, что это нужно? :)

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

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

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

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

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

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

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

undef $hash{'a'}

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

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

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

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

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

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

delete $hash{'a'}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

use Tie::IxHash;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

use Data::Diver qw(Dive);

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

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

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

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

   return unless @keys;

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

   return 1;
}

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

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

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

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

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

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

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

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

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

use 5.010;

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

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

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

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

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

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

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

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

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

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

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

Модуль Data::Dumper на CPAN (или версия 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 для кода.

АВТОР И АВТОРСКИЕ ПРАВА

Copyright (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.

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

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

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.32.0/perlfaq4

Spec-Zone.ru

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