Вот способ сделать это, используя 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 ;
Это понадобится только для прохождения теста «туда-обратно».