在 Perl 中反转哈希的键和值

Posted

技术标签:

【中文标题】在 Perl 中反转哈希的键和值【英文标题】:Inverting a Hash's Key and Values in Perl 【发布时间】:2011-09-02 00:09:15 【问题描述】:

我想让值成为键,键成为值。这样做的最佳方法是什么?

【问题讨论】:

在多个键映射到同一个值的情况下,你想做什么? 【参考方案1】:

改编自http://www.dreamincode.net/forums/topic/46400-swap-hash-values/:

假设您的哈希存储在$hash

while (($key, $value) = each %hash) 
   $hash2$value=$key;


%hash=%hash2;

似乎可以通过反向实现更优雅的解决方案 (http://www.misc-perl-info.com/perl-hashes.html#reverseph):

%nhash = reverse %hash;

请注意,使用反向时,重复的值将被覆盖。

【讨论】:

相反的方式很好,但它有一些警告(直接从 perl 文档粘贴):如果一个值在原始哈希中重复,则只有其中一个可以表示为倒置哈希。此外,这必须展开一个散列并构建一个全新的散列,这可能需要一些时间来处理大型散列,例如来自 DBM 文件的散列。 完全同意。在微不足道的情况下,我认为 reverse 很棒,但它不是您强调的通用解决方案。 重复值也会被 while 循环版本覆盖。 当然可以,但是在 while 循环中,您可以更轻松地添加代码来处理重复项... 我对此进行了测试。 “reverse”比“while”解决方案使用的内存略多。对于小散列,反向解决方案要快得多,并且通常更快一些,因为反向是内置的。对于非常大的散列,由于内存使用,反向更慢。但是哈希本身比反向所需的临时内存要大得多。使用反向在两个巨大的“虚拟哈希”(例如 DBM 文件)之间进行复制是错误的。 'map' 方法使用的内存最多,运行速度最慢。一般来说,我会使用反向。【参考方案2】:

使用reverse:

use Data::Dumper;

my %hash = ('month', 'may', 'year', '2011');
print Dumper \%hash;
%hash = reverse %hash;
print Dumper \%hash;

【讨论】:

【参考方案3】:

如前所述,最简单的是

my %inverse = reverse %original;

如果多个元素具有相同的值,它会“失败”。您可以创建一个 HoA 来处理这种情况。

my %inverse;
push @ $inverse $original$_  , $_ for keys %original;

【讨论】:

Hash::Util 中有一个invert() 方法会很方便,它默认推送一个HoA - 或者当它检测到有重复值时也是如此。也许perlcritic 警告密钥被建议push @ $inverted $hash$_ , $_ for keys %hash; 的重复项覆盖会更好。 Perl6's .invert method requires push to avoid the overwritten duplicate issue 也是。 我猜有些模式你想要在反转哈希时将重复值覆盖到一个键中。【参考方案4】:

所以你想要一个哈希中的反向键和值?所以使用反向... ;)

%hash2 = reverse %hash;

reverting (k1 => v1, k2 => v2) - yield (v2=>k2, v1=>k1) - 这就是你想要的。 ;)

【讨论】:

【参考方案5】:
my %orig_hash = (...);
my %new_hash;

%new_hash = map  $orig_hash$_ => $_  keys(%orig_hash);

【讨论】:

谁说错了,只是展示了另一种方法,因为已经提到了反向(-:【参考方案6】:

map-over-keys 解决方案更加灵活。如果你的值不是一个简单的值怎么办?

my %forward;
my %reverse;

#forward is built such that each key maps to a value that is a hash ref:
# a => 'something', b=> 'something else'

%reverse = map  join(',', @$_qw(a b)) => $_  keys %forward;

【讨论】:

【参考方案7】:

这是一种使用Hash::MultiValue 的方法。

use experimental qw(postderef);

sub invert 
  use Hash::MultiValue;
  my $mvh = Hash::MultiValue->from_mixed(shift);

  my $inverted;    
  $mvh->each( sub  push $inverted-> $_[1] ->@* , $_[0]  ) ;
  return $inverted;

要对此进行测试,我们可以尝试以下方法:

my %test_hash = (
  q => [qw/1 2 3 4/],
  w => [qw/4 6 5 7/],
  e => ["8"],
  r => ["9"],
  t => ["10"],
  y => ["11"],
);

my $wow  = invert(\%test_hash);
my $wow2 = invert($wow);

use DDP;
print "\n \%test_hash:\n\n" ;
p %test_hash;
print "\n \%test_hash inverted as:\n\n" ;
p $wow ;

# We need to sort the contents of the multi-value array reference
# for the is_deeply() comparison:
map  
   $test_hash$_ = [ sort  $a cmp $b || $a <=> $b  @ $test_hash$_  ] 
 keys %test_hash ; 

map  
   $wow2->$_ = [ sort  $a cmp $b || $a <=> $b  @ $wow2->$_  ] 
 keys %$wow2 ; 

use Test::More ;
is_deeply(\%test_hash, $wow2, "double inverted hash == original");
done_testing;

附录

请注意,为了通过这里的噱头测试,invert() 函数依赖于将数组引用作为值的%test_hash。如果您的散列值不是数组引用,要解决此问题,您可以将常规/混合散列“强制”为多值散列,Hash::MultiValue 然后可以将其添加到对象中。但是,这种方法意味着即使是单个值也会显示为数组引用:

for ( keys %test_hash )   
     if ( ref $test_hash$_ ne 'ARRAY' )  
           $test_hash$_  = [ $test_hash$_ ] 
      

这就是:

ref($_) or $_ = [ $_ ] for values %test_hash ;

这只需要通过“往返”测试。

【讨论】:

感谢 mst 和 #perl-help 的速记 :-)【参考方案8】:

假设您所有的值都是简单且唯一的字符串,这是一种更简单的方法。


%hash = ( ... );
@newhashvalues %hash = (keys %hash);

这称为散列片。由于您使用%newhash 生成密钥列表,因此您将% 更改为@

reverse() 方法不同,这将按照与原始哈希相同的顺序插入新的键和值。 keysvalues 总是以相同的顺序返回它们的值(each 也是如此)。

如果您需要对其进行更多控制,例如对其进行排序以便重复值获得所需的键,请使用两个散列切片。


%hash = ( ... );
@newhash @hashsort keys %hash  = (sort keys %hash);

【讨论】:

以上是关于在 Perl 中反转哈希的键和值的主要内容,如果未能解决你的问题,请参考以下文章

perl返回哈希和的键和值

如何交换散列中的键和值

从数组 ruby​​ 生成哈希键和值

Map里面的键和值可以为空吗

如何在 Bash 中间接获取关联数组的键和值?

在 TypeScript 中迭代对象的键和值