Срезы

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

fred flintstone:2168:301 Cobblestone Way:555-1212:555-2121:3
barney rubble:709918:3128 Granite Blvd:555-3333:555-3438:0

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

while () {
chomp;
my @items = split /:/;
my($card_num, $count) = ($items[1], $items[5]);
... # Теперь работаем с этими двумя переменными
}

Но массив @items больше ни для чего не нужен; он выглядит совершенно лишним. Может быть, результат split лучше будет присвоить списку скаляров:

my($name, $card_num, $addr, $home, $work, $count) = split /:/;

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


В таких ситуациях некоторые программисты используют фиктивные имена вида $dummy_1, показывающие, что после разбивки строки значение этого элемента не используется. Но Ларри решил, что это слишком хлопотно, поэтому в Perl был добавлен специальный способ использования undef. «Присваивание» элемента списка значению undef говорит о том, что соответствующий элемент исходного списка попросту игнорируется:

my(undef, $card_num, undef, undef, undef, $count) = split /:/;

Удобнее? Да, из программы исчезают ненужные переменные. Впрочем, есть и недостаток: теперь нам придется считать undef, чтобы определить, какому элементу соответствует $count. А при большом количестве элементов в списке решение становится слишком громоздким. Например, некоторые программисты, которые хотели извлечь из данных stat только значение mtime, писали код следующего вида:

my(undef, undef, undef, undef, undef, undef, undef,
undef, undef, $mtime) = stat $some_file;

Стоит ошибиться с подсчетом undef, и вместо mtime вы получите atime или ctime; такие ошибки весьма усложняют отладку. Но существует более удобное решение: Perl может индексировать списки по аналогии с массивами.


Результат называется срезом (slice) списка. Значение mtime является элементом с индексом 9 в списке, возвращаемом stat2, и его можно получить по соответствующему индексу:

my $mtime = (stat $some_file)[9];

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

my $mtime = stat($some_file)[9]; # Синтаксическая ошибка!

Срез состоит из списка в круглых скобках и следующего за ним индекса в квадратных скобках. Круглые скобки, в которые заключаются аргументы функций, не подходят. Но вернемся к библиотеке: список, с которым мы работаем, является возвращаемым значением split. Мы можем воспользоваться синтаксисом срезов для извлечения элементов 1 и 5 с индексами:

my $card_num = (split /:/)[1];
my $count = (split /:/)[5];

Нельзя сказать, что подобные срезы в скалярном контексте (извлечение одного элемента из списка) чем-то плохи, но было бы проще и эффективнее, если бы нам не приходилось вызывать split дважды. Мы и не будем делать это дважды; оба значения можно получить, используя срез списка в списочном контексте:

my($card_num, $count) = (split /:/)[1, 5];

Команда извлекает из списка элементы с индексами 1 и 5, возвращая их в виде списка из двух элементов.


Присваивая этот список двум переменным my, мы получаем ровно то, что хотели. Срез выполняется только один раз, и значения сразу двух переменных задаются в простой и понятной записи. Срезы часто оказываются самым простым способом извлечения нескольких элементов из списка. В следующем примере из списка извлекается первый и последний элемент; при этом мы используем тот факт, что индекс –1 соответствует последнему элементу:

my($first, $last) = (sort @names)[0, -1];

Индексы могут следовать в произвольном порядке и даже повторяться. В этом примере извлекаются 5 элементов списка, содержащего 10 элементов:

my @names = qw{ zero one two three four five six seven eight nine };
my @numbers = ( @names )[ 9, 0, 2, 1, 0 ];
print "Bedrock @numbers\n"; # Bedrock nine zero two one zero

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела Perl на эту тему:
Отбор элементов списка
Перехват ошибок в блоках eval
Преобразование элементов списка
Срезы массивов
Срезы хешей

Вернуться в раздел: Perl / 16. Расширенные возможности Perl