Sorting AofH over hash key(s)...

Sorting AofH over hash key(s)...

am 30.10.2007 21:40:41 von newsbot

I've searched this NG, the web, and CPAN and haven't seen anything to
do this, and while I have a solution, I'm wondering what other
solutions might be living inside some other pretty brilliant brain
cages here on c.l.p.m...

The problem seems fairly straight-forward: I want to be able to sort
an Array of Hashes by an arbitrary number of hash keys. For instance,
given this hash:

my $h = [
{ color => 'red', size => 1, width => 640, height => 480 },
{ color => 'blue', size => 4, width => 800, height => 600 },
{ color => 'green', size => 2, width => 1024, height => 768 },
{ color => 'orange', size => 5, width => 320, height => 280 },
{ color => 'purple', size => 3, width => 40, height => 50 },
];

I'd like to sort by the keys: color, size, width, and/or height. I
realize for only ONE hash key, I can do:

for my $info (sort { $a->{color} cmp $b->{color} } @{$h}) { blah() }
#...

But... What if I want to sort by say... color and then size? Or
height and then width? Or all the keys in some order? Again haven't
seen anything to do this. It's hard to imagine this has never been
solved, but then again, I've done a fair amount of Perl coding in my
day and I haven't run across the need to do this until recently.

My solution? Somewhat similar to a mod I saw on CPAN once that
flattened deep hashes into a dotted namespace. It was a serialization
(or a namespace transformation, more like it):

$h->{norman}->{width}-{arm}->{finger}->{pinky} = 1.5;

became:

$h{norman.width.arm.finger.pinky} = 1.5;

Or something like that. Anyway, my approach was to serialize the data
(not the keys) in the hashes in order of keys I wanted to sort on,
sort THAT array, and deserialize back in the right key order:

my $temp = [
'red\xFF1\xFF640\xFF480',
'blue\xFF4\xFF800\xFF600',
'green'\xFF2\xFF1024\xFF768',
'orange\xFF5\xFF320\xFF280',
'purple\xFF\xFF3\xFF40\xFF50',
];

my @sortedArray = sort @{$temp};

## Then deserialize in correct order...

Also, my example, with a lot of numeric data is somewhat bad, I
realize. I realize my solution doesn't sort numeric data properly
(due to ordinal values), but for the solution I needed it for,
immediately, the serialize -> sort -> deserialize method works for
now. The numeric/ordinal issue is one of the reasons I am asking
simply from a purist standpoint.

Anyone got something different/better? A mod to recommend that I am
not seeing on CPAN?

Thanks!
/usr/ceo

Re: Sorting AofH over hash key(s)...

am 30.10.2007 22:03:03 von Mirco Wahab

/usr/ceo wrote:
> my $h = [
> { color => 'red', size => 1, width => 640, height => 480 },
> { color => 'blue', size => 4, width => 800, height => 600 },
> { color => 'green', size => 2, width => 1024, height => 768 },
> { color => 'orange', size => 5, width => 320, height => 280 },
> { color => 'purple', size => 3, width => 40, height => 50 },
> ];
>
> I'd like to sort by the keys: color, size, width, and/or height. I
> realize for only ONE hash key, I can do:
>
> for my $info (sort { $a->{color} cmp $b->{color} } @{$h}) { blah() }
> #...
>
> But... What if I want to sort by say... color and then size? Or
> height and then width? Or all the keys in some order? Again haven't
> seen anything to do this. It's hard to imagine this has never been
> solved, but then again, I've done a fair amount of Perl coding in my
> day and I haven't run across the need to do this until recently.

The (somehow) canonical solution would be a sorter function like:

...
my @hs = sort mysortorder @$h;

sub mysortorder {
$a->{color} cmp $b->{color} ||
$b->{width} <=> $a->{width} ||
$b->{height} <=> $a->{height} ||
$b->{size} <=> $a->{size}
}

for my $s (@hs) {
print "$_=>$s->{$_}, " for keys %$s;
print "\n"
}
...

Regards

M.

Re: Sorting AofH over hash key(s)...

am 30.10.2007 22:03:12 von it_says_BALLS_on_your forehead

