在 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()
方法不同,这将按照与原始哈希相同的顺序插入新的键和值。 keys
和 values
总是以相同的顺序返回它们的值(each
也是如此)。
如果您需要对其进行更多控制,例如对其进行排序以便重复值获得所需的键,请使用两个散列切片。
%hash = ( ... );
@newhash @hashsort keys %hash = (sort keys %hash);
【讨论】:
以上是关于在 Perl 中反转哈希的键和值的主要内容,如果未能解决你的问题,请参考以下文章