Подтвердить что ты не робот

Инвертирование ключа хэша и значений в Perl

Я хотел бы сделать значение ключом, а ключ - значением. Каков наилучший способ сделать это?

4b9b3361

Ответ 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 ;

Это нужно было бы только для того, чтобы пройти тест "туда и обратно".