On Oct 30, 4:40 pm, "/usr/ceo" wrote:
> I've searched this NG, the web, and CPAN and haven't seen anything to
> do this, and while I have a solution, I'm wondering what other
> solutions might be living inside some other pretty brilliant brain
> cages here on c.l.p.m...
>
> The problem seems fairly straight-forward: I want to be able to sort
> an Array of Hashes by an arbitrary number of hash keys. For instance,
> given this hash:
>
> my $h = [
> { color => 'red', size => 1, width => 640, height => 480 },
> { color => 'blue', size => 4, width => 800, height => 600 },
> { color => 'green', size => 2, width => 1024, height => 768 },
> { color => 'orange', size => 5, width => 320, height => 280 },
> { color => 'purple', size => 3, width => 40, height => 50 },
> ];
>
> I'd like to sort by the keys: color, size, width, and/or height. I
> realize for only ONE hash key, I can do:
>
> for my $info (sort { $a->{color} cmp $b->{color} } @{$h}) { blah() }
> #...
>
> But... What if I want to sort by say... color and then size? Or
> height and then width? Or all the keys in some order? Again haven't
> seen anything to do this. It's hard to imagine this has never been
> solved, but then again, I've done a fair amount of Perl coding in my
> day and I haven't run across the need to do this until recently.
>
> My solution? Somewhat similar to a mod I saw on CPAN once that
> flattened deep hashes into a dotted namespace. It was a serialization
> (or a namespace transformation, more like it):
>
> $h->{norman}->{width}-{arm}->{finger}->{pinky} = 1.5;
>
> became:
>
> $h{norman.width.arm.finger.pinky} = 1.5;
>
> Or something like that. Anyway, my approach was to serialize the data
> (not the keys) in the hashes in order of keys I wanted to sort on,
> sort THAT array, and deserialize back in the right key order:
>
> my $temp = [
> 'red\xFF1\xFF640\xFF480',
> 'blue\xFF4\xFF800\xFF600',
> 'green'\xFF2\xFF1024\xFF768',
> 'orange\xFF5\xFF320\xFF280',
> 'purple\xFF\xFF3\xFF40\xFF50',
> ];
>
> my @sortedArray = sort @{$temp};
>
> ## Then deserialize in correct order...
>
> Also, my example, with a lot of numeric data is somewhat bad, I
> realize. I realize my solution doesn't sort numeric data properly
> (due to ordinal values), but for the solution I needed it for,
> immediately, the serialize -> sort -> deserialize method works for
> now. The numeric/ordinal issue is one of the reasons I am asking
> simply from a purist standpoint.

# from the Perl Cookbook

my @sorted =
sort { $a->name cmp $b->name
||
$b->age <=> $a->age } @employees;

Re: Sorting AofH over hash key(s)...

am 30.10.2007 22:14:22 von glex_no-spam

/usr/ceo wrote:
> I've searched this NG, the web, and CPAN and haven't seen anything to
> do this, and while I have a solution, I'm wondering what other
> solutions might be living inside some other pretty brilliant brain
> cages here on c.l.p.m...
>
> The problem seems fairly straight-forward: I want to be able to sort
> an Array of Hashes by an arbitrary number of hash keys. For instance,
> given this hash:
>
> my $h = [
> { color => 'red', size => 1, width => 640, height => 480 },
> { color => 'blue', size => 4, width => 800, height => 600 },
> { color => 'green', size => 2, width => 1024, height => 768 },
> { color => 'orange', size => 5, width => 320, height => 280 },
> { color => 'purple', size => 3, width => 40, height => 50 },
> ];
>
> I'd like to sort by the keys: color, size, width, and/or height. I
> realize for only ONE hash key, I can do:
>
> for my $info (sort { $a->{color} cmp $b->{color} } @{$h}) { blah() }
> #...
>
> But... What if I want to sort by say... color and then size? Or
> height and then width? Or all the keys in some order?

Just modify the sort to take into account the other keys.

sort {
$a->{ 'color' } cmp $b->{ 'color' } ||
$a->{ 'size' } <=> $b->{ 'size' }
}@{$h};

perldoc -q "How do I sort"

Re: Sorting AofH over hash key(s)...

am 30.10.2007 22:16:10 von newsbot

On Oct 30, 4:03 pm, nolo contendere wrote:
> On Oct 30, 4:40 pm, "/usr/ceo" wrote:
>
> [Problem description snipped]
>
> # from the Perl Cookbook
>
> my @sorted =
> sort { $a->name cmp $b->name
> ||
> $b->age <=> $a->age } @employees;

