假设我有一个数组,而且我知道我会做很多"该数组是否包含X?" 检查。 执行此操作的有效方法是将数组转换为哈希,其中键是数组的元素,然后您可以说
有没有简单的方法可以执行此数组到哈希的转换? 理想情况下,它应该足够通用以接受匿名数组并返回匿名哈希。
1
| %hash = map { $_ => 1 } @array; |
它不如" @hash {@array} = ..."解决方案那么短,但是那些解决方案要求哈希和数组已经在其他地方定义,而该解决方案可以采用匿名数组并返回匿名哈希。
这是将数组中的每个元素都与" 1"配对。当此(键,1,键,1,键1)对的列表分配给哈希时,奇数的键成为哈希的键,而偶数的键成为各自的值。
1
| @hash{@array} = (1) x @array; |
这是一个散列片,是散列中的值的列表,因此它的前面是列表y @。
从文档:
If you're confused about why you use
an '@' there on a hash slice instead
of a '%', think of it like this. The
type of bracket (square or curly)
governs whether it's an array or a
hash being looked at. On the other
hand, the leading symbol ('$' or '@')
on the array or hash indicates whether
you are getting back a singular value
(a scalar) or a plural one (a list).
在这里使用@引用哈希的语法是哈希切片。我们基本上是说$hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ...是=左侧的一个列表,即一个左值,我们正在分配给该列表,该列表实际上进入了哈希并设置所有命名键的值。在这种情况下,我仅指定一个值,以便该值进入$hash{$keys[0]},而其他哈希条目都使用未定义的值自动生存(生效)。 [我最初的建议是将表达式设置为1,这会将一个键设置为1,将另一个键设置为undef。为了保持一致性,我对其进行了更改,但是正如我们将在下面看到的,确切的值无关紧要。]
当您意识到左值(即=左侧的表达式)是从哈希表中构建出来的列表时,那么就会开始理解为什么我们使用该@。 [除了我认为这会在Perl 6中改变。]
这里的想法是您使用哈希作为集合。重要的不是我分配的值;只是钥匙的存在。因此,您要做的不是:
1
| if ($hash{$key} == 1) # then key is in the hash |
代替:
1
| if (exists $hash{$key}) # then key is in the set |
实际上,仅运行exists检查要比打乱散列中的值更有效,尽管对我而言,重要的只是用散列键表示集合的概念。另外,有人指出,通过在此处使用undef作为值,我们将比分配值消耗更少的存储空间。 (并且也减少了混乱,因为该值无关紧要,而且我的解决方案将只为哈希中的第一个元素分配一个值,而将其他值保留为undef,而其他一些解决方案则使车轮转向构建值的数组进入哈希表;完全浪费了精力)。
请注意,如果键入if ( exists $hash{ key } )不太适合您(我宁愿使用它,因为感兴趣的问题实际上是键的存在而不是键值的真实性),那么您可以使用简短易懂的键
我一直以为
1
| foreach my $item (@array) { $hash{$item} = 1 } |
至少很好并且可读/可维护。
这里有一个前提,即做很多"数组是否包含X?"的最有效方法。检查是将数组转换为哈希。效率取决于稀缺的资源,通常取决于时间,但有时取决于空间,有时取决于程序员的工作量。通过同时保留一个列表和该列表的哈希,您至少要使所消耗的内存增加一倍。另外,您还要编写更多原始代码,这些代码需要进行测试,记录等。
或者,查看List :: MoreUtils模块,特别是功能any(),none(),true()和false()。它们都以一个块为条件,并以一个列表作为参数,类似于map()和grep():
print"At least one value undefined" if any { !defined($_) } @list;
我进行了一个快速测试,将/ usr / share / dict / words的一半加载到一个数组(25000个单词)中,然后从数组中整个字典(每第5000个单词)中选择11个单词,同时使用这两个数组-to-hash方法和List :: MoreUtils中的any()函数。
在从源代码构建的Perl 5.8.8上,array-to-hash方法的运行速度比any()方法快1100倍(在Ubuntu 6.06打包的Perl 5.8.7中,速度快1300倍)。
但是,这还不是全部内容-数组到哈希的转换大约需要0.04秒,在这种情况下,数组到哈希方法的时间效率比any()方法快1.5到2倍。仍然不错,但不及恒星。
我的直觉是,在大多数情况下,数组到哈希方法将胜过any(),但是如果我有一些更可靠的指标(很多测试用例,不错的统计分析,也许每种方法都需要一些big-O算法分析,等等。)根据您的需求,List :: MoreUtils可能是一个更好的解决方案;它当然更加灵活,并且需要更少的编码。记住,过早的优化是一种罪过... :)
同样值得一提的是完整性,我使用2个相同长度的数组@keys和@vals进行此操作的常用方法是一个哈希...
my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
在perl 5.10中,有一个接近魔术的~~运算符:
1 2 3 4
| sub invite_in {
my $vampires = [ qw(Angel Darla Spike Drusilla) ];
return ($_[0] ~~ $vampires) ? 0 : 1 ;
} |
看到这里:http://dev.perl.org/perl5/news/2007/perl-5.10.0.html
Raldi的解决方案可以做到这一点(不需要原始的'=>'):
1
| my %hash = map { $_,1 } @array; |
此技术还可用于将文本列表转换为哈希:
1
| my %hash = map { $_,1 } split(",",$line) |
此外,如果您有一行这样的值:" foo = 1,bar = 2,baz = 3",则可以执行以下操作:
1
| my %hash = map { split("=",$_) } split(",",$line); |
[编辑包括]
提供的另一种解决方案(需要两行)是:
1 2 3 4 5
| 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。
1 2 3 4 5
| use Perl6::Junction qw'any';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... } |
1 2 3 4 5 6 7 8 9 10 11 12
| #!/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)
1 2 3 4 5 6 7
| $VAR1 = {
'8' => '2',
'4' => '3',
'9' => '1',
'2' => '5',
'5' => '4'
}; |
如果不想污染名称空间,可以将代码放入子例程。
1 2 3 4 5 6
| my $hash_ref =
sub{
my %hash;
@hash{ @{[ qw'one two three' ]} } = undef;
return \\%hash;
}->(); |
甚至更好:
1 2 3 4 5 6 7 8 9 10 11 12
| 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; |
如果您真的想传递数组引用:
1 2 3 4 5 6 7 8
| sub keylist(\\@){
my %hash;
@hash{ @{$_[0]} } = undef if @_;
return \\%hash;
}
my @key_list = qw'one two three';
my $hash_ref = keylist @key_list; |
如果您进行了大量的设定理论运算-您也可以使用Set :: Scalar或类似的模块。然后$s = Set::Scalar->new( @array )将为您构建Set-您可以使用以下命令进行查询:$s->contains($m)。
您可能还想签出Tie :: IxHash,它实现了有序的关联数组。这样一来,您就可以在数据的一个副本上进行两种类型的查找(哈希和索引)。