Как преобразовать массив в хеш с именами переменных, сопоставленными как ключи в Perl? - PullRequest
2 голосов
/ 28 марта 2012

Я часто использую этот шаблон в Perl

sub fun {
    my $line = $_[0];
    my ( $this, $that, $the_other_thing ) = split /\t/, $line;
    return { 'this' => $this, 'that' => $that, 'the_other_thing' => $the_other_thing};
}

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

sub fun {
    my $line = $_[0];
    my ( $this, $that, $the_other_thing ) = split /\t/, $line;
    return &to_hash( $this, $that, $the_other_thing );
}

Это помогает, когда количество элементов увеличивается. Как мне это сделать? Похоже, я мог бы комбинировать PadWalker и замыкания, но я бы хотел сделать это, используя только основной язык.

РЕДАКТИРОВАТЬ: THB предоставил умное решение этой проблемы, но я не проверял его, потому что он обходит много сложных частей (тм). Как бы вы это сделали, если бы хотели полагаться на семантику деструктуризации базового языка и отражать фактические переменные?

EDIT2: вот решение, на которое я намекал, используя PadWalker & closures:

use PadWalker qw( var_name );

# Given two arrays, we build a hash by treating the first set as keys and
# the second as values
sub to_hash {
    my $keys = $_[0];
    my $vals = $_[1];
    my %hash;
    @hash{@$keys} = @$vals;
    return \%hash;
}

# Given a list of variables, and a callback function, retrieves the
# symbols for the variables in the list.  It calls the function with
# the generated syms, followed by the original variables, and returns
# that output.
# Input is: Function, var1, var2, var3, etc....
sub with_syms {
    my $fun = shift @_;
    my @syms = map substr( var_name(1, \$_), 1 ), @_;
    $fun->(\@syms, \@_);
}

sub fun {
    my $line = $_[0];
    my ( $this, $that, $other) = split /\t/, $line;
    return &with_syms(\&to_hash, $this, $that, $other);
}

Ответы [ 4 ]

4 голосов
/ 29 марта 2012

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

Вместо этого вы можете использовать фрагмент хеша:

sub fun {
   my ($line) = @_;
   my %hash;
   @hash{qw( this that the_other_thing )} = split /\t/, $line;
   return \%hash;
}

Вы можете скрыть фрагмент в функции to_hash, если вы этого хотите.

sub to_hash {
   my $var_names = shift;
   return { map { $_ => shift } @$var_names };
}

sub fun_long {
   my ($line) = @_;
   my @fields = split /\t/, $line;
   return to_hash [qw( this that the_other_thing )] @fields;
}

sub fun_short {
   my ($line) = @_;
   return to_hash [qw( this that the_other_thing )], split /\t/, $line;
}

Но если вы настаиваете, вот версия PadWalker:

use Carp      qw( croak );
use PadWalker qw( var_name );

sub to_hash {
   my %hash;
   for (0..$#_) {
      my $var_name = var_name(1, \$_[$_])
         or croak("Can't determine name of \$_[$_]");
      $hash{ substr($var_name, 1) } = $_[$_];
   }
   return \%hash;
}

sub fun {
   my ($line) = @_;
   my ($this, $that, $the_other_thing) = split /\t/, $line;
   return to_hash($this, $that, $the_other_thing);
}
2 голосов
/ 28 марта 2012

Это делает это:

my @part_label = qw( part1 part2 part3 );

sub fun {
    my $line = $_[0];
    my @part = split /\t/, $line;
    my $no_part = $#part_label <= $#part ? $#part_label : $#part;
    return map { $part_label[$_] => $part[$_] } (0 .. $no_part);
}

Конечно, ваш код должен где-то называть части.Приведенный выше код делает это с помощью qw (), , но вы можете, если хотите, ваш код автоматически генерировать имена.

[Если вы ожидаете очень большой список * part_labels *, тогда вывероятно, следует избегать идиомы * (0 .. $ no_part) *, но для списков среднего размера она работает нормально.]

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

sub to_hash ($$) {
    my @var_name = @{shift()};
    my @value    = @{shift()};
    $#var_name == $#value or die "$0: wrong number of elements in to_hash()\n";
    return map { $var_name[$_] => $value[$_] } (0 .. $#var_name);
}

sub fun {
    my $line = $_[0];
    return to_hash [qw( this that the_other_thing )], [split /\t/, $line];
}
2 голосов
/ 29 марта 2012

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

Этот код, похоже, работает.Пожалуйста, объясните, если я вас неправильно понял.

use strict;
use warnings;

use Data::Dumper;
$Data::Dumper::Terse++;

my $line = "1111 2222 3333 4444 5555 6666 7777 8888 9999\n";

print Dumper to_hash($line, qw/ class division grade group kind level rank section tier  /);

sub to_hash {
  my @fields = split ' ', shift;
  my %fields = map {$_ => shift @fields} @_;
  return \%fields;
}

output

{
  'division' => '2222',
  'grade' => '3333',
  'section' => '8888',
  'tier' => '9999',
  'group' => '4444',
  'kind' => '5555',
  'level' => '6666',
  'class' => '1111',
  'rank' => '7777'
}

Для более общего решения, которое построит хэш из любых двух списковЯ предлагаю функцию zip_by из List::UtilsBy

use strict;
use warnings;

use List::UtilsBy qw/zip_by/;
use Data::Dumper;
$Data::Dumper::Terse++;

my $line = "1111 2222 3333 4444 5555 6666 7777 8888 9999\n";

my %fields = zip_by { $_[0] => $_[1] }
    [qw/ class division grade group kind level rank section tier  /],
    [split ' ', $line];

print Dumper \%fields;

Вывод идентичен выводу моего исходного решения.

См. Также pairwiseфункция из List::MoreUtils, которая принимает пару массивов вместо списка ссылок на массивы.

1 голос
/ 28 марта 2012

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

...