题
假设我有一个数组,我知道我会做很多“阵列包含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;
这里的语法是指用一个 @
是一个哈希片。我们基本上是说 $hash{$keys[0]}
和 $hash{$keys[1]}
和 $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
作为此处的值,我们将消耗比分配值更少的存储空间。(并且也减少了混乱,因为值并不重要,我的解决方案将仅向散列中的第一个元素分配一个值,而将其他元素分配给 undef
, ,以及其他一些解决方案正在转动侧手翻来构建一个值数组以进入哈希;完全白费力气)。
请注意,如果输入 if ( exists $hash{ key } )
对你来说并没有太多的工作(我更喜欢使用它,因为感兴趣的问题实际上是密钥的存在而不是其值的真实性),那么你可以使用简短而甜蜜的
@hash{@key} = ();
这里有一个预设,是做很多“阵列包含x”的最有效方法?检查是将数组转换为哈希。效率取决于稀缺资源,通常是时间,有时是空间,有时是程序员的努力。同时保存列表和列表的散列所消耗的内存至少增加了一倍。另外,您正在编写更多需要测试、记录等的原始代码。
作为替代方案,请查看 List::MoreUtils 模块,特别是函数 any()
, none()
, true()
和 false()
. 。它们都以块作为条件,以列表作为参数,类似于 map()
和 grep()
:
print "At least one value undefined" if any { !defined($_) } @list;
我进行了一个快速测试,将 /usr/share/dict/words 的一半加载到数组中(25000 个单词),然后使用数组查找从整个字典中选择的 11 个单词(每 5000 个单词)。 -to-hash 方法和 any()
List::MoreUtils 中的函数。
在从源代码构建的 Perl 5.8.8 上,数组到哈希方法的运行速度几乎比 any()
方法(在 Ubuntu 6.06 的打包 Perl 5.8.7 下快 1300 倍。)
然而,这并不是完整的故事 - 数组到哈希的转换大约需要 0.04 秒,在这种情况下,数组到哈希方法的时间效率比之前的方法快 1.5 倍到 2 倍 any()
方法。仍然不错,但远没有那么出色。
我的直觉是数组到哈希的方法将会击败 any()
在大多数情况下,但如果我有一些更可靠的指标(大量的测试用例,体面的统计分析,也许对每种方法进行一些大O算法分析等),我会感觉好多了。根据您的需求,列出::MoreUtils 可能是一个更好的解决方案;它当然更灵活并且需要更少的编码。请记住,过早优化是一种罪过......:)
我一直以为
foreach my $item (@array) { $hash{$item} = 1 }
至少是好的并且可读/可维护。
在 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::连接点.
use Perl6::Junction qw'any';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... }
如果你做了很多集合论运算 - 你也可以使用 设置::标量 或类似的模块。然后 $s = Set::Scalar->new( @array )
将为您构建集合 - 您可以使用以下方式查询它: $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;
您可能还想查看 领带::IxHash, ,它实现有序关联数组。这将允许您对一份数据副本执行两种类型的查找(哈希和索引)。
#!/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'
};