Re: Printing a hash in a specific order?
by artist (Parson) on Mar 15, 2003 at 15:06 UTC
|
# Prints this without tie:
# apples
# oranges
#
# Prints this with tie:
# oranges
# apples
use Tie::IxHash;
tie %menu, 'Tie::IxHash';
$menu{oranges} = 1;
$menu{apples} = 2;
foreach (keys %menu)
{
print "$_\n";
}
From Description of the module:
This Perl module implements Perl hashes that preserve the order in which the hash elements were added. The order is not affected when values corresponding to existing keys in the IxHash are changed. The elements can also be set to any arbitrary supplied order. The familiar perl array operations can also be performed on the IxHash.
artist
| [reply] [d/l] |
|
|
artist,
Thank you very much! I have to look under the hood of this module as I think I have two problems that may make using it a little less than straight forward.
- The object may change over time so I would know how to order keys that haven't been created yet.
- If it is truly forcing the order and not performing some other kind of magic, than it is going to hurt performance.
In reference to point one, I could always just create a place holder for all possible keys, but I am not sure I want to do that since some parameters can exist with empty string as the value and I do not want to accidently create a parameter in a record that didn't previously exist. As far as point two. I have about 150,000 records in in the database - I wouldn't mind adding some overhead for ease of use as long as it doesn't add an extra hour to processing time.
Thanks again - I will definately check out this module.
Cheers - L~R
| [reply] |
|
|
Hi L~R,
You can input your data in benchmarking here.
My benchmark, comparing 200,00 records and each record contain a 50 fields containing small numeric data as key and value, gives the results:
Benchmark: timing 200000 iterations of with_tie, without_tie...
with_tie: 98 wallclock secs (96.22 usr + 0.00 sys = 96.22 CPU) @ 2078.61/s (n=200000)
without_tie: 14 wallclock secs (13.25 usr + 0.00 sys = 13.25 CPU) @ 15094.34/s (n=200000)
Rate with_tie without_tie
with_tie 2079/s -- -86%
without_tie 15094/s 626% --
And the code is
use Benchmark qw(cmpthese);
use strict;
use Tie::IxHash;
sub with_tie {
tie my %menu, 'Tie::IxHash';
foreach (1..50){$menu{$_} = $_; }
}
sub without_tie{
my %menu;
foreach (1..50){$menu{$_} = $_;}
}
cmpthese(200000, {
with_tie => \&with_tie,
without_tie => \&without_tie });
artist | [reply] [d/l] |
Re: Printing a hash in a specific order?
by webfiend (Vicar) on Mar 15, 2003 at 15:11 UTC
|
If the order is important, then perhaps a hash might not be the best solution. Maybe an array or a pseudo-hash could serve your purposes better?
Update: Or, you know, what artist said :-)
I just realized that I was using the same sig for nearly three years.
| [reply] |
|
|
webfiend,
As I said at the bottom of my post. I think if I were to start over, I would use an array (I believe pseudo-hashes are dead for now). I very well may start over since this is my first OO project. Thank you for re-affirming what I already guessed was the most logical solution - starting over.
Cheers - L~R
Update: Using an array doesn't really solve the problem, so starting over is not the answer either. Since new parameters can be added to the record once it is retrieved, the same logic for sorting the output of the hash would have to be used to splice the parameter into the array at the right location.
| [reply] |
Re: Printing a hash in a specific order?
by poj (Abbot) on Mar 15, 2003 at 16:49 UTC
|
This prints a single record, or do you want to print many records in a defined order ? #!/usr/bin/perl -w
use strict;
# test HOH
my %test = (
'rec1' => {
'key'=>"rec1",
'a' =>"11" x 50,
'b' =>"1b1b1b",
'c' =>"c1c1c1c1c",
'z' =>"zz11zz11zz"
},
'rec2' => {
'key'=>"rec2",
'a' =>"22" x 40,
'b' =>"2b2b2b",
'c' =>"c2c2c2c2c",
'x' =>"xx22xx22xx"
}
);
my $obj = new mydata(\%test);
$obj->Print('rec2');
$obj->Print('rec1');
package mydata;
sub new {
my (undef,$self)=@_;
bless $self;
}
sub Print {
my ($self,$key) = @_;
my $record = $self->{$key};
my @parameters = ('key','a','z','c','b','x');
foreach my $parm (@parameters) {
next unless ($record->{$parm});
my $value = ($parm eq 'key') ? $key : $record->{$parm};
printf "%-16s: %s\n",$parm,substr($value,0,62) ;
printf "\t%s\n",substr($value,62) if length $value > 62;
}
}
poj | [reply] [d/l] |
Re: Printing a hash in a specific order?
by artist (Parson) on Mar 15, 2003 at 16:23 UTC
|
my @records;
$records[1] = { a => 1, d=>4, b=> 2, c=> 3};
$records[2] = { a => 11, d=>14, b=>12}; # didn't define 'c' here
$records[3] = { a => 123, d=>10, e=>200, c=> 21, d=>9888 }; # differen
+t order, 'b' absent
my @keys = qw( a c b); # only want to print a c and b
foreach my $record (@records){
print join "\t" =>
map { $_, length($record->{$_}) < 3 ?
$record->{$_} : substr($record->{$_},0,1)}
grep { defined $record->{$_}} @keys, "\n";
print "\n";
}
Output:
a 1 c 3 b 2
a 11 b 12
a 1 c 21
artist | [reply] [d/l] |
Re: Printing a hash in a specific order?
by fruiture (Curate) on Mar 15, 2003 at 15:13 UTC
|
A Hash does not have any order by definition. You speak of "template hash output that I am unaware of", and that can't imply a certain order of the hash keys. You can only generalize by sorting the keys lexically (or however), not "ordering". Your example _knows_ the hash it outputs and provides they keys to be output with their values. A "generic" way can only be generic, a hash is 'generically' without order (actually there is an order that depends on the way the hash is hashed).
Data::Dumper is a "generic" way of outputting various data structues ;)
--
http://fruiture.de
| [reply] |
|
|
fruiture,
I am not looking for a way to store the hash in a specific order. In my post, I acknowledged that hash keys do not come out in the order they are created. I was looking for a way to have the output automatically sorted (via some magic such as artist provided) into a pre-determined format when it came out un-ordered. Thanks for mentioning Data::Dumper - it made me think of something. I may be able to use it if I decide to stay with a hash and not start over.
Cheers - L~R
| [reply] |
|
|
| [reply] |
Re: Printing a hash in a specific order?
by Anonymous Monk on Mar 15, 2003 at 15:55 UTC
|
my ($key) = keys %{$hashref};
| [reply] [d/l] |
|
|
Anonymous Monk,
It is magic - bad magic, but magic none the less. Without showing you my very poorly written OO code. Basically, I am creating a complex hash (HoHo...). There is really only one key to the hash at any one time - which points further down the complex structure. So $hashref is a scalar that is a reference that points to the actual hash. The %{$hashref} is dereferencing the reference it to make it look like a real hash. The keys extracts the one and only key that is there. Putting the parens around the my ($key) part changes the context from scalar (which would have returned the number of keys i.e. 1) to a list context (which returns the one and only key).
Cheers - L~R
| [reply] |
|
|
my $key = each %{$hashref};
The approach you're using with my($key) = keys %{$hashref}; essentially generates a list of the keys in memory, assigns the first element in the list to $key, and then throws rest of the list out. If the size of the hash is large this could be wasteful, but even with small hashes there is a noticeable difference between the two idioms when benchmarking.
Dan Kubb, Perl Programmer
Updated: Made minor corrections noted by Limbic-Region | [reply] [d/l] [select] |
|
|
Re: Printing a hash in a specific order?
by thpfft (Chaplain) on Mar 15, 2003 at 18:30 UTC
|
If you just want the output to look tidy, or to be ordered in a predictable way, then a reasonably simple lexical sort will probably serve. That will get you as far as putting all the alias-* fields next to each other, but if your goal is to make the information human-friendly - ie ordered in an arbitrary way - then the answer will have to be a form of template.
You could consider a full templating system like the Template Toolkit - the initial investment of time would almost certainly pay off later - but for your present requirements all you need is a sort of master sequence that will serve as a guide:
key
type
flags
Alias-.*
Full Name
Post Office
Description
...
Building-.*
owner
Then you just step through the template dropping the right data into your output string in response to each line. Implementing the wildcards will be a bit tricky, but you could use something like the following wildly untested bit of pseudo-perl to obey regexes in the template:
my $output;
while (<template>) {
chomp;
for (grep { /$_/ } keys %{ $data{$key} }) {
$output .= "$_: " . $data{$key}{$_} . "\n";
}
}
which would of course be bristling with taint- and sanity-checks before it made it out into the world :) | [reply] [d/l] [select] |
Re: Printing a hash in a specific order?
by dga (Hermit) on Mar 15, 2003 at 20:53 UTC
|
Something which has not been explicitly said, but which was alluded to in the Template post goes like this.
my @keys_in_order = qw( key type flags Alias-2 ... );
#declared someplace so it doesn't get assigned each pass
sub Print
{
my $hashref=shift;
foreach my $k ( @keys_in_order )
{
my $value=$hashref->{$k} if defined $hashref->{$k};
#rest of processing here
}
}
This way the keys come out in order if something defined was put in that spot in the hash. (this could be changed to just if or if exists depending on what values are really there and which aren't, decided by the application paramaters. This allows values to have 0 and still print in the current incarnation.) So changing the key order printout is as easy as changing the array which says which order to print out the hash values. You get the convienience of a hash which remains a fully optimal hash until printed then prints out in an arbritrary order for a minor cost.
| [reply] [d/l] |