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::ParserCPAN Модуль, который я использую для разбора создает для них объект класса 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;
}
Другие вопросы по тегам