Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to iterate through a hash in sorted order using the while(my($key, $value) ... ) {} method?

For a hash of this format:

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,
    },
    ...
}

I have this code that correctly iterates through the hash:

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

However, the order that these items are iterated in seems to be pretty random. Is it possible to use the same while format to iterate through them in the order specified by the 'order' key in the hash for each item?

(I know I can sort the keys first and then foreach loop through it. Just looking to see if there is cleaner way to do this.)

like image 461
Vidur Avatar asked Nov 27 '22 21:11

Vidur


2 Answers

You can do some thing like :

foreach my $key (sort keys %{$itemHash}) {
    print "$key : " . $itemHash->{$key}{name} . "\n";
}
like image 62
sunil_mlec Avatar answered Dec 10 '22 03:12

sunil_mlec


The concept of an "ordered hash" is wrong. While an array is an ordered list of elements, and therefore accessible by index, a hash is an (un-ordered) collection of key-value pairs, where the keys are a set.

To accomplish your task, you would have to sort the keys by the order property:

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

You can then create the key-value pairs via map:

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

We could wrap this up into a sub:

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

to get an even-sized list of key-value elements, and

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});
  };
}

to create a callback that is a drop-in for the each.

We can then do:

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

There is a severe drawback to this approach: each only uses two elements at a time, and thus operates in constant memory. This solution has to read in the whole hash and store an array of all keys. Unnoticable with small hashes, this can get important with a very large amount of elements.

like image 30
amon Avatar answered Dec 10 '22 03:12

amon