Saturday, January 21, 2012

perlfaq4 — работа с данными http://beshenov.ru/perlfaq/4.html

http://beshenov.ru/perlfaq/4.html

perlfaq4 — работа с данными

Этот раздел FAQ отвечает на вопросы по работе с числами, датами, строками, массивами, хешами и прочими видами данных.

Числа

Почему вместо ожидаемых чисел вроде 19.95 я получаю длинные дроби вроде 19.9499999999999?
Почему int() не работает?
Почему восьмеричные данные обрабатываются неправильно?
Есть ли в Perl функция round()? А ceil() и floor()? А тригонометрические?
Как делать преобразования между записями в разных системах счисления?
Почему & работает не так, как ожидается?
Как перемножить матрицы?
Как применить операцию к целочисленной последовательности?
Как выводить римские цифры?
Почему мои случайные числа не случайны?
Как получить случайное число из определенного диапазона?

Даты

Как узнать номер дня или недели в году?
Как определить текущий век или тысячелетие?
Как сравнить две даты и найти разницу?
Как преобразовать строку в число секунд с начала эпохи?
Как определить юлианский день?
Как определить вчерашнюю дату?
Есть ли у Perl проблема 2000 года? Он совместим с 2000 годом?

Строки

Как проверить ввод?
Как избавиться от экранирования в строке?
Как удалить парные символы?
Как раскрыть вызовы функций в строке?
Как найти вложенные элементы?
Как обратить строку?
Как раскрыть в строке символы табуляции?
Как переформатировать абзац?
Как извлечь или поменять N символов строки?
Как заменить N-е вхождение чего-либо?
Как подсчитать количество вхождений подстроки в строку?
Как записать все слова в строке с заглавной буквы?
Как разделить строку по некоторому символу, исключая его вхождения внутри полей?
Как удалить пробельные символы в начале или в конце строки?
Как заполнить строку пробелами до определенной длины, либо число — нулями?
Как извлечь из строки определенные столбцы?
Как получить значение soundex для строки?
Как раскрыть в строке переменные?
Почему не стоит всегда заключать переменные в двойные кавычки?
Почему не работает текст в конструкции <<HERE?

Массивы

В чём разница между списками и массивами?
В чём разница между $array[1] и @array[1]?
Как удалить из списка или массива повторы?
Как узнать, содержится ли в списке или массиве определенный элемент?
Как найти симметрическую разность или пересечение двух массивов?
Как проверить, эквивалентны ли два массива или два хеша?
Как найти первый элемент массива, соответствующий определенному условию?
Как работать со связанными списками?
Как создать циклический список?
Как перемешать элементы массива случайным образом?
Как обработать каждый элемент массива?
Как выбрать случайный элемент массива?
Как переставить N элементов списка?
Как отсортировать массив?
Как работать с битовыми массивами?
Почему defined() возвращает true для пустых массивов и хешей?

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

Как обработать весь хеш?
Как объединить два хеша?
Что произойдет, если добавить или удалить ключи хеша во время прохода по нему?
Как найти элемент хеша по значению?
Как узнать, сколько в хеше записей?
Как отсортировать хеш (по ключам, по значениям)?
Как всегда держать хеш отсортированным?
В чём разница между delete и undef для хешей?
Почему связанные хеши не различают defined и exists?
Как на половине пути сбросить операцию each?
Как выбрать из двух хешей уникальные ключи?
Как сохранить в DBM-файле многомерный массив?
Как сделать, чтобы хеш запоминал порядок размещения элементов?
Почему передача процедуре неопределенного элемента хеша создает его?
Как сделать аналог структуры C, класса C++, хеш или массив хешей или массивов?
Как в качестве ключей хеша использовать ссылки?

Разное

Как правильно работать с бинарными данными?
Как узнать, является ли скаляр числом; целое ли оно, либо с плавающей точкой?
Как сохранять данные между вызовами программы?
Как вывести или копировать рекурсивную структуру данных?
Как определить методы для всех классов/объектов?
Как проверить контрольную сумму кредитной карты?
Как упаковать массивы или числа с плавающей точкой для кода XS?

Числа

Почему вместо ожидаемых чисел вроде 19.95 я получаю длинные дроби вроде 19.9499999999999?

Внутреннее представление чисел с плавающей точкой в вашем компьютере — двоичное. Цифровые компьютеры не могут точно хранить все числа — используются представления по степеням двойки. Некоторые вещественные числа в процессе теряют точность. Эта проблема с представлением чисел компьютерами затрагивает все языки программирования, не только Perl.
Все ужасающие подробности представления и преобразования чисел есть в perlnumber.
Для ограничения числа десятичных знаков в числах можно использовать функции printf и sprintf. Для подробностей см. «Floating Point Arithmetic».
printf "%.2f", 10/3;  my $number = sprintf "%.2f", 10/3;

Почему int() не работает?

Скорее всего, int() работает прекрасно. Просто числа не совсем такие, как вы думаете.
Во-первых, см. предыдущий вопрос.
Например, это
print int(0.6/0.2-2), "\n";
на большинстве компьютеров напечатает 0, а не 1, потому что даже такие простые числа как 0.6 и 0.2 не имеют точного представления числами с плавающей точкой. То, что выше, по вашему мнению, равно трем, на деле будет чем-то в духе 2.9999999999999995559.

Почему восьмеричные данные обрабатываются неправильно?

(отвечает Брайан Д. Фой)
Скорее всего, вы пытаетесь преобразовать строку в число, а Perl воспринимает строки только как десятичные числа. Когда Perl преобразует строку в число, он игнорирует начальные пробелы и нули и полагает, что оставшиеся цифры — десятичные:
my $string = '0644';  print $string + 0;  # печатает 644  print $string + 44; # печатает 688 — конечно, не восьмеричное число!
Эта проблема обычно связана с одной из встроенных функций Perl, которая совпадает по имени с командой Unix, использующей в командной строке в качестве аргументов восьмеричные числа. В следующем примереchmod из командной строки знает, что первый аргумент восьмеричный, потому что на этом основан ее интерфейс:
$ chmod 644 file
Если вы хотите использовать те же самые цифры (664) в Perl, то надо будет указать интерпретатору, что они восьмеричные, добавив перед ними 0, либо при помощи oct:
chmod(     0644, $file);   # правильно, в начале стоит 0 chmod( oct(644), $file );  # тоже правильно
Проблема возникает, если вы берете числа из строковых данных, например, из аргументов командной строки в @ARGV:
chmod( $ARGV[0],      $file);   # неправильно, даже если "0644"  chmod( oct($ARGV[0]), $file );  # правильно, строка обрабатывается как восьмеричная запись
Всегда можно проверить используемое значение, если вывести его в восьмеричной записи и посмотреть, совпадает ли вывод с ожидаемым. Напечатайте его в восьмеричном и десятичном виде:
printf "0%o %d", $number, $number;

Есть ли в Perl функция round()? А ceil() и floor()? А тригонометрические?

Обратите внимание, что int() просто отбрасывает дробную часть (округляет к нулю). Для округления до определенной цифры обычно проще всего использовать sprintf() или printf().
printf("%.3f", 3.1415926535);   # печатает 3.142
Модуль POSIX (часть стандартной поставки Perl) реализует ceil()floor() и ряд других математических функций, в том числе и тригонометрические.
use POSIX; $ceil   = ceil(3.5);   # 4 $floor  = floor(3.5);  # 3
В Perl версии 5.000—5.003 тригонометрия содержалась в модуле Math::Complex. Начиная с 5.004, тригонометрические функции находятся в Math::Trig (часть стандартной поставки Perl). Внутри используется модуль Math::Complex, и значения некоторых функций могут не быть вещественными — например, арксинус 2.
В финансовых приложениях округление может иметь большое значение, поэтому нужно аккуратно выбирать метод округления. В подобных ситуациях имеет смысл не полагаться на системные методы округления, а реализовать необходимую функцию самостоятельно.
Чтобы убедиться в этом, рассмотрим проблему округления на середине интервала:
for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}  0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.0 1.0
Не грешите на Perl — то же самое происходит в C, так диктует IEEE. Целые числа Perl с абсолютными значениями до 2**31 (на 32-битных машинах) ведут себя практически как целые числа в математике. Другие числа — не обязательно.

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

