Ordered hashes

J

January Weiner

Hi,

I run into this basic problem now and then.

Hashes allow you to access an element by a key. However, hashes are not
ordered. Take the following example: I have a hash containing a menu
structure:

my %menu = ( -file => [ "new", "open", "save" ],
-analize => [ "count", "add", "subtract" ],
-help => [ "yelp", "cry", "bang your head on the wall" ],
) ;

Now I have some arbitrary function that should display this menu. However,
the order should be preserved, and there is no way we could order it
automatically (e.g. alphabetically). I see two solutions:

1) create a menu index:

my @menu_idx = ( "-file", "-analize", "-help" ) ;

for(@menu_idx) {
do_something($menu{$_}) ;
}

2) pretend %menu is an array:

my @menu = ( "-file", [ "new", "open", "save" ],
"-analize", [ "count", "add", "subtract" ],
"-help", [ "yelp", "cry", "bang your head on the wall" ],
) ;

my %menu_hash = @menu ;

for($i = 0 ; $i < scalar(@menu) ; $i += 2) {
do_something($menu_hash{$menu[$i]}) ;
}

Neither seems to be satisfactory. Are there better ways of dealing with
this problem?

j.

--
 
C

Ch Lamprecht

January said:
Hi,

I run into this basic problem now and then.

Hashes allow you to access an element by a key. However, hashes are not
ordered. Take the following example: I have a hash containing a menu
structure:

my %menu = ( -file => [ "new", "open", "save" ],
-analize => [ "count", "add", "subtract" ],
-help => [ "yelp", "cry", "bang your head on the wall" ],
) ;

Now I have some arbitrary function that should display this menu. However,
the order should be preserved, and there is no way we could order it
automatically (e.g. alphabetically). I see two solutions:
Neither seems to be satisfactory. Are there better ways of dealing with
this problem?

j.
Hi,
perldoc -q order hash

HTH, Chris
 
A

Anno Siegel

January Weiner said:
Hi,

I run into this basic problem now and then.

Hashes allow you to access an element by a key. However, hashes are not
ordered. Take the following example: I have a hash containing a menu
structure:

my %menu = ( -file => [ "new", "open", "save" ],
-analize => [ "count", "add", "subtract" ],
-help => [ "yelp", "cry", "bang your head on the wall" ],
) ;

Now I have some arbitrary function that should display this menu. However,
the order should be preserved,

There are modules that give you hashes that *do* preserve order.
Tie::IxHash is one.
and there is no way we could order it
automatically (e.g. alphabetically). I see two solutions:

Why not? A typical menu doesn't have a million entries. It is usually
no problem to sort the entries alphabetically by their hash keys each
time they are displayed.
1) create a menu index:

my @menu_idx = ( "-file", "-analize", "-help" ) ;

for(@menu_idx) {
do_something($menu{$_}) ;
}

That is probably what order-preserving hashes do under the hood.
2) pretend %menu is an array:

Pretend? You have changed the representation, the menu now *is* an
array.
my @menu = ( "-file", [ "new", "open", "save" ],
"-analize", [ "count", "add", "subtract" ],
"-help", [ "yelp", "cry", "bang your head on the wall" ],
) ;

my %menu_hash = @menu ;

for($i = 0 ; $i < scalar(@menu) ; $i += 2) {
do_something($menu_hash{$menu[$i]}) ;
}

Neither seems to be satisfactory. Are there better ways of dealing with
this problem?

I'd probably use another variant. Represent the menu as a list of
pairs:

my @menu = (
[ -file => [ qw( new open save)]],
[ -analyze => [ qw( count add subtract)]],
[ -help => [ qw( yelp cry), 'bang your head on the wall']],
);

Then you can loop over @menu in the given order (like an array) and
access the name and the choices individually, like with a hash.

for ( @menu ) {
print "$_->[ 0]: ", join( ', ', @{ $_->[ 1] }), "\n";
}

Anno
 
X

xhoster

January Weiner said:
Now I have some arbitrary function that should display this menu.
However, the order should be preserved, and there is no way we could
order it automatically (e.g. alphabetically). I see two solutions:

1) create a menu index:

my @menu_idx = ( "-file", "-analize", "-help" ) ;

for(@menu_idx) {
do_something($menu{$_}) ;
}

# how about a hash slice?
do_somehitng($_) foreach @menu{@menu_idx};
2) pretend %menu is an array:

my @menu = ( "-file", [ "new", "open", "save" ],
"-analize", [ "count", "add", "subtract" ],
"-help", [ "yelp", "cry", "bang your head on the wall" ],
) ;

my %menu_hash = @menu ;

for($i = 0 ; $i < scalar(@menu) ; $i += 2) {
do_something($menu_hash{$menu[$i]}) ;
}

Neither seems to be satisfactory. Are there better ways of dealing with
this problem?

How to deal with the problem depends on what specifically you find
unsatisfactory about the solutions you already have.

Xho
 
J

January Weiner

Anno Siegel said:
There are modules that give you hashes that *do* preserve order.
Tie::IxHash is one.

Right, thanks -- that is the solution I sought.
Why not? A typical menu doesn't have a million entries. It is usually
no problem to sort the entries alphabetically by their hash keys each
time they are displayed.

Either you have Menus not sorted logically, or you have to remember that
"File" is "aFile" and "Help" is "zHelp". Anyway, menus were just an
example I came up with.
Pretend? You have changed the representation, the menu now *is* an
array.

Yes, formally speaking it is, but practically speaking I do not use it as
one, correct? Anyway, it was a metaphor.
I'd probably use another variant. Represent the menu as a list of
pairs:
my @menu = (
[ -file => [ qw( new open save)]],
[ -analyze => [ qw( count add subtract)]],
[ -help => [ qw( yelp cry), 'bang your head on the wall']],
);
Then you can loop over @menu in the given order (like an array) and
access the name and the choices individually, like with a hash.

I think I'm lost. How can I access the elements of @menu above
individually like in a hash? I have to access the elements of @menu by
their index, and cannot do so by the key (or you mean the elements of the
anonymous arrays within?).

Cheers, and thanks!

j.

--
 
A

Anno Siegel

January Weiner said:
Right, thanks -- that is the solution I sought.


Either you have Menus not sorted logically, or you have to remember that
"File" is "aFile" and "Help" is "zHelp". Anyway, menus were just an
example I came up with.

Ah, okay. You could sort them alphabetically, but that isn't the
order you want.
Pretend? You have changed the representation, the menu now *is* an
array.

Yes, formally speaking it is, but practically speaking I do not use it as
one, correct? Anyway, it was a metaphor.
I'd probably use another variant. Represent the menu as a list of
pairs:
my @menu = (
[ -file => [ qw( new open save)]],
[ -analyze => [ qw( count add subtract)]],
[ -help => [ qw( yelp cry), 'bang your head on the wall']],
);
Then you can loop over @menu in the given order (like an array) and
access the name and the choices individually, like with a hash.

I think I'm lost. How can I access the elements of @menu above
individually like in a hash?

You can't, but for what my example code does you don't need to.
I have to access the elements of @menu by
their index, and cannot do so by the key (or you mean the elements of the
anonymous arrays within?).

Basically you need a hash *and* an array for that. You can do that
yourself, or use one of the ordered-hash modules that hide the
implementation.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top