Perl: отображение на первый элемент списков
Задача: создать хеш, используя карту, где ключи - это элементы данного массива @a, а значения - это первые элементы списка, возвращаемые некоторой функцией f($element_of_a):
my @a = (1, 2, 3);
my %h = map {$_ => (f($_))[0]} @a;
Все в порядке, пока f() не возвращает пустой список (это абсолютно правильно для f(), и в этом случае я бы хотел назначить undef). Ошибка может быть воспроизведена с помощью следующего кода:
my %h = map {$_ => ()[0]} @a;
Сама ошибка звучит как "Нечетное количество элементов в назначении хэша". Когда я переписываю код такой, что:
my @a = (1, 2, 3);
my $s = ()[0];
my %h = map {$_ => $s} @a;
или же
my @a = (1, 2, 3);
my %h = map {$_ => undef} @a;
Perl вообще не жалуется.
Так как мне решить эту проблему - получить первые элементы списка, возвращаемого функцией f(), когда возвращаемый список пуст?
Версия Perl 5.12.3
Благодарю.
3 ответа
Я просто немного поиграл, и кажется, что ()[0]
в контексте списка интерпретируется как пустой список, а не как undef
скаляр. Например, это:
my @arr = ()[0];
my $size = @arr;
print "$size\n";
печать 0
, Так $_ => ()[0]
примерно эквивалентно просто $_
,
Чтобы исправить это, вы можете использовать scalar
функция для принудительного скалярного контекста:
my %h = map {$_ => scalar((f($_))[0])} @a;
или вы можете добавить явное undef
в конец списка:
my %h = map {$_ => (f($_), undef)[0]} @a;
или вы можете обернуть возвращаемое значение вашей функции в истинный массив (а не просто в плоский список):
my %h = map {$_ => [f($_)]->[0]} @a;
(Лично мне больше нравится этот последний вариант)
Особое поведение фрагмента пустого списка описано в разделе "Фрагменты" в perldata
:
Часть пустого списка - все еще пустой список. […] Это облегчает написание циклов, которые завершаются, когда возвращается нулевой список:
while ( ($home, $user) = (getpwent)[7,0]) { printf "%-8s %s\n", $user, $home; }
Второе предложение Джонатана Леффлера - лучшее, что можно сделать, это решить проблему с самого начала, если это вообще возможно:
sub f {
# ... process @result
return @result ? $result[0] : undef ;
}
Явный undef
необходимо для решения проблемы пустого списка.
Во-первых, большое спасибо всем ответчикам! Теперь я чувствую, что должен предоставить реальные детали реальной задачи.
Я анализирую XML-файл, содержащий набор элементов, каждый из которых выглядит так:
<element>
<attr_1>value_1</attr_1>
<attr_2>value_2</attr_2>
<attr_3></attr_3>
</element>
Моя цель - создать Perl-хеш для элемента, который содержит следующие ключи и значения:
('attr_1' => 'value_1',
'attr_2' => 'value_2',
'attr_3' => undef)
Давайте ближе посмотрим на <attr_1>
элемент. XML::DOM::Parser
CPAN
Модуль, который я использую для разбора создает для них объект класса XML::DOM::Element
давай дадим название $attr
для ознакомления. Название элемента легко получить $attr->getNodeName
, но для доступа к тексту, заключенному в <attr_1>
теги нужно получить все <attr_1>
Сначала дочерние элементы:
my @child_ref = $attr->getChildNodes;
За <attr_1>
а также <attr_2>
элементы ->getChildNodes
возвращает список, содержащий ровно одну ссылку (на объект XML::DOM::Text
класс), а для <attr_3>
он возвращает пустой список. Для <attr_1>
а также <attr_2>
Я должен получить значение $child_ref[0]->getNodeValue
в то время как для <attr_3>
Я должен разместить undef
в результирующий хеш, так как там нет текстовых элементов.
Итак, вы видите, что f
функция (метод ->getChildNodes
в реальной жизни) реализация не может контролироваться:-) Полученный код, который я написал, таков (подпрограмма снабжена списком XML::DOM::Element
ссылки на элементы <attr_1>
, <attr_2>
, а также <attr_3>
):
sub attrs_hash(@)
{
my @keys = map {$_->getNodeName} @_; # got ('attr_1', 'attr_2', 'attr_3')
my @child_refs = map {[$_->getChildNodes]} @_; # got 3 refs to list of XML::DOM::Text objects
my @values = map {@$_ ? $_->[0]->getNodeValue : undef} @child_refs; # got ('value_1', 'value_2', undef)
my %hash;
@hash{@keys} = @values;
%hash;
}