Можно ли перебирать хеш в отсортированном порядке, используя метод while(my($key, $value)) {}?

Для хэша этого формата:

my $itemHash = {
    tag1 => {
        name => "Item 1",
        order => 1,
        enabled => 1,
    },
    tag2 => {
        name => "Item 2",
        order => 2,
        enabled => 0,
    },
    tag3 => {
        name => "Item 3",
        order => 3,
        enabled => 1,
    },
    ...
}

У меня есть этот код, который правильно перебирает хеш:

keys %$itemHash; # Resets the iterator
while(my($tag, $item) = each %$itemHash) {
    print "$tag is $item->{'name'}"
}

Однако порядок, в котором повторяются эти элементы, кажется довольно случайным. Можно ли использовать один и тот же формат while для их повторения в порядке, указанном ключом «порядок» в хэше для каждого элемента?

(Я знаю, что могу сначала отсортировать ключи, а затем выполнить цикл foreach. Просто смотрю, есть ли более чистый способ сделать это.)


person Vidur    schedule 25.09.2012    source источник
comment
Возможно, стоит взглянуть на модуль Tie::IxHash, хотя вам придется либо изменить свой код, чтобы вставить его в хэш в порядке, соответствующем ключу «порядок», либо иногда вызывать метод Tie::IxHash Reorder, чтобы расположить все в правильном порядке.   -  person rohanpm    schedule 26.09.2012


Ответы (4)


Концепция «упорядоченного хэша» неверна. В то время как массив представляет собой упорядоченный список элементов и, следовательно, доступен по индексу, хэш — это (неупорядоченная) коллекция пар ключ-значение, где ключи представляют собой набор.

Чтобы выполнить вашу задачу, вам нужно будет отсортировать ключи по свойству order:

my @sorted = sort {$hash{$a}{order} <=> $hash{$b}{order}} keys %$itemHash;

Затем вы можете создать пары ключ-значение через map:

my @sortedpairs = map {$_ => $itemHash->{$_}} @sorted;

Мы могли бы обернуть это в подпрограмму:

sub ridiculousEach {
  my %hash = @_;
  return map
      {$_ => $hash{$_}}
        sort
          {$hash{$a}{order} <=> $hash{$b}{order}}
             keys %hash;
}

чтобы получить список элементов ключ-значение четного размера, и

sub giveIterator {
  my %hash = @_;
  my @sorted = sort {$hash{$a}{order} <=> $hash{$b}{order}} keys %hash;
  return sub {
     my $key = shift @sorted;
     return ($key => $hash{$key});
  };
}

чтобы создать обратный вызов, который является раскрывающимся для каждого.

Затем мы можем сделать:

my $iterator = giveIterator(%$itemHash);
while (my ($tag, $item) = $iterator->()) {
  ...;
}

У этого подхода есть серьезный недостаток: each использует одновременно только два элемента и, таким образом, работает в постоянной памяти. Это решение должно считывать весь хеш и сохранять массив всех ключей. Незаметно с маленькими хешами, это может стать важным с очень большим количеством элементов.

person amon    schedule 25.09.2012
comment
Да, отсортированный хеш, вероятно, не лучший способ описать это, но я пытался быть кратким в названии. Спасибо! - person Vidur; 26.09.2012

Вы можете сделать что-то вроде:

foreach my $key (sort keys %{$itemHash}) {
    print "$key : " . $itemHash->{$key}{name} . "\n";
}
person sunil_mlec    schedule 12.10.2012
comment
Спасибо, намного лучше, чем другие ответы! - person Mark K Cowan; 29.03.2017

Порядок, в котором ключи из хэша не определены. Итак, вам нужно рассортировать ключи. Одним из способов, как вы сказали, было бы вытащить ключи и отсортировать их, а затем прокрутить ключи.

Другой способ — сортировать их на лету. Я не уверен, что вы считаете это более чистым. Что-то вроде:

for my $key ( sort { $itemHash->{$a}{order} <=> $itemhash->{$b}{order} } keys %$itemHash ) {
  print "$key is $itemHash->{$key}{name}";
}
person harleypig    schedule 25.09.2012
comment
Ага. Я знал об этом методе. Мне было интересно, есть ли короткий способ сделать то же самое с циклом while - person Vidur; 26.09.2012

Это будет совсем чище. мы должны использовать cmp для сортировки строк.

my $itemHash = {
    tag1 => {
        name => "Item 1",
        order => 1,
        enabled => 1,
    },
    tag2 => {
        name => "Item 2",
        order => 2,
        enabled => 0,
    },
    tag3 => {
        name => "Item 3",
        order => 3,
        enabled => 1,
    }
};

foreach((sort{$a cmp $b}(keys(%$itemHash)))){
print "$_ is $itemHash->{$_}->{'name'}\n";
}
person Saravanan    schedule 27.09.2012