在Perl中,如何创建其键来自给定数组的哈希?


80

假设我有一个数组,而且我知道我会做很多“该数组是否包含X?” 检查。执行此操作的有效方法是将数组转换为哈希,其中的键是数组的元素,然后您可以说

if($ hash {X}){...}

有没有简单的方法可以执行此数组到哈希的转换?理想情况下,它应该足够通用以接受匿名数组并返回匿名哈希。

Answers:


120
%hash = map { $_ => 1 } @array;

它不像“ @hash {@array} = ...”解决方案那么短,但是那些解决方案要求哈希和数组已经在其他地方定义,而该解决方案可以采用匿名数组并返回匿名哈希。

这是将数组中的每个元素都与“ 1”配对。当此(键,1,键,1,键1)对的列表分配给哈希时,奇数的键成为哈希的键,偶数的键成为各自的值。


43
 @hash{@array} = (1) x @array;

这是一个散列片,是散列中的值的列表,因此它的前面是列表y @。

文档

如果您对为什么在哈希片上使用'@'而不是'%'感到困惑,请这样想。方括号的类型(方形或大写)决定要查看的是数组还是哈希。另一方面,数组或散列上的前导符号(“ $”或“ @”)表示您要返回的是奇异值(标量)还是复数(列表)。


1
哇,我从未听说过(或想到过)那个。谢谢!我在理解其工作方式时遇到了麻烦。您可以添加说明吗?特别是,如何获取名为%hash的哈希并使用@符号引用它?
raldi

2
raldi:这是一个哈希切片,是哈希中的值的列表,因此它的前面是列表y @。请参阅perldoc.perl.org/perldata.html#Slices-特别是本节的最后一段
2008年

您应该将其添加到答案中!
raldi

您能解释一下RHS吗?谢谢。
Susheel Javadi

1
(列表)x $ number复制列表$ number次。在标量上下文中使用数组将返回元素的数量,因此(1)x @array是1的列表,其长度与@array相同。
moritz

39
@hash{@keys} = undef;

在这里用引用哈希的语法@是哈希切片。我们基本上是说$hash{$keys[0]}AND $hash{$keys[1]}AND $hash{$keys[2]}...是=左侧的一个列表,是一个左值,并且我们正在分配给该列表,该列表实际上进入哈希并为所有命名键设置值。在这种情况下,我仅指定了一个值,因此该值进入$hash{$keys[0]},而其他哈希条目都使用未定义的值自动生存(生效)。[我最初的建议是将表达式设置为1,将一个键设置为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,而其他一些解决方案则使工作陷入困境,以构建要输入的值数组哈希;完全浪费了精力)。


1
这个优先于另一个,因为它不会创建临时列表来初始化哈希。这应该更快并且消耗更少的内存。
Leon Timmermans

1
冷淡:您必须先声明“我的%hash”,然后声明“ @hash {@arr} = 1”(不要声明“ my”)。
Michael Carman

8
= (),不是= undef,只是为了保持一致性,即隐式地对所有值使用undef,而不仅仅是在第一个值之后。(如这些注释所示,很难看到undef并且认为可以将其更改为1并影响所有哈希值。)
ysth

2
由于值在此处最终以“ undef”结尾(而且可能不是您想的那样,正如ysth所指出的那样),您不能只在代码中使用“ if($($ hash {$ value})”这样的哈希。您需要“ if(存在$ hash {$ value})”。
戴夫·克罗斯

2
它会是不错的,如果你编辑你的答案指出,它需要与存在的使用,存在是比实际加载的哈希值检查感实性更有效,而民主基金需要较少的空间比1
bhollis


8

我一直以为

foreach my $item (@array) { $hash{$item} = 1 }

至少很好并且可读/可维护。


7

这里有一个前提,即做很多“数组是否包含X?”的最有效方法。检查是将数组转换为哈希。效率取决于稀缺的资源,通常取决于时间,但有时取决于空间,有时取决于程序员的工作量。通过同时保留一个列表和该列表的哈希,您至少要使消耗的内存增加一倍。另外,您还要编写更多原始代码,这些代码需要进行测试,记录等。

作为替代方案,看看一览:: MoreUtils模块,具体的功能any()none()true()false()。它们都以一个块作为条件,并以一个列表作为参数,类似于map()grep()

print "At least one value undefined" if any { !defined($_) } @list;

我进行了一个快速测试,将/ usr / share / dict / words的一半加载到一个数组(25000个单词)中,然后从数组中整个字典(每第5000个单词)中选择11个单词,同时使用这两个数组哈希方法和 any()List :: MoreUtils中函数。

在从源代码构建的Perl 5.8.8上,array-to-hash方法的运行速度比sash方法快了1100倍。 any()方法快1100倍(在Ubuntu 6.06打包的Perl 5.8.7中,速度快1300倍)。

但是,这还不是全部内容-数组到哈希的转换大约需要0.04秒,在这种情况下,数组到哈希方法的时间效率要比数组到哈希方法快1.5到2倍。 any()方法。仍然不错,但还不及恒星。

我的直觉是,any()在大多数情况下,数组到哈希的方法将胜过,但是如果我有一些更可靠的指标(很多测试用例,体面的统计分析,也许一些大型的,每种方法的算法分析,等等。)根据您的需求,List :: MoreUtils可能是更好的解决方案;它当然更加灵活,并且需要更少的编码。记住,过早的优化是一种罪过... :)


这不能回答问题。它还错过了要点……从数组到哈希的转换仅发生一次……在程序的运行时间中总共增加了0.04秒(在2008年),而查找发生了很多次。
吉姆·巴尔特

2
我试图解决根本问题,而不仅仅是回答问题。List::MoreUtils取决于使用情况,可能是合适的方法,也可能不是合适的方法。您的用例可能有很多查找;其他人可能不会。关键是数组到哈希的转换和List::MoreUtils解决确定成员资格的潜在问题。知道多种方法可以让您针对特定用例选择最佳方法。
arclight


5

同样值得一提的是完整性,我通常使用2个相同长度的数组来做到这一点的方法@keys@vals而您更希望使用的是哈希值...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);


4
通常的成语@keys-1$#keys
Stefan Majewsky

@StefanMajewsky我有一段时间没有真正看到它了。我自己远离它-这很丑。
Tamzin Blake 2012年

3

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;

1
$ _ => 1和$ _,1之间的区别纯粹是风格上的。我个人更喜欢=>,因为它似乎可以更明确地指示键/值链接。您的@hash {@array} = 1解决方案不起作用。只有值(与@array第一密钥相关联的一个)中的一个被设置为1
戴夫十字

2

您也可以使用Perl6 :: Junction

use Perl6::Junction qw'any';

my @arr = ( 1, 2, 3 );

if( any(@arr) == 1 ){ ... }

1
如果对大型阵列执行多次操作,则速度可能会慢很多。
ysth

1
实际执行一次要慢很多。它必须创建一个对象。然后不久,它将摧毁那个物体。这仅仅是可能的例子。
布拉德·吉尔伯特

1

如果您进行了大量的设定理论运算-您也可以使用Set :: Scalar或类似的模块。然后$s = Set::Scalar->new( @array )将为您构建集-您可以使用进行查询$s->contains($m)


1

如果不想污染名称空间,可以将代码放入子例程中。

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;

%hash = map{ $_, undef } @keylist
布莱德·吉尔伯特

1
#!/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'
        };

hasref在这里似乎有点夸张。
bobbogo 2014年

0

您可能还想查看Tie :: IxHash,它实现了有序的关联数组。这样一来,您就可以在一个数据副本上执行两种类型的查找(哈希和索引)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.