假设我有一个数组,我知道我会做很多“数组是否包含 X?” 检查。执行此操作的有效方法是将该数组转换为哈希,其中键是数组的元素,然后您可以说
if($hash{X}) { ... }
有没有一种简单的方法来进行这种数组到哈希的转换?理想情况下,它应该足够通用,可以采用匿名数组并返回匿名哈希。
%hash = map { $_ => 1 } @array;
它不像“@hash{@array} = ...”解决方案那么短,但那些需要哈希和数组已经在其他地方定义,而这个可以采用匿名数组并返回匿名哈希。
它的作用是获取数组中的每个元素并将其与“1”配对。当这个 (key, 1, key, 1, key 1) 对的列表被分配给一个散列时,奇数的成为散列的键,偶数的成为各自的值。
@hash{@array} = (1) x @array;
它是一个散列切片,一个来自散列的值列表,所以它在前面得到 list-y @。
从文档:
如果你对为什么在哈希切片上使用“@”而不是“%”感到困惑,可以这样想。括号的类型(方括号或花括号)决定了要查看的是数组还是散列。另一方面,数组或散列上的前导符号('$' 或 '@')表示您返回的是单数(标量)还是复数(列表)。
@hash{@keys} = undef;
此处使用 an 引用散列的语法@
是散列切片。我们基本上说$hash{$keys[0]}
AND $hash{$keys[1]}
AND $hash{$keys[2]}
... 是 = 左侧的一个列表,一个左值,我们分配给该列表,它实际上进入散列并设置所有命名键的值。在这种情况下,我只指定了一个值,因此该值进入$hash{$keys[0]}
,而其他哈希条目都使用未定义的值自动激活(变得生动)。[我最初的建议是设置表达式 = 1,这会将一个键设置为 1,将其他键设置为undef
. 为了保持一致性,我对其进行了更改,但正如我们将在下面看到的,确切的值并不重要。]
当您意识到左值(= 左侧的表达式)是一个由散列构建的列表时,我们就会开始理解为什么我们使用它@
。[除了我认为这将在 Perl 6 中改变。]
这里的想法是您将哈希作为一个集合使用。重要的不是我分配的价值;这只是钥匙的存在。所以你想要做的不是这样的:
if ($hash{$key} == 1) # then key is in the hash
反而:
if (exists $hash{$key}) # then key is in the set
实际上,只运行exists
检查比处理散列中的值更有效,尽管对我来说这里重要的只是你只用散列的键表示一个集合的概念。另外,有人指出,通过undef
在这里使用 as 值,我们将消耗比分配值更少的存储空间。(并且也减少了混乱,因为值无关紧要,我的解决方案将只为散列中的第一个元素分配一个值而留下其他元素,undef
而其他一些解决方案正在转动车轮以构建一个要进入的值数组哈希;完全浪费精力)。
请注意,如果打字if ( exists $hash{ key } )
对您来说不是太多工作(我更喜欢使用它,因为感兴趣的问题实际上是键的存在而不是其值的真实性),那么您可以使用简短而甜蜜的
@hash{@key} = ();
我一直以为
foreach my $item (@array) { $hash{$item} = 1 }
至少很好并且可读/可维护。
这里有一个预设,即做很多“数组是否包含 X?”的最有效方法。检查是将数组转换为哈希。效率取决于稀缺资源,通常是时间,但有时是空间,有时是程序员的努力。通过同时保存列表和列表的哈希值,您至少会增加一倍的内存消耗。另外,您正在编写更多需要测试、记录等的原始代码。
作为替代方案,请查看 List::MoreUtils 模块,特别是函数any()
、none()
和。它们都将块作为条件,将列表作为参数,类似于and :true()
false()
map()
grep()
print "At least one value undefined" if any { !defined($_) } @list;
我进行了快速测试,将 /usr/share/dict/words 的一半加载到一个数组(25000 个单词),然后在数组中查找从整个字典(每 5000 个单词)中选择的 11 个单词,同时使用数组-to-hash 方法和any()
List::MoreUtils 中的函数。
在从源代码构建的 Perl 5.8.8 上,array-to-hash 方法的运行速度几乎是该any()
方法的 1100 倍(在 Ubuntu 6.06 打包的 Perl 5.8.7 下快 1300 倍。)
然而,这还不是全部——数组到哈希的转换大约需要 0.04 秒,在这种情况下,数组到哈希方法的时间效率比该any()
方法快 1.5 到 2 倍。仍然很好,但没有那么出色。
我的直觉是,any()
在大多数情况下,数组到哈希的方法会胜出,但如果我有一些更可靠的指标(大量测试用例、体面的统计分析,也许还有一些大的—— O 每种方法的算法分析等)根据您的需要,List::MoreUtils 可能是更好的解决方案;它当然更灵活,需要更少的编码。请记住,过早优化是一种罪过... :)
在 perl 5.10 中,有一个近乎神奇的 ~~ 运算符:
sub invite_in {
my $vampires = [ qw(Angel Darla Spike Drusilla) ];
return ($_[0] ~~ $vampires) ? 0 : 1 ;
}
同样值得注意的是完整性,我通常使用 2 个相同长度的数组执行此操作的方法,@keys
而@vals
您更喜欢使用哈希...
my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
Raldi 的解决方案可以收紧到这一点(原始的 '=>' 不是必需的):
my %hash = map { $_,1 } @array;
此技术还可用于将文本列表转换为哈希:
my %hash = map { $_,1 } split(",",$line)
此外,如果您有这样的一行值: "foo=1,bar=2,baz=3" 您可以这样做:
my %hash = map { split("=",$_) } split(",",$line);
[编辑包括]
提供的另一个解决方案(需要两行)是:
my %hash;
#The values in %hash can only be accessed by doing exists($hash{$key})
#The assignment only works with '= undef;' and will not work properly with '= 1;'
#if you do '= 1;' only the hash key of $array[0] will be set to 1;
@hash{@array} = undef;
你也可以使用Perl6::Junction。
use Perl6::Junction qw'any';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... }
如果你做了很多集合论运算 - 你也可以使用Set::Scalar或类似的模块。然后$s = Set::Scalar->new( @array )
将为您构建 Set - 您可以使用以下方式查询它:$s->contains($m)
.
如果您不想污染名称空间,可以将代码放入子例程中。
my $hash_ref =
sub{
my %hash;
@hash{ @{[ qw'one two three' ]} } = undef;
return \%hash;
}->();
甚至更好:
sub keylist(@){
my %hash;
@hash{@_} = undef;
return \%hash;
}
my $hash_ref = keylist qw'one two three';
# or
my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
如果你真的想传递一个数组引用:
sub keylist(\@){
my %hash;
@hash{ @{$_[0]} } = undef if @_;
return \%hash;
}
my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my @a = qw(5 8 2 5 4 8 9);
my @b = qw(7 6 5 4 3 2 1);
my $h = {};
@{$h}{@a} = @b;
print Dumper($h);
给出(注意重复的键获得数组中最大位置的值 - 即 8->2 而不是 6)
$VAR1 = {
'8' => '2',
'4' => '3',
'9' => '1',
'2' => '5',
'5' => '4'
};
您可能还想查看Tie::IxHash,它实现了有序关联数组。这将允许您在一份数据副本上进行两种类型的查找(散列和索引)。