Как и всегда в Perl, есть несколько способов. Ниже приводятся некоторые примеры и подходы для распространенных преобразований между записями чисел. Это не исчерпывающее изложение, а только краткий обзор.
В некоторых последующих примерах из perlfaq4 используется модуль Bit::Vector из CPAN. ИногдаBit::Vector стоит предпочесть встроенным функциям Perl, потому что модуль работает с числами любого размера, оптимизирован по скорости для некоторых операций, а также использует привычную (по крайней мере, для некоторых программистов) запись.

Как преобразовать шестнадцатеричную запись в десятичную?

При помощи встроенного в Perl преобразования с префиксом 0x:
$dec = 0xDEADBEEF;
При помощи функции hex:
$dec = hex("DEADBEEF");
При помощи pack:
$dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
При помощи модуля Bit::Vector из CPAN:
use Bit::Vector; $vec = Bit::Vector->new_Hex(32, "DEADBEEF"); $dec = $vec->to_Dec();

Как преобразовать десятичную запись в шестнадцатеричную?

При помощи sprintf:
$hex = sprintf("%X", 3735928559); # верхний регистр (A-F) $hex = sprintf("%x", 3735928559); # нижний регистр (a-f)
При помощи unpack:
$hex = unpack("H*", pack("N", 3735928559));
При помощи Bit::Vector:
use Bit::Vector; $vec = Bit::Vector->new_Dec(32, -559038737); $hex = $vec->to_Hex();
Bit::Vector также поддерживает нечетное число бит:
use Bit::Vector; $vec = Bit::Vector->new_Dec(33, 3735928559); $vec->Resize(32); # убрать начальный 0, если не требуется $hex = $vec->to_Hex();

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

