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

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

【问题讨论】:

  • 在多个键映射到同一个值的情况下,你想做什么?

标签: perl hash


【解决方案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 = ( ... );
        @newhash{values %hash} = (keys %hash);
        
        

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

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

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

        
        %hash = ( ... );
        @newhash{ @hash{sort keys %hash} } = (sort keys %hash);
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-12-30
          • 1970-01-01
          • 2014-07-23
          • 1970-01-01
          • 2023-04-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多