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

时间:2020-03-06 14:22:58  来源:igfitidea点击:

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

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

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

解决方案

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

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

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

@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",而其他一些解决方案则使车轮转向构建值的数组进入哈希表;完全浪费了精力)。

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;

@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).

我们也可以使用Perl6 :: Junction。

use Perl6::Junction qw'any';

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

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

在perl 5.10中,有一个接近魔术的~~运算符:

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

请注意,如果键入if(exist $ hash {key})对我们来说不是太多工作(我更喜欢使用它,因为感兴趣的事情实际上是键的存在而不是键值的真实性),可以用简短而甜蜜的

@hash{@key} = ();

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

或者,查看List :: MoreUtils模块,特别是函数any(),none(),true()和false()。它们都以块为条件,以列表为参数,类似于map()和grep():

如果有任何{!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()",但是如果我有一些更可靠的指标(很多测试用例,不错的统计分析),我会感觉好很多。 ,可能是每种方法的一些大O算法分析,等等。)根据需求,List :: MoreUtils可能是一个更好的解决方案。它当然更灵活,并且需要更少的编码。记住,过早的优化是一种罪过... :)

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

如果我们进行了很多设置理论运算,则也可以使用Set :: Scalar或者类似的模块。然后$ s = Set :: Scalar-> new(@array)将为我们构建Set,我们可以使用$ s-> contains($ m)进行查询。

我一直以为

foreach my $item (@array) { $hash{$item} = 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;