При помощи встроенного в Perl преобразования чисел с нулями в начале:
$dec = 033653337357; # обратите внимание на начальный 0!
При помощи функции oct:
$dec = oct("33653337357");
При помощи Bit::Vector:
use Bit::Vector; $vec = Bit::Vector->new(32); $vec->Chunk_List_Store(3, split(//, reverse "33653337357")); $dec = $vec->to_Dec();

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

При помощи sprintf:
$oct = sprintf("%o", 3735928559);
При помощи Bit::Vector:
use Bit::Vector; $vec = Bit::Vector->new_Dec(32, -559038737); $oct = reverse join('', $vec->Chunk_List_Read(3));

Как преобразовать двоичную запись в десятичную?

Perl 5.6 позволяет напрямую записывать числа при помощи префикса 0b:
$number = 0b10110110;
При помощи oct:
my $input = "10110110"; $decimal = oct( "0b$input" );
При помощи pack и ord:
$decimal = ord(pack('B8', '10110110'));
Для больших строк — при помощи pack и unpack:
$int = unpack("N", pack("B32", substr("0" x 32 . "11110101011011011111011101111", -32))); $dec = sprintf("%d", $int);  # substr() используется, чтобы заполнить строку в 32 символа нулями слева.
При помощи Bit::Vector:
$vec = Bit::Vector->new_Bin(32, "11011110101011011011111011101111"); $dec = $vec->to_Dec();

Как преобразовать десятичную запись в двоичную?

При помощи sprintf (начиная с Perl 5.6):
$bin = sprintf("%b", 3735928559);
При помощи unpack:
$bin = unpack("B*", pack("N", 3735928559));
При помощи Bit::Vector:
use Bit::Vector; $vec = Bit::Vector->new_Dec(32, -559038737); $bin = $vec->to_Bin();
Прочие преобразования (из шестнадцатеричной записи — в восьмеричную, из двоичной — в шестнадцатеричную, и так далее) оставляем читателю в качестве упражнения.

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

Поведение бинарных арифметических операторов зависит от того, применяются ли они к числам или строкам. Операторы воспринимают строки и числа как последовательности битов и работают с ними (строка"3" представляет битовую последовательность 00110011, а число 3 — последовательность 00000011).
Поэтому 11 & 3 применяет операцию «и» к числам (получается 3), а "11" & "3" — к стокам (получается "1").
Большинство проблем с & и | возникает, когда программист думает, что работают с числом, хотя на деле имеется строка. Остальные связаны с кодом вроде такого:
if ("\020\020" & "\101\101") {     # ...     }
Тут строка из двух нулевых байтов (результат "\020\020" & "\101\101") не соотносится с ложным значением в Perl. Нужно сделать так:
if ( ("\020\020" & "\101\101") !~ /[^\000]/) {     # ...     }

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

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

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

Чтобы вызвать функцию для каждого элемента массива, а потом собрать результаты, используйте
@results = map { my_func($_) } @array;
Например,
@triple = map { 3 * $_ } @single;
Чтобы вызвать функцию для каждого элемента массива, не не использовать результаты:
foreach $iterator (@array) {     some_func($iterator);     }
Чтобы вызвать функцию для каждого целого числа из определенного (небольшого) интервала, можно использовать
@results = map { some_func($_) } (5 .. 25);
но следует учитывать, что оператор .. создает массив со всеми целыми числами интервала. Для больших интервалов может потребоваться много памяти. Вместо этого используйте
@results = (); for ($i=5; $i < 500_005; $i++) {     push(@results, some_func($i));     }
Это было исправлено в Perl5.005, где .. в цикле for дает проход по всем числам диапазона без хранения всех элементов в памяти.
for my $i (5 .. 500_005) {     push(@results, some_func($i));     }
— это не создаст список с 500 000 чисел.

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

Используйте модуль Roman.

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

Если вы используете версию Perl до 5.004, то, чтобы инициализировать генератор случайных чисел, требуется вызвать srand один раз при запуске программы:
BEGIN { srand() if $] < 5.004 }
Начиная с 5.004, srand автоматически вызывается в начале программы. Не вызывайте srand больше одного раза — это делает числа менее случайными, а не наоборот.
Компьютеры хороши по части предсказуемости и плохи по части случайности (если не считать вещей, происходящих из-за ошибок в ваших программах :-). Об этом см. статью random в подборке Тома Феникса «Far More Than You Ever Wanted To Know». Джон фон Нейман сказал: «Каждый, кто использует арифметические методы генерирования случайных чисел, безусловно, грешит».
Если нужны более случайные числа, чем те, что дают rand и srand, то вам стоит также посмотреть модульMath::TrulyRandom из CPAN. Он использует неточности в работе системного таймера, чтобы генерировать случайные числа, но это занимает определенное время. Если нужен более качественный генератор случайных чисел, чем тот, что прилагается к операционной системе, посмотрите книгу «Numerical Recipes in C».

Как получить случайное число из определенного диапазона?

Чтобы получить случайное число, заключенное между двумя значениями, можно сгенерировать при помощи встроенной функции rand() случайное число от 0 до 1, а потом отобразить его на требуемый интервал.
rand($x) возвращает число, такое что 0 <= rand($x) < $x. Таким образом, чтобы получить число в диапазоне от x до y, требуется случайное число от 0 до разности y и x.
Так, чтобы получить число от 10 до 15 включительно, вам требуется число от 0 до 5, к которому потом можно прибавить 10.
my $number = 10 + int rand( 15-10+1 ); # ( 10, 11, 12, 13, 14, либо 15 )
Отсюда в общем виде получаем следующую функцию, которая выбирает случайное целое число между двумя целыми границами (включительно) — например, random_int_between(50,120):
sub random_int_between {     my($min, $max) = @_;     # Подразумевается, что два аргумента — целочисленные!     return $min if $min == $max;     ($min, $max) = ($max, $min)  if  $min > $max;     return $min + int rand(1 + $max - $min);     }

Даты

Как узнать номер дня или недели в году?

Функция localtime возвращает номер дня в году. Без аргументов она использует текущее время.
$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 ) );
Модуль 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, который часто называют веком, что неправильно, потому как в большинстве подобных систем это только две первые цифры года, так что на них при определении века или тысячелетия полагаться нельзя.

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

(отвечает Брайан Д. Фой)
Можно просто сохранить все даты в виде чисел, а потом посчитать разность. Но жизнь не всегда так проста. Если вы хотите работать с форматированными данными, то могут быть полезными модули Date::Manip,Date::Calc и DateTime.

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

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

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

(Отвечает Брайан Д. Фой и Дейв Кросс)
Можете использовать модуль Time::JulianDay из CPAN. Но убедитесь, что действительно хотите найти юлианский день, потому что разные люди по-разному его понимают. Например, см.http://www.hermetic.ch/cal_stud/jdn.htm.
Также можете попробовать модуль 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

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

(отвечает Брайан Д. Фой)
Используйте один из модулей Date. С DateTime всё просто: передайте то же время за вычетом одного дня.
use DateTime;  my $yesterday = DateTime->now->subtract( days => 1 );  print "Вчера было $yesterday\n";
Также можно использовать функцию Today_and_Now модуля Date::Calc.
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 часа. Есть два дня в году, когда это не так: это переходы между летним и зимним временем. Пусть лучше об этом заботятся модули.
Если вам просто необходимо решить задачу самостоятельно (или вы не можете использовать один из модулей), то вот решение, использующее модуль Time::Local, прилагающийся к Perl:
# код Гюннара Хьялмарсона use Time::Local; my $today = timelocal 0, 0, 12, ( localtime )[3..5]; my ($d, $m, $y) = ( localtime $today-86400 )[3..5]; printf "Вчера было %d-%02d-%02d\n", $y+1900, $m+1, $d;
В этом случае вы отсчитываете день от полудня и вычитаете 24 часа. Даже если календарный день длится 23 или 25 часов, то вы всё равно получите предыдущий календарный день, хотя и не в полдень. Так как время дня нас не интересует, то разница в час не имеет значения.

Есть ли у Perl проблема 2000 года? Он совместим с 2000 годом?

Если коротко, то у Perl нет проблемы 2000 года; и да, он совместим с 2000 годом, что бы это ни значило. Хотя программисты, которых вы наняли работать на Perl, могут быть несовместимыми.
Подробный ответ: вопрос связан с неправильным пониманием проблемы. Perl так же совместим с 2000 годом, как и карандаш, ни больше ни меньше. Можете записать карандашом не совместимую с 2000 годом заметку? Конечно можете. Это проблема карандаша? Конечно нет.
Функции для даты и времени, прилагающиеся к Perl (gmtime и localtime), дают верную информацию для определения года гораздо позднее 2000 (для 32-битных машин проблема встанет в 2038 году). Год, возвращаемый этими функциями в списковом контексте, дается за вычетом 1900. Для годов между 1910 и 1999 это совпадает с двумя последними цифрами. Чтобы избежать проблемы 2000 года, просто не считайте возвращаемое число последними цифрами года — это не так.
В скалярном контексте gmtime() и localtime() возвращают строку с датой и временем, которая содержит полную запись года. Например, $timestamp = gmtime(1005613200) записывает в $timestamp строку"Tue Nov 13 01:00:00 2001". Тут нет никакой проблемы 2000 года.
Это не значит, что Perl нельзя использовать для написания не совместимых с 2000 годом программ. Можно, как и карандаш. Это будет ошибкой программиста, но не языка. Рискуя взволновать людей, пугающих нас «проблемой 2000»: «В Perl нет „проблемы 2000", она есть у людей». Подробное описание см. вhttp://www.perl.org/about/y2k.html.

Строки

Как проверить ввод?

(отвечает Брайан Д. Фой)
Есть несколько способов убедиться, что значения соответствуют ожидаемым. Помимо конкретных примеров из perlfaq, можно также обратить внимание на модули, в названиях которых есть «Assert» и «Validate», а также прочие модули вроде Regexp::Common.
Некоторые модули включают проверку конкретных типов ввода, например, Business::ISBN,Business::CreditCardEmail::Valid и Data::Validate::IP.

Как избавиться от экранирования в строке?

Это зависит от того, что подразумевается под «экранированием». Про экранирование URL написано вperlfaq9. Экранирование в командной оболочке производится обратной косой чертой (\) и удаляется при помощи
s/\\(.)/$1/g;
Это не раскрывает "\n" или "\t" и другие специальные последовательности.

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

(отвечает Брайан Д. Фой)
Можно использовать оператор подстановки для поиска пар символов (или более длинных сочетаний) и заменить их одиночными. В следующей подстановке мы находим символ при помощи (.). Запоминающие скобки сохраняют его в обратной ссылке \1, и с ее помощью мы требуем, чтобы непосредственно последующим был тот же символ. Мы заменяем эту часть строки на $1.
s/(.)\1/$1/g;
Также можно использовать оператор транслитерации tr///. В следующем примере список для поиска в tr///ничего не содержит, но опция c дополняет его так, будто он содержит всё что угодно. В списке замены тоже ничего нет, так что тут tr/// — практически пустая операция, потому что не делает никаких замен (точнее, заменяет символ на тот же). Однако опция s удаляет идущие подряд одинаковые символы в строке
my $str = 'Haarlem';   # это в Голландии $str =~ tr///cs;       # теперь Harlem, как в Нью-Йорке

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

(отвечает Брайан Д. Фой)
Это задокументированно в perlref, и, хотя это не очень легкое чтиво, всё работает. В каждом из следующих примеров функция вызывается внутри фигурных скобок для разыменования ссылки. Если возвращается больше одного значения, то можно создать и разыменовать анонимный массив. В этом случае функция вызывается в списковом контексте.
print "Значения времени — @{ [localtime] }.\n";
Если нужно вызвать функцию в скалярном контексте, то нужно несколько изменить код. На самом деле, внутри фигурных скобок может быть все что угодно, поэтому нужно просто получить в итоге ссылку на скаляр, а как это сделать — ваше дело. Заметьте, что круглые скобки создают списковый контекст, поэтому для того, чтобы контекст был скалярным, перед именем функции указывается scalar:
print "Текущее время ${\(scalar localtime)}.\n"  print "Текущее время ${ my $x = localtime; \$x }.\n";
Если функция уже возвращает ссылку, то не нужно ее самостоятельно создавать.
sub timestamp { my $t = localtime; \$t }  print "Текущее время ${ timestamp() }.\n";
Модуль Interpolation также делает много чудес. Можно указать имя переменной, в примере ниже — E, чтобы создать связанный хеш, который отвечает за интерполяцию. Также есть несколько других методов.
use Interpolation E => 'eval'; print "Значения времени — $E{localtime()}.\n";
В большинстве случаев может быть проще обычное объединение строк, которое также делает контекст скалярным.
print "Текущее время " . localtime() . ".\n";

Как найти вложенные элементы?

Это не то, что можно сделать одним регулярным выражением, каким бы оно ни было сложным. Для поиска чего-то между двумя символами шаблон вроде /x([^x]*)x/ даст в $1 промежуточную подстроку. Для различных ограничителей нужно что-то вроде /alpha(.*?)omega/. Но ни один из этих шаблонов не работает со вложенностью. Для сбалансированных выражений с разделителями ({[ или < используйте модуль Regexp::Common из CPAN, либо см. (??{ code }) в странице документации perlre. В остальных случаях придется писать парсер.
Если вы действительно хотите написать парсер, то есть ряд модулей и странных штук, которые сильно упростят вам жизнь. В CPAN есть Parse::RecDescentParse::Yapp и Text::Balanced; также имеется программаbyacc. Начиная с Perl 5.8, в стандартную поставку входит Text::Balanced.
Простой подход с разрушением строки изнутри, который стоит попробовать — последовательная выборка компонентов с наибольшей вложенностью:
while (s/BEGIN((?:(?!BEGIN)(?!END).)*)END//gs) {     # сделать что-то с $1     }
Более хитрый подход — переложить эту работу на движок регулярных выражений Perl. Вот код, любезно предоставленный Дином Инада, который скорее годится на Конкурс запутанных исходников, но он действительно работает:
# в $_ содержится строка для разбора # BEGIN и END — открывающие и закрывающие отметки # в тексте с вложенностью.  @( = ('(',''); @) = (')',''); ($re=$_)=~s/((BEGIN)|(END)|.)/$)[!$3]\Q$1\E$([!$2]/gs; @$ = (eval{/$re/},$@!~/unmatched/i); print join("\n",@$[0..$#$]) if( $$[-1] );

Как обратить строку?

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

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

Можно сделать это вручную:
1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
Либо можно использовать модуль Text::Tabs из стандартной поставки Perl:
use Text::Tabs; @expanded_lines = expand(@lines_with_tabs);

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

Используйте Text::Wrap из стандартной поставки Perl:
use Text::Wrap; print wrap("\t", '  ', @paragraphs);
Передаваемые Text::Wrap абзацы не должны содержать переводы строк. Также Text::Wrap не делает выключку вправо.
Или используйте модуль Text::Autoformat из CPAN. Форматирование файлов можно упростить, назначив короткое имя однострочнику (командой alias):
alias fmt="perl -i -MText::Autoformat -n0777 \     -e 'print autoformat $_, {all=>1}' $*"
Чтобы разобраться во многих возможностях Text::Autoformat, обратитесь к документации.

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

Символы строки можно получить при помощи substr(). Например, для первого символа установите начало на позиции 0, а длину необходимой строки — 1.
$string = "Just another Perl Hacker"; $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-е вхождение чего-либо?

Нужно самостоятельно считать номер вхождения. Например, требуется заменить пятое вхождение "whoever"или "whomever" на "whosoever" или "whomsoever", независимо от регистра. Ниже предполагается, что заменяемая строка содержится в $_.
$count = 0; s{((whom?)ever)}{ ++$count == 5       # 5-е по счету?     ? "${2}soever"  # да, заменить     : $1            # оставить как есть         }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/// примерно так:
$string = "ThisXlineXhasXsomeXx'sXinXit"; $count = ($string =~ tr/X//); print "Символов X в строке: $count";
Это удобно, если вы просто ищете один символ. Но если вы хотите подсчитать в большой строке количество подстрок в несколько символов, то tr/// не поможет. Можете поместить глобальный поиск по шаблону в циклwhile(). Например, подсчитаем отрицательные числа:
$string = "-9 55 48 -2 23 -76 4 14 -44"; while ($string =~ /-\d+/g) { $count++ } print "Отрицательных чисел в строке: $count";
Другой вариант использует глобальный поиск по шаблону в списковом контексте, после чего приравнивает результат к скаляру, получая количество совпадений:
$count = () = $string =~ /-\d+/g;

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

Чтобы сменить регистр первой буквы каждого слова на верхний, используйте
$line =~ s/\b(\w)/\U$1/g;
Это имеет побочный эффект и превращает "don't do it" в "Don'T Do It". Иногда так нужно, в других случаях пригодится более точное решение Брайана Д. Фоя:
$string =~ s/ (              (^\w)    # в начале строки                |      # или              (\s\w)   # после пробельного символа                )             /\U$1/xg;  $string =~ s/([\w']+)/\u\L$1/g;
Для записи всей строки в верхнем регистре:
$line = uc($line);
Для записи всех слов в нижнем регистре, за исключением первой буквы:
$line =~ s/(\w+)/\u\L$1/g;
Вы можете (или даже должны) учитывать локальные установки с прагмой use locale. Несчетные подробности см. в perllocale.
Часто это связывается с написанием заголовков, но это не совсем верно. Так, можно рассмотреть правильное написания названия фильма: «Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb».
Модуль 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";     }

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

Некоторые модули осуществляют подобный разбор, в том числе Text::BalancedText::CSVText::CSV_XS иText::ParseWords.
Рассмотрим обработку строки CSV. Тут нельзя использовать split(/,/), потому запятые внутри полей не должны учитываться при разбиении. Вот пример строки с данными:
SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"
Из-за ограничений, налагаемых кавычками, получается довольно сложная задача. К счастью, за нас все решил Джефри Фридл, автор «Mastering Regular Expressions» (предполагается, что строка содержится в$text):
@new = (); push(@new, $+) while $text =~ m{     "([^\"\\]*(?:\\.[^\"\\]*)*)",?  # группировка текста в кавычках     | ([^,]+),?     | ,     }gx; push(@new, undef) if substr($text,-1,1) eq ',';
Если нужно внутри поля в кавычках разместить символы кавычек, то экранируйте их обратной косой чертой ("примерно \"так\"").
Или используйте модуль Text::ParseWords из стандартной поставки Perl:
use Text::ParseWords; @new = quotewords(",", 0, $text);

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

(отвечает Брайан Д. Фой)
Это можно сделать подстановкой. Для одной строки нужно заменить все начальные или конечные пробельные символы на пустые строки. Это делается в два шага:
s/^\s+//; s/\s+$//;
Также можно записать это в одну подстановку, хотя объединенная операция кажется более медленной, чем отдельные. Но это может вас и не волновать.
s/^\s+|\s+$//g;
В этом регулярном выражении ветвление соответствует пробелам в начале и проблемам в конце, потому что якори начала-конца имеют меньший приоритет, чем ветвление. С флагом /g подстановка применяется ко всем вхождениям, поэтому обрабатываются пробелы в начале и пробелы в конце. Помните, что перевод строки в конце соотносится с \s+, и якорь $ может совпасть с физическим концом строки, так что перевод строки тоже пропадает. Просто добавьте к выводу перевод строки, чтобы сохранить пустые строки (состоящие только из пробельных символов), которые целиком стирает замена ^\s+.
while( <> )     {     s/^\s+|\s+$//g;     print "$_\n";     }
Для строковой переменной, в которой записано несколько логических строк, добавьте флаг /m (multi-line) и применяйте регулярное выражение для каждой составляющей строки. С флагом /m якорь $ будет совпадать с позицией до внутреннего перевода строк, так что переводы строк удаляться не будут. Но это также удаляет конечные переводы строк.
$string =~ s/^\s+|\s+$//gm;
Заметьте, что строки, состоящие только из пробелов, пропадут, так как первая часть ветвления может совпасть с целой строкой и заменить ее пустой. Если пустые строки нужно сохранить, то придется немного поменять регулярное выражение. Вместо поиска любого пробельного символа, которым считается и перевод строки, ищите все прочие символы помимо "\n":
$string =~ s/^[\t\f ]+|[\t\f ]+$//mg;

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

В следующих примерах $pad_len — длина, до которой нужно заполнить строку, $text или $num — строка или число, а $pad_char — заполняющий символ. Если заранее известен заполнитель, то вместо $pad_char можно использовать строковую константу. Аналогично вместо $pad_len можно использовать постоянное целое число.
Самый простой подход задействует функцию sprintf. Она может добавлять пробелы справа или слева, либо нули слева, не урезая строку. Функция pack может заполнять строки только пробелами справа и урезает строку до максимальной длины $pad_len.
# Заполнение пробелами слева (не урезает строку): $padded = sprintf("%${pad_len}s", $text); $padded = sprintf("%*s", $pad_len, $text);    # аналогично  # Заполнение пробелами справа (не урезает строку): $padded = sprintf("%-${pad_len}s", $text); $padded = sprintf("%-*s", $pad_len, $text);   # аналогично  # Заполнение числа нулями слева (не урезает строку): $padded = sprintf("%0${pad_len}d", $num); $padded = sprintf("%0*d", $pad_len, $num);    # аналогично  # Заполнение пробелами слева при помощи pack (урезает строку): $padded = pack("A$pad_len",$text);
Если требуется заполнить строку каким-то символом помимо пробела или нуля, то используйте следующие подходы. Они все создают заполнение с оператором x и объединяют его с $text. Строка не урезается.
Заполнение слева и справа любым символом, создается новая строка:
$padded = $pad_char x ( $pad_len - length( $text ) ) . $text; $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 ) );

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

(отвечает Брайан Д. Фой)
Если вы знаете, где находятся колонки с данными, то используйте 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' );
Если нужно работать с CSV, то не делайте так, потому что формат несколько сложнее. Используйте один из модулей вроде Text::CSVText::CSV_XS или Text::CSV_PP.
Если нужно разбить строку с фиксированными колонками, то можете использовать unpack с флагом форматирования A (ASCII). При помощи числа после флага вы можете задать ширину колонки. Подробности см. в документации для pack и unpack в perlfunc.
my @fields = unpack( $line, "A8 A8 A8 A16 A4" );
Заметьте, что пробелы в аргументе формата unpack не обозначают настоящие пробелы. Если у вас есть разделенные пробелами данные, лучше использовать split.

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

(отвечает Брайан Д. Фой)
Можете использовать модуль Text::Soundex. Если хотите делать нечеткое сравнение, также попробуйте модули String::ApproxText::Metaphone и Text::DoubleMetaphone.

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

(отвечает Брайан Д. Фой)
Если без этого можно обойтись, то обойдитесь. Если можете использовать систему шаблонов вродеText::Template или Template, используйте ее. Кроме того, вы можете решить задачу при помощи sprintf илиprintf:
my $string = sprintf 'Привет, %s и %s!', $foo, $bar;
Хотя в качестве простого случая, где нежелательно задействовать полноценную систему с шаблонами, рассмотрим строчку с двумя скалярными переменными. Пусть требуется раскрыть $foo и $bar в значения переменных:
my $foo = 'Фред'; my $bar = 'Барни'; $string = 'Привет, $foo и $bar!';
Один из способов — оператор подстановки и двойной флаг /e. Первое /e заменяет $1 на $foo. Второе /eзаменяет $foo на значение 'Фред', которое и будет в строке в конечном итоге:
$string =~ s/(\$\w+)/$1/eeg;   # 'Привет, Фред и Барни'
/e молча игнорирует нарушения строгого режима (strict), заменяя неопределенные имена переменных пустой строкой. Так как используется флаг /e (даже дважды), то имеются те же проблемы безопасности, что и при использовании eval в строковом виде. Если в $foo есть что-то странное — например,@{[ system "rm -rf /" ]} — то можно получить неприятности.
Чтобы избежать проблем с безопасностью, можно не вычислять имена переменных, а взять значения из хеша. При помощи одного /e можно проверить, что в хеше имеется значение; если значения нет, то оно заменяется какой-то отметкой (здесь ???), показывающей, что вы о чём-то забыли:
my $string = 'Тут есть $foo и $bar';  my %Replacements = (     foo  => 'Фред',     );  # $string =~ s/\$(\w+)/$Replacements{$1}/g; $string =~ s/\$(\w+)/     exists $Replacements{$1} ? $Replacements{$1} : '???'     /eg;  print $string;

Почему не стоит всегда заключать переменные в двойные кавычки?

Проблема в том, что двойные кавычки вызывают преобразование в строку, и строками становятся числа и ссылки, даже когда этого не требуется. Знайте, что раскрытие двойных кавычек используется для создания новых строк. Если у вас уже есть строка, нужна ли другая?
Если вы привыкли писать странные вещи вроде таких:
print "$var";        # плохо $new = "$old";       # плохо somefunc("$var");    # плохо
то ждите неприятностей. В подавляющем числе случаев проще и лучше будет делать так:
print $var; $new = $old; somefunc($var);
Вы не только замедляете работу, но и рискуете внести ошибки в код, если скаляр — не строка или число, а ссылка:
func(\@array); sub func {     my $aref = shift;     my $oref = "$aref";    # неправильно }
Также вы можете столкнуться с коварными проблемами для тех немногих операторов в Perl, которые различают строку и число, таких как волшебный оператор автоинкремента ++ и функция syscall().
Также перевод в строку разрушает массивы.
@lines = `command`; print "@lines";     # неправильно — лишние пробелы print @lines;       # правильно

Почему не работает текст в конструкции <<HERE?

Проверьте три условия:
После << не должно быть пробела.
Возможно, в конце должна быть точка с запятой.
Перед меткой нельзя так просто ставить пробелы.
Если хотите выровнять текст в <<HERE, то можно сделать так:
# всё вместе ($VAR = <<HERE_TARGET) =~ s/^\s+//gm;     your text     goes here HERE_TARGET
Но метка HERE_TARGET всё равно должна прилегать к краю. Если нужно выравнять и ее, то следует добавить кавычки:
($quote = <<'    FINIS') =~ s/^\s+//gm;         ...we will have peace, when you and all your works have         perished--and the works of your dark master to whom you         would deliver us. You are a liar, Saruman, and a corrupter         of men's hearts.  --Theoden in /usr/src/perl/taint.c     FINIS $quote =~ s/\s+--/\n--/;
Ниже приводится хорошая универсальная функция для приведения в порядок выравненного текста в <<HERE. Блок текста ожидается в качестве аргумента. Функция просматривает, начинается ли каждая строка с общей подстроки; если начинается, то эта подстрока удаляется; если нет, то берется количество начальных пробелов в первой строке и удаляется из начала всех последующих.
sub fix {     local $_ = shift;     my ($white, $leader);    # общий пробел и общая начальная подстрока     if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {         ($white, $leader) = ($2, quotemeta($1));     } else {         ($white, $leader) = (/^(\s+)/, '');     }     s/^\s*?$leader(?:$white)?//gm;     return $_; }
Специальные начальные подстроки определяются функцией динамически:
$remember_the_main = fix<<'    MAIN_INTERPRETER_LOOP'; @@@ int @@@ runops() { @@@     SAVEI32(runlevel); @@@     runlevel++; @@@     while ( op = (*op->op_ppaddr)() ); @@@     TAINT_NOT; @@@     return 0; @@@ } MAIN_INTERPRETER_LOOP
Также обрабатывается выравнивание с одинаковым начальным отступом; последующее дополнительное выравнивание сохраняется:
$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

Массивы

В чём разница между списками и массивами?

Массив имеет переменную длину, список — нет. Массив — это то, куда можно проталкивать (push) и откуда можно выталкивать (pop) элементы, список — это просто набор значений. Некоторые люди различают их так: список — это значение, а массив — переменная. Процедуры получают и возвращают списки, какие-то элементы языка могут находиться в списковом контексте, вы инициализируете массивы списками и проходите в foreach() по списку. Переменные, начинающиеся на @ — массивы, анонимные массивы — такие же массивы, в скалярном контексте массив ведет себя как число элементов, процедуры получают аргументы через массив @_, а pushpop и shift работают только для массивов.
Отметим, что «списков в скалярном контексте» не существует. Когда вы пишете
$scalar = (2, 5, 7, 9);
то в скалярном контексте используются запятые, так что тут вообще нет списка! Это приводит к тому, что возвращается последнее значение: 9.

В чём разница между $array[1] и @array[1]?

Первое — скалярное значение, второе — срез массива, делающий его списком с одним (скалярным) значением. Вы должны использовать $, если необходимо скалярное значение (чаще всего) и @, когда требуется список с одним значением (очень-очень редко, почти никогда).
Иногда разницы никакой, иногда разница есть. Например, сравните:
$good[0] = `программа, которая выводит несколько строк`;
и
@bad[0]  = `та же программа, которая выводит несколько строк`;
Прагма use warnings и флаг -w предупредит вас, когда это важно.

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

(отвечает Брайан Д. Фой)
Используйте хеш. Если вспоминаете о чем-то, не подразумевающем повторы, то думайте о ключах хеша.
Если для порядок следования элементов не имеет значения, то вы должны просто создать хеш, а потом извлечь ключи. Не важно, как вы создаете хеш — важно, что вы используете keys для получения уникальных элементов.
my %hash   = map { $_, 1 } @array; # или срез хеша:  @hash{ @array } = (); # или 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), поэтому next пропускает итерацию, и цикл переходит к следующему элементу.
my @unique = (); my %seen   = ();  foreach my $elem ( @array )     {     next if $seen{ $elem }++;     push @unique, $elem;     }
Вы можете записать это еще короче, используя grep, который делает то же самое.
my %seen = (); my @unique = grep { ! $seen{ $_ }++ } @array;

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

(Частично использованы ответы Анно Зигеля и Брайана Д. Фоя)
Слова «содержится ли в» должны наводить на мысль, что для хранения данных стоило использовать хеш, а не список или массив. Хеши созданы для быстрого ответа на такой вопрос, массивы — нет.
Опять же, есть несколько решений. Чтобы проверить наличие элемента в массиве или хеше, можно использовать появившийся в Perl 5.10 оператор умного поиска ~~ (smart matching):
use 5.010;  if( $item ~~ @array )     {     say "В массиве есть $item"     }  if( $item ~~ %hash )     {     say "В хеше есть $item"     }
В предыдущих версиях Perl необходимо сделать чуть больше. Если хотите часто производить такой запрос для разных строковых значений, то самым эффективным решением будет обратить исходный массив и работать с хешем, ключи которого содержат значения массива:
@blues = qw/azure cerulean teal turquoise lapis-lazuli/; %is_blue = (); for (@blues) { $is_blue{$_} = 1 }
Теперь можно делать проверку $is_blue{$color}. Может оказаться лучше сразу хранить всё в хеше.
Если все значения — небольшие целые числа, то можно использовать простой индексированный массив, он займет в памяти меньше места:
@primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31); @is_tiny_prime = (); for (@primes) { $is_tiny_prime[$_] = 1 } # or simply  @istiny_prime[@primes] = (1) x @primes;
Теперь можно делать проверку $is_tiny_prime[$number].
Если значения — целые числа, то можно сэкономить довольно много памяти, если использовать битовые строки:
@articles = ( 1..10, 150..2000, 2017 ); undef $read; for (@articles) { vec($read,$_,1) = 1 }
Теперь можно делать проверку vec($read,$n,1) для какого-то $n.
Эти подходы обеспечивают быстрые отдельные проверки, но требуют перестройки исходного списка или массива. Это окупается, только если нужно проверить несколько значений в одном и том же массиве.
Если требуется только единичная проверка, то для этого есть функция first, экспортируемая стандартным модулем List::Util. Она останавливает поиск, как только встречается элемент. Для быстроты функция написана на C, но ее эквивалент на Perl выглядит примерно так:
sub first (&@) {     my $code = shift;     foreach (@_) {         return $_ if &{$code}();     }     undef; }
Если скорость не очень волнует, то общий подход — grep в скалярном контексте (возвращается количество элементов, соответствующих условию) для прохода по всему списку. Но это полезно, если требуется знать, сколько раз встречается элемент:
my $is_there = grep $_ eq $whatever, @array;
Если нужно извлечь элементы, соответствующие определенному условию, то просто используйте grep в списковом контексте:
my @matches = grep $_ eq $whatever, @array;

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

Используйте хеши. Вот код для одновременного нахождения симметрической разности и пересечения. Предполагается, что каждый элемент встречается в исходных массивах единожды:
@union = @intersection = @difference = (); %count = (); foreach $element (@array1, @array2) { $count{$element}++ } foreach $element (keys %count) {     push @union, $element;     push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;     }
Заметьте, что это симметрическая разность, то есть, множество элементов, которые принадлежат A и B, но не A и B одновременно. Это как исключающее «или».

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

Начиная с Perl 5.10, ответ можно получить при помощи оператора ~~:
use 5.010;  if( @array1 ~~ @array2 )     {     say "Массивы совпадают";     }  if( %hash1 ~~ %hash2 )   # без проверки значений!     {     say "Ключи хешей совпадают";     }
Следующая процедура работает для одномерных массивов. Используется сравнение строк, определенные и не определенные пустые строки не различаются. Если нужно по-другому, то измените код соответствующим образом.
$are_equal = compare_arrays(\@frogs, \@toads);  sub compare_arrays {     my ($first, $second) = @_;     no warnings;    # отключить предупреждения -w undef     return 0 unless @$first == @$second;     for (my $i = 0; $i < @$first; $i++) {         return 0 if $first->[$i] ne $second->[$i];         }     return 1;     }
Для многомерных структур может пригодиться подход на основе модуля FreezeThaw из CPAN:
use FreezeThaw qw(cmpStr); @a = @b = ( "this", "that", [ "more", "stuff" ] );  printf "a и b содержат %s массивы\n",     cmpStr(\@a, \@b) == 0     ? "одинаковые"     : "разные";
Это также работает при сравнении хешей. Ниже показаны два разных результата:
use FreezeThaw qw(cmpStr cmpStrHard);  %a = %b = ( "this" => "that", "extra" => [ "more", "stuff" ] ); $a{EXTRA} = \%b; $b{EXTRA} = \%a;  printf "a и b содержат %s хеши\n", cmpStr(\%a, \%b) == 0 ? "одинаковые" : "разные";  printf "a и b содержат %s хеши\n", cmpStrHard(\%a, \%b) == 0 ? "одинаковые" : "разные";
Первая проверка показывает, что хеши содержат одинаковые данные, хотя вторая — нет. Какая проверка предпочтительна, читатель может решить самостоятельно.

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

Чтобы найти первый элемент массива, соответствующий определенному условию, можете использовать функцию 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 }     }
Если нужен индекс массива, то можете пройтись по индексам и остановиться, как только условие выполнится:
my( $found, $index ) = ( undef, -1 ); for( $i = 0; $i < @array; $i++ ) {     if( $array[$i] =~ /Perl/ ) {         $found = $array[$i];         $index = $i;         last;         }     }

Как работать со связанными списками?

Обычно связанные списки не нужны в Perl, потому что для простых массивов функции pushpopshift иunshift действуют с обоих концов; можно также использовать splice, чтобы добавить или удалить произвольное число элементов в любом месте. pop и shift для динамических массивов в Perl имеют сложность O(1). Если не делается pop и shift, то push требует переразмещения каждые log(N) раз, а unshiftкаждый раз выполняет копирование указателей.
Если действительно нужно, то можете использовать структуры, описанные в perldsc и perltoot, и делать то, что сказано в книге по алгоритмам. Например, рассмотрим узел списка:
$node = {     VALUE => 42,     LINK  => undef, };
По списку можно перемещаться таким образом:
print "Список: "; for ($node = $head;  $node; $node = $node->{LINK}) {     print $node->{VALUE}, " ";     } print "\n";
А так — добавлять элементы:
my ($head, $tail); $tail = append($head, 1);    # прирастить новую голову for $value ( 2 .. 10 ) {     $tail = append($tail, $value);     }  sub append {     my($list, $value) = @_;     my $node = { VALUE => $value };     if ($list) {         $node->{LINK} = $list->{LINK};         $list->{LINK} = $node;         }     else {         $_[0] = $node;    # заменить переданный список         }     return $node;     }
Но, повторимся, обычно достаточно встроенных структур Perl.

Как создать циклический список?

(отвечает Брайан Д. Фой)
Если хотите бесконечно проходить по массиву, то можете инкрементировать индекс по модулю числа элементов:
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 — ссылка на массив     my $i = @$deck;     while (--$i) {         my $j = int rand ($i+1);         @$deck[$i,$j] = @$deck[$j,$i];         } }  # перемешать коллекцию музыки # my @mpeg = <audio/*/*.mp3>; fisher_yates_shuffle( \@mpeg );    # перемешать массив @mpeg без копирования print @mpeg;
Заметьте, что реализация выше перетасовывает элементы на месте, в отличие от List::Util::shuffle(), которая принимает список и возвращает новый.
Возможно, вы встречались с алгоритмами, которые используют splice для случайного выбора элемента и перестановки с текущим:
srand; @new = (); @old = 1 .. 10;    # просто пример while (@old) {     push(@new, splice(@old, rand @old, 1));     }
Это плохое решение, потому что splice уже имеет сложность O(N), и, так как вы вызываете его N раз, то изобретаете алгоритм квадратичной сложности O(N**2). Perl столь эффективен, что вы можете и не заметить скорость работы, но это до тех пор, пока вы не встретитесь с большими массивами.

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

Используйте for или foreach:
for (@lines) {     s/foo/bar/;    # заменить слово     tr/XZ/ZX/;     # переставить буквы     }
Вот другой пример с расчетом объема шаров:
for (@volumes = @radii) {   # в @volumes — измененные элементы     $_ **= 3;     $_ *= (4/3) * 3.14159;  # это будет приведено к константе     }
Также это можно сделать с map(), который создан для преобразования одного списка в другой:
@volumes = map {$_ ** 3 * (4/3) * 3.14159} @radii;
Если нужно так же изменить значения хеша, то используйте функцию values. Начиная с Perl 5.6, значения не копируются, поэтому, если в примере ниже изменяется $orbit, то изменяется само значение:
for $orbit ( values %orbits ) {     ($orbit **= 3) *= (4/3) * 3.14159;     }
До Perl 5.6 в values передавались копии значений, так что код часто содержит конструкции в духе@orbits{keys %orbits} вместо values %orbits, если требуется внести изменения в хеш.

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

Используйте функцию rand() (см. раздел rand в perlfunc):
$index   = rand @array; $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 "следующая перестановка: (@perm)\n";     }
Для еще более быстрой работы можно сделать так:
use Algorithm::Permute;  my @array = 'a'..'d';  Algorithm::Permute::permute {     print "следующая перестановка: (@array)\n";     } @array;
Вот небольшая программа, которая генерирует все перестановки для всех слов и на каждой строке ввода. Алгоритм, использованный в permute(), описан в четвертом (еще не опубликованном) томе «Искусства программирования» Кнута и работает для любого списка:
#!/usr/bin/perl -n # Генератор упорядоченных перестановок Фишера-Краузе  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, потому что блок сравнения может вызываться по нескольку раз для одного и того же элемента. Вот пример с выделением в каждом элементе первого слова, следующего за числом, и сортировки по этим словам без учета регистра:
@idx = (); for (@data) {     ($item) = /\d+\s*(\S+)/;     push @idx, uc($item);     } @sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ];
Это можно записать следующим образом, используя прием, известный как «преобразование Шварца»:
@sorted = map  { $_->[0] }     sort { $a->[1] cmp $b->[1] }     map  { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data;
Если нужно сортировать по нескольким полям, то полезен следующий подход:
@sorted = sort {     field1($a) <=> field1($b) ||     field2($a) cmp field2($b) ||     field3($a) cmp field3($b)     } @data;
Это несложно совместить с описанным выше предварительным расчетом ключей.
Подробно о таком подходе см. статью о sort в «Far More Than You Ever Wanted To Know».
Также см. вопрос perlfaq4 о сортировке хешей.

Как работать с битовыми массивами?

Используйте pack() и unpack(), либо vec() с побитовыми операциями.
Вам не нужно хранить отдельные биты в массиве, это огромная растрата памяти. Для преобразования массива битов в строку используйте vec(). В примере ниже в $vec устанавливается N-й бит, только если установлен $ints[N]:
@ints = (...);    # массив битов, например, ( 1, 0, 0, 1, 1, 0 ... ) $vec = ''; foreach( 0 .. $#ints ) {     vec($vec,$_,1) = 1 if $ints[$_];     }
Строка $vec занимает ровно столько битов, сколько требуется. Например, если в @ints имеется 16 элементов, то $vec займет только два байта (не считая затрат на скалярную переменную).
Вот код, преобразующий вектор $vec в массив @ints:
sub bitvec_to_list {     my $vec = shift;     my @ints;     # Найти плотность нулевых байтов, а затем выбрать лучший алгоритм     if ($vec =~ tr/\0// / length $vec > 0.95) {         use integer;         my $i;          # Этот метод быстрее, если большинство байтов — нулевые         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 {         # Это быстрый алгоритм для общего случая         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;     }
Также можно использовать модуль Bit::Vector из CPAN:
$vector = Bit::Vector->new($num_of_bits); $vector->Index_List_Store(@ints); @ints = $vector->Index_List_Read();
Bit::Vector дает эффективные методы для битовых векторов, множеств небольших целых чисел, а также арифметики больших целых чисел.
Вот более подробный пример с vec():
# Пример с vec $vector = "\xff\x0f\xef\xfe"; print "Строка \\xff\\x0f\\xef\\xfe представляет число ", unpack("N", $vector), "\n"; $is_set = vec($vector, 23, 1); print "23-й бит: ", $is_set ? "1" : "0", ".\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  ширина: $width  значение: $value\n";     pvec($vector);     }  sub pvec {     my $vector = shift;     my $bits = unpack("b*", $vector);     my $i = 0;     my $BASE = 8;      print "длина вектора в байтах: ", length($vector), "\n";     @bytes = unpack("A8" x length($vector), $bits);     print "биты: @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;    # копировать; сохранить %hash1  foreach my $key2 ( keys %hash2 )     {     if( exists $new_hash{$key2} )         {         warn "Ключ [$key2] есть в обоих хешах!";         # обработать случай повтора (можно ограничиться предупреждением)         ...         next;         }     else         {         $new_hash{$key2} = $hash2{$key2};         }     }
Если вы хотите создать новый хеш, то можете использовать такой же подход с циклом, только поменяйте%new_hash на %hash1.
foreach my $key2 ( keys %hash2 )     {     if( exists $hash1{$key2} )         {         warn "Ключ [$key2] есть в обоих хешах!";         # обработать случай повтора (можно ограничиться предупреждением)         ...         next;         }     else         {         $hash1{$key2} = $hash2{$key2};         }     }
Если вам всё равно, какой из хешей перезапишет ключи и значения другого, то для добавления одного хеша к другому можно просто использовать срез. В этом случае при совпадении ключей значения из %hash2заменяют значения из %hash1:
@hash1{ keys %hash2 } = values %hash2;

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

(отвечает Брайан Д. Фой)
Простой ответ: «Не делайте так!»
Если вы проходите по хешу, используя each(), то можете удалить только что полученный ключ, не беспокоясь о последствиях. Если же вы удалите или добавите другие ключи, то итератор может пропустить какие-то или пройти по другим несколько раз, так как perl может перестроить таблицу хеша. См. раздел each в perlfunc.

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

Создайте обратный хеш:
%by_value = reverse %by_key; $key = $by_value{$value};
Это не очень эффективно. Более экономно для памяти использовать
while (($key, $value) = each %by_key) {     $by_value{$value} = $key;     }
Если в хеше могут содержаться повторяющиеся значения, то приведенные методы найдут только один из связанных ключей. Это может вас волновать, а может и не волновать. Если волнует, то всегда можно обратить хеш в хеш массивов:
while (($key, $value) = each %by_key) {     push @{$key_list_by_value{$value}}, $key;     }

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

Если имеется в виду число ключей, то достаточно просто использовать функцию keys() в скалярном контексте:
$num_keys = keys %hash;
keys() также сбрасывает итератор, поэтому вы можете получить странные результаты, если используете ее между вызовами других операций над хешем вроде each().

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

(отвечает Брайан Д. Фой)
Чтобы отсортировать хеш, начните с ключей. В следующем примере функции сортировки передается список ключей, и она сравнивает их в порядке ASCII (может зависеть от локальных установок). Выходной список содержит отсортированные ключи. После этого можно пройти по ним и создать отчет, в котором ключи перечисляются по порядку.
my @keys = sort { $a cmp $b } keys %hash;  foreach my $key ( @keys )     {     printf "%-20s %6d\n", $key, $hash{$key};     }
Однако часть кода с sort() можно записать более хитро, и вместо сравнения ключей рассчитывать соответствующие значения и сравнивать их.
Например, чтобы порядок результата не учитывал регистр, мы используем в строке в двойных кавычках последовательность \L, меняющую регистр на нижний; блок sort() сравнивает значения с измененным регистром, чтобы определить, в каком порядке размещать ключи:
my @keys = sort { "\L$a" cmp "\L$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 также может быть полезным. Хотя это позволит держать хеш отсортированным, вы должны избегать замедления работы от интерфейса tie. Вы уверены, что вам это нужно? :-)

В чём разница между delete и undef для хешей?

Хеши содержат пары скаляров: первый элемент — ключ, второй — значение. Ключ будет приведен к строке, хотя его значение может быть скаляром произвольного типа: строкой, числом, либо ссылкой. Если ключ $keyимеется в хеше %hash, то exists($hash{$key}) возвращает true. Значение для данного ключа может бытьundef, тогда $hash{$key} соответствует undef, в то время как exists $hash{$key} возвращает true. В этом случае в хеше имеется пара <$keyundef>.
Покажем на диаграммах. Вот таблица %hash:
КлючЗначение
a3
x7
d0
e2
Справедливы следующие условия
$hash{'a'}                       true $hash{'d'}                       false defined $hash{'d'}               true defined $hash{'a'}               true exists $hash{'a'}                true (только в Perl 5) grep ($_ eq 'a', keys %hash)     true
Если вы сделаете
undef $hash{'a'}
то в таблице будет:
КлючЗначение
aundef
x7
d0
e2
и справедливы следующие условия (изменения выделены):
$hash{'a'}                       false $hash{'d'}                       false defined $hash{'d'}               true defined $hash{'a'}               false exists $hash{'a'}                true (только в Perl 5) grep ($_ eq 'a', keys %hash)     true
Заметьте последние два: имеется неопределенное значение, но определенный ключ!
Теперь рассмотрим
delete $hash{'a'}
После этого в таблице будет
КлючЗначение
x7
d0
e2
и справедливы следующие условия (изменения выделены):
$hash{'a'}                       false $hash{'d'}                       false defined $hash{'d'}               true defined $hash{'a'}               false exists $hash{'a'}                false (только в Perl 5) grep ($_ eq 'a', keys %hash)     false
Как видно, пропала вся запись.

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

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

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

(отвечает Брайан Д. Фой)
Для сброса each можете использовать функцию keys или values. Чтобы просто сбросить итератор, используемый each, не производя других действий, вызовите одну из функций в пустом контексте:
keys %hash;      # просто сбрасывает итератор values %hash;    # просто сбрасывает итератор
См. документацию для each в perlfunc.

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

Сначала извлеките ключи из хешей в списки, затем разрешите описанную выше задачу удаления повторов. Пример:
%seen = (); for $element (keys(%foo), keys(%bar)) {     $seen{$element}++;     } @uniq = keys %seen;
Или короче:
@uniq = keys %{{%foo,%bar}};
Или, если требуется экономить память:
%seen = (); while (defined ($key = each %foo)) {     $seen{$key}++; } while (defined ($key = each %bar)) {     $seen{$key}++; } @uniq = keys %seen;

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

Или самостоятельно преобразуйте структуру в стоковый вид (невеселое занятие), либо используйте модульMLDBM из CPAN (основывающийся на Data::Dumper) поверх DB_File или GDBM_File.

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

Используйте 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,...)

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

Если вы про что-то такое:
somefunc($hash{"nonesuch key here"});
то элемент автоматически появляется, храните ли вы что-то там или нет. Это из-за того, что функции получают скаляры по ссылке. Если somefunc() изменяет $_[0], то она должна быть готова занести его обратно в версию вызова.
Это было исправлено в Perl 5.004.
Обычно простой доступ к значению для несуществующего ключа не заставляет ключ постоянно присутствовать в хеше. Это отличается от поведения AWK.

Как сделать аналог структуры C, класса C++, хеш или массив хешей или массивов?

Обычно используются ссылки, примерно так:
$record = {     NAME   => "Jason",     EMPNO  => 132,     TITLE  => "deputy peon",     AGE    => 23,     SALARY => 37_000,     PALS   => [ "Norbert", "Rhys", "Phineas"], };
Ссылки задокументированы в perlref и растущем perlreftut. Примеры сложных структур данных приведены вperldsc и perllol. Примеры структур и классов есть в perltoot.

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

(отвечает Брайан Д. Фой)
Ключи хешей — строки, поэтому вы не можете на деле использовать в качестве них ссылки. Когда вы пытаетесь это сделать, perl превращает ссылку в ее строковую форму (например, HASH(0xDEADBEEF)). Из строковой формы вы не можете вернуться к ссылке, по крайней мере, не производя самостоятельно дополнительных манипуляций. Также помните, что ключи хеша должны быть уникальными, однако две разные переменные могут иметь одинаковую ссылку (и в дальнейшем эти переменные могут изменяться).
Может оказаться подходящим модуль Tie::RefHash, распространяемый с Perl. Он берет на себя эти дополнительные манипуляции.

Разное

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

Perl прекрасно работает с бинарными данными. Однако в Windows или DOS нужно установить бинарныйbinmode, чтобы избежать преобразования конца строк. В целом, следует изменять binmode всякий раз, когда требуется работать с бинарными данными.
Также см. binmode в perlfunc или perlopentut.
Если вам требуется работать с 8-битными текстовыми данными, то см. perllocale. Если нужно работать с многобайтовыми символами, то есть ряд сложностей — см. perlfaq6.

Как узнать, является ли скаляр числом; целое ли оно, либо с плавающей точкой?

Если вас не интересуют обозначения IEEE вроде "NaN" или "Infinity", то можно использовать регулярные выражения.
if (/\D/)            { print "содержит не-цифры\n" } if (/^\d+$/)         { print "целое беззнаковое число\n" } if (/^-?\d+$/)       { print "целое число\n" } if (/^[+-]?\d+$/)    { print "целое число со знаком + или -\n" } if (/^-?\d+\.?\d*$/) { print "вещественное число\n" } if (/^-?(?:\d+(?:\.\d*)?|\.\d+)$/) { print "десятичное число\n" } if (/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)         { print "число с плавающей точкой\n" }
Есть несколько распространенных модулей для этой задачи. Scalar::Util (распространяется с Perl версии 5.8 и выше) предоставляет доступ к внутренней функции looks_like_number для определения, похожа ли переменная на число. Data::Types экспортирует функции, которые проверяют типы данных приведенными выше, а также прочими регулярными выражениями. Кроме того, есть модуль Regexp::Common, содержащий регулярные выражения для проверки чисел различного типа. Эти три модуля доступны из CPAN.
Для POSIX-систем Perl поддерживает POSIX::strtod. У нее довольно путаная семантика, поэтому есть более удобная функция-обертка 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. Модуль POSIX из стандартной поставки Perl предоставляет функции strtod и strtol для преобразования строк в вещественные двойной точности и длинные целые соответственно.

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

Для каких-то особых приложений можно использовать один из модулей DBM, см. AnyDBM_File. В общем случае стоит обратиться к модулям FreezeThaw и Storable из CPAN. Начиная с Perl 5.8, Storable входит в стандартную поставку. Вот пример использования функций store и retrieve из Storable:
use Storable; store(\%hash, "filename");  # ... $href = retrieve("filename");        # по ссылке %hash = %{ retrieve("filename") };   # прямо в хеш

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

Модуль 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).

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

Возьмите из CPAN модуль Business::CreditCard.

Как упаковать массивы или числа с плавающей точкой для кода XS?

Код kgbpack.c в модуле PGPLOT из CPAN создан для этого. Если вы плотно занимаетесь расчетами с числами с плавающей точкой, то подумайте о том, чтобы использовать модуль PDL из CPAN — он сильно упрощает дело.

No comments:

Post a Comment