Я хотел бы сделать значение ключом, а ключ - значением. Каков наилучший способ сделать это?
Инвертирование ключа хэша и значений в Perl
Ответ 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;
Обратите внимание, что с обратным, дублирующиеся значения будут перезаписаны.
Ответ 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;
Ответ 4
my %orig_hash = (...);
my %new_hash;
%new_hash = map { $orig_hash{$_} => $_ } keys(%orig_hash);
Ответ 5
Итак, вы хотите использовать обратные ключи и vals в хеше? Так что используйте reverse...;)
%hash2 = reverse %hash;
возврат (k1 = > v1, k2 = > v2) - выход (v2 = > k2, v1 = > k1) - и это то, что вы хотите.;)
Ответ 6
Решение по сравнению с ключами более гибкое. Что делать, если ваше значение не является простым значением?
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;
Добавление
Обратите внимание, что для прохождения теста gimmicky здесь функция 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 ;
Это нужно было бы только для того, чтобы пройти тест "туда и обратно".