我发现自己在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为这个问题提供了一个聪明的解决方案,但我没有检查它,因为它绕过了很多困难的部分(tm)。如果你想依赖核心语言的解构语义,并将你的反射从实际变量中赶走,你会怎么做?
第二版:以下是我在使用PadWalker&闭包:
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);
}
您可以使用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);
}
这样做:
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)*习惯用法,但对于中等大小的列表,它可以正常工作。]
更新以回应OP的以下评论:您提出了一个有趣的挑战。我喜欢它。下面的离你想要的有多近?
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];
}
如果我理解你的意思,你想通过将给定的键序列分配给从数据记录中分离的值来构建哈希。
这个代码似乎起到了作用。如果我误解了你,请解释一下。
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Terse++;
my $line = "1111 2222 3333 4444 5555 6666 7777 8888 9999n";
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;
}
输出
{
'division' => '2222',
'grade' => '3333',
'section' => '8888',
'tier' => '9999',
'group' => '4444',
'kind' => '5555',
'level' => '6666',
'class' => '1111',
'rank' => '7777'
}
对于从任意两个列表构建哈希的更通用的解决方案,我建议使用List::UtilsBy
中的zip_by
函数
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 9999n";
my %fields = zip_by { $_[0] => $_[1] }
[qw/ class division grade group kind level rank section tier /],
[split ' ', $line];
print Dumper %fields;
输出与我的初始解决方案相同。
另请参阅List::MoreUtils
中的pairwise
函数,该函数采用一对数组,而不是数组引用列表。
除了自己解析Perl代码之外,仅使用核心语言是不可行的to_hash
函数。被调用的函数不知道这些参数是变量、其他函数的返回值、字符串文字,还是你。。。更不用说他们的名字了。它不在乎,也不应该在乎。