tags:

views:

97

answers:

1

Don't want to sort the entries.

using this does not preserve the order as well

 foreach my $val (keys %hash) {
     ...
 } 
+9  A: 

Hashes are unordered by default in Perl 5. You can use tie and Tie::IxHash to override this behavior. Be warned though, there is a performance hit and other considerations (like the fact that the order will not be preserved in copies).

#!/usr/bin/perl

use strict;
use warnings;

use Tie::IxHash;

tie my %hash, "Tie::IxHash"
    or die "could not tie %hash";

$hash{one}   = 1;
$hash{two}   = 2;
$hash{three} = 3;

for my $k (keys %hash) {
    print "$k $hash{$k}\n";
}

A better option may be to use an array or a hash of hashes:

#!/usr/bin/perl

use strict;
use warnings;

my %hash;
$hash{one}   = { data => 1, order => 1 };
$hash{three} = { data => 3, order => 2 };
$hash{two}   = { data => 2, order => 3 };

for my $k (sort { $hash{$a}{order} <=> $hash{$b}{order} } keys %hash) {
    print "$k $hash{$k}{data}\n";
}

As for performance, here are the results of a benchmark:

hash: e, c, a, b, d, f
IndexedOO: a, b, c, d, e, f
IxHash: a, b, c, d, e, f
hoh_pis: a, b, c, d, e, f
hoh: a, b, c, d, e, f
IxHashOO: a, b, c, d, e, f
Indexed: a, b, c, d, e, f
           Rate   IxHash       hoh  Indexed IxHashOO  hoh_pis IndexedOO     hash
IxHash    108/s       --      -34%     -39%     -56%     -67%      -70%     -85%
hoh       163/s      51%        --      -8%     -33%     -51%      -55%     -77%
Indexed   177/s      64%        9%       --     -27%     -46%      -52%     -75%
IxHashOO  243/s     125%       49%      37%       --     -26%      -33%     -65%
hoh_pis   330/s     205%      102%      86%      36%       --      -10%     -53%
IndexedOO 366/s     238%      124%     106%      50%      11%        --     -48%
hash      704/s     551%      332%     297%     189%     113%       92%       --

From this we can see that using Tie::Hash::Indexed in its OO capacity (rather than its tied capacity) is the best of indexed hash methods and is about half the speed of an unordered hash. The HoH method is pretty close if you use a custom sort that takes advantage of the fact that we know where each item should go (if you plan on deleting keys, you will need to return grep { defined } @k; instead of just @k).

Here is the benchmark:

#!/usr/bin/perl

use strict;
use warnings;

use Tie::IxHash;
use Tie::Hash::Indexed;
use Benchmark;

#this is O(n) instead of O(n log n) or worse
sub perfect_insert_sort {
    my $h = shift;
    my @k;
    for my $k (keys %$h) {
        $k[$h->{$k}{order}] = $k;
    }
    return @k;
}

my @keys = qw/a b c d e f/;
my %subs = (
    hash => sub {
        my $i;
        my %h = map { $_ => $i++ } @keys;
        return join ", ", keys %h;
    },
    hoh => sub {
        my $i;
        my $order;
        my %h = map {
            $_ => { data => $i++, order => $order++ }
        } @keys;
        return join ", ", sort { $h{$a}{order} <=> $h{$b}{order} }
            keys %h;
    },
    hoh_pis => sub {
        my $i;
        my $order;
        my %h = map {
            $_ => { data => $i++, order => $order++ }
        } @keys;
        return join ", ", perfect_insert_sort \%h;
    },
    IxHash => sub {
        my $i;
        tie my %h, "Tie::IxHash";
        %h = map { $_ => $i++ } @keys;
        return join ", ", keys %h;
    },
    Indexed => sub {
        my $i;
        tie my %h, "Tie::Hash::Indexed";
        %h = map { $_ => $i++ } @keys;
        return join ", ", keys %h;
    },
    IxHashOO => sub {
        my $i;
        my $o = tie my %h, "Tie::IxHash",
            map { $_ => $i++ } @keys;
        return join ", ", $o->Keys;
    },
    IndexedOO => sub {
        my $i;
        my $o = tie my %h, "Tie::Hash::Indexed",
            map { $_ => $i++ } @keys;
        my @k = my $k = $o->FIRSTKEY;
        while ($k = $o->NEXTKEY($k)) {
            push @k, $k;
        }
        return join ", ", @k;
    },
);

for my $sub (keys %subs) {
    print "$sub: ", $subs{$sub}(), "\n";
}

@keys = 1 .. 1_000;
Benchmark::cmpthese -1, \%subs;
Chas. Owens
[`Tie::Hash::Indexed`](http://p3rl.org/Tie::Hash::Indexed) is faster than IxHash. The tie mechanism itself is the big slowdown in both solutions, though.
daxim
@daxim I reached for the first one I could remember. I don't think I am familiar with `Tie::Hash::Indexed`, I will have to take a look.
Chas. Owens
The tying mechanism eats up a lot of performance itself. To get that back, you can use the tied object directly where you know its going to be a tied hash. `$obj = tied %hash; $obj->STORE($key, $value)` for example. The complete interface is in the Tie::Hash documentation.
Schwern