Dad-gummit, I *looked* in the PC, and didn't see this... That syntax
makes perfect sense. It never occurred to me to extended the { sort
$a->{key} cmp $b->{key} } syntax into something broader with logical
operators. You get stuck in a rut with what you see as 90% used
sometimes.

NOW the solution is on the internet... :-)

Thanks!
/usr/ceo

Re: Sorting AofH over hash key(s)...

am 31.10.2007 03:13:54 von Tad McClellan

/usr/ceo wrote:
> On Oct 30, 4:03 pm, nolo contendere wrote:
>> On Oct 30, 4:40 pm, "/usr/ceo" wrote:
>>
>> [Problem description snipped]
>>
>> # from the Perl Cookbook
>>
>> my @sorted =
>> sort { $a->name cmp $b->name
>> ||
>> $b->age <=> $a->age } @employees;
>
> Dad-gummit, I *looked* in the PC, and didn't see this... That syntax
> makes perfect sense. It never occurred to me to extended the { sort
> $a->{key} cmp $b->{key} } syntax into something broader with logical
> operators. You get stuck in a rut with what you see as 90% used
> sometimes.
>
> NOW the solution is on the internet... :-)


Errr, it was already on the internet...

http://faq.perl.org/perlfaq4.html#How_do_I_sort_a_hash

.... and on your very own hard disk!

perldoc -q sort


--
Tad McClellan
email: perl -le "print scalar reverse qq/moc.noitatibaher\100cmdat/"

Re: Sorting AofH over hash key(s)...

am 05.11.2007 10:02:59 von Salvador Fandino

/usr/ceo wrote:
> I've searched this NG, the web, and CPAN and haven't seen anything to
> do this, and while I have a solution, I'm wondering what other
> solutions might be living inside some other pretty brilliant brain
> cages here on c.l.p.m...
>
> The problem seems fairly straight-forward: I want to be able to sort
> an Array of Hashes by an arbitrary number of hash keys. For instance,
> given this hash:

....

> Anyone got something different/better? A mod to recommend that I am
> not seeing on CPAN?

You can use Sort::Key or Sort::Maker from CPAN to do that:

For instance, to sort by color then by width:

use Sort::Key::Multi qw(si_keysort);
# si_ stands for string + integer
# is like saying use 'cmp' to compare the first key
# and '<=>' for the second

my @sorted = si_keysort { $_->{color}, $_->{width} } @$h;


You can even generate the sorter function dynamically:

use Sort::Key qw(multikeysorter);

# define how the different data properties have to be compared:
my %sorting_types = (color => 'string',
size => 'integer',
width => 'integer',
height => 'integer');

my @order = qw(color size width); # change to suit your needs;

my @sorting_types = map $sorting_types{$_}, @order;

my $sorter = multikeysorter(sub {
@{$_}{@order} # that's a hash slice...
},
@sorting_types);

my $sorted_data = $sorter->(@data);

Cheers,

- Salva

Re: Sorting AofH over hash key(s)...

am 05.11.2007 10:03:21 von Salvador Fandino

/usr/ceo wrote:
> I've searched this NG, the web, and CPAN and haven't seen anything to
> do this, and while I have a solution, I'm wondering what other
> solutions might be living inside some other pretty brilliant brain
> cages here on c.l.p.m...
>
> The problem seems fairly straight-forward: I want to be able to sort
> an Array of Hashes by an arbitrary number of hash keys. For instance,
> given this hash:

....

> Anyone got something different/better? A mod to recommend that I am
> not seeing on CPAN?

You can use Sort::Key or Sort::Maker from CPAN to do that:

For instance, to sort by color then by width:

use Sort::Key::Multi qw(si_keysort);
# si_ stands for string + integer
# is like saying use 'cmp' to compare the first key
# and '<=>' for the second

my @sorted = si_keysort { $_->{color}, $_->{width} } @$h;


You can even generate the sorter function dynamically:

use Sort::Key qw(multikeysorter);

# define how the different data properties have to be compared:
my %sorting_types = (color => 'string',
size => 'integer',
width => 'integer',
height => 'integer');

my @order = qw(color size width); # change to suit your needs;

my @sorting_types = map $sorting_types{$_}, @order;

my $sorter = multikeysorter(sub {
@{$_}{@order} # that's a hash slice...
},
@sorting_types);

my $sorted_data = $sorter->(@data);

Cheers,

- Salva