Nested sorting of a hash

Nested sorting of a hash

am 06.12.2007 12:23:49 von Justin C

I have a hash containing stock codes (the keys) and the number of units
sold (the values). I can sort them easily to give the best selling items
at the top and those that sold the fewest at the bottom. There are many
items with the same number of units sold, what I'd like to do is sort
the keys within there relevant position in the in the list.

Here is an example of the data I have:

Code Qty
ABZ001 13
ADF001 7
ADF002 6
ADF003 6
ADF004 6
ABZ002 5
ABZ003 5
ABZ120 4
ABZ047 4
ABZ022 4
ADF027 4
ADF019 4

What I'd like to have is within those items that have sold 4 units they
be sorted asciibetically.

The sort I have for the hash so far is:

foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
print "$_;\t$sales{$_}\n";
}

Thank you for any help you can give with this.

Justin.

--
Justin C, by the sea.

Re: Nested sorting of a hash

am 06.12.2007 12:44:56 von bugbear

Justin C wrote:
>
> The sort I have for the hash so far is:
>
> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
> print "$_;\t$sales{$_}\n";
> }
>
> Thank you for any help you can give with this.

You need to make your hash into an array of hashes, and sort that.
You cannot get the results you desire by sorting the keys;
you must sort by considering the code/sold as a pair,
using a primary/secondary comparison in the sort.

BugBear

Re: Nested sorting of a hash

am 06.12.2007 12:51:27 von Glenn Jackman

At 2007-12-06 06:23AM, "Justin C" wrote:
[...]
> Code Qty
> ABZ001 13
> ADF001 7
> ADF002 6
> ADF003 6
> ADF004 6
> ABZ002 5
> ABZ003 5
> ABZ120 4
> ABZ047 4
> ABZ022 4
> ADF027 4
> ADF019 4
>
> What I'd like to have is within those items that have sold 4 units they
> be sorted asciibetically.
>
> The sort I have for the hash so far is:
>
> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
> print "$_;\t$sales{$_}\n";
> }

Note that the <=> operator returns 0 if the operands are equal, so

foreach my $key (sort {$sales{$b} <=> $sales{$a} or $a cmp $b} keys %sales) {
print "$key\t$sales{$key}\n";
}

This answer does not limit sorting the keys only when the value is 4.

--
Glenn Jackman
"You can only be young once. But you can always be immature." -- Dave Barry

Re: Nested sorting of a hash

am 06.12.2007 12:52:17 von Ben Morrow

Quoth justin.news@purestblue.com:
> I have a hash containing stock codes (the keys) and the number of units
> sold (the values). I can sort them easily to give the best selling items
> at the top and those that sold the fewest at the bottom. There are many
> items with the same number of units sold, what I'd like to do is sort
> the keys within there relevant position in the in the list.

>
> The sort I have for the hash so far is:
>
> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {

foreach ( sort {
$sales{$b} <=> $sales{$a}
||
$a cmp $b
} keys %sales ) {

> print "$_;\t$sales{$_}\n";
> }

The only time a sort expression returns false is when they compared
equal, so you can combine them with ||.

Ben

Re: Nested sorting of a hash

am 06.12.2007 12:56:02 von Tad McClellan

Justin C wrote:
> I have a hash containing stock codes (the keys) and the number of units
> sold (the values).

> Here is an example of the data I have:
>
> Code Qty
> ABZ001 13
> ADF001 7
> ADF002 6
> ADF003 6
> ADF004 6
> ABZ002 5
> ABZ003 5
> ABZ120 4
> ABZ047 4
> ABZ022 4
> ADF027 4
> ADF019 4
>
> What I'd like to have is within those items that have sold 4 units they
> be sorted asciibetically.


Your question is answered in the Perl FAQ:

How do I sort a hash (optionally by value instead of key)?


> The sort I have for the hash so far is:
>
> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
> print "$_;\t$sales{$_}\n";
> }


foreach ( sort { $sales{$b} <=> $sales{$a} or $a cmp $b } keys %sales) {


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

Re: Nested sorting of a hash

am 06.12.2007 15:21:08 von Justin C

On 2007-12-06, Ben Morrow wrote:
>
> Quoth justin.news@purestblue.com:
>> I have a hash containing stock codes (the keys) and the number of units
>> sold (the values). I can sort them easily to give the best selling items
>> at the top and those that sold the fewest at the bottom. There are many
>> items with the same number of units sold, what I'd like to do is sort
>> the keys within there relevant position in the in the list.
>
>>
>> The sort I have for the hash so far is:
>>
>> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
>
> foreach ( sort {
> $sales{$b} <=> $sales{$a}
> ||
> $a cmp $b
> } keys %sales ) {
>
>> print "$_;\t$sales{$_}\n";
>> }
>
> The only time a sort expression returns false is when they compared
> equal, so you can combine them with ||.

Thank you for that. It's quite elegant really. Thanks also to Glenn for
(almost) the same answer... I say almost, is there any difference
between || and 'or' in the above?

Justin.

--
Justin C, by the sea.

Re: Nested sorting of a hash

am 06.12.2007 16:26:48 von Glenn Jackman

At 2007-12-06 09:21AM, "Justin C" wrote:
> On 2007-12-06, Ben Morrow wrote:
[...]
> > foreach ( sort {
> > $sales{$b} <=> $sales{$a}
> > ||
> > $a cmp $b
> > } keys %sales ) {
> >
> >> print "$_;\t$sales{$_}\n";
> >> }
> >
> > The only time a sort expression returns false is when they compared
> > equal, so you can combine them with ||.
>
> Thank you for that. It's quite elegant really. Thanks also to Glenn for
> (almost) the same answer... I say almost, is there any difference
> between || and 'or' in the above?

Not in this case. The only difference is precedence: or is lower than ||
In other scenarios the difference is large. See perldoc -q precedence and
perldoc perlop

--
Glenn Jackman
"You can only be young once. But you can always be immature." -- Dave Barry

Re: Nested sorting of a hash

am 06.12.2007 17:36:37 von jurgenex

bugbear wrote:
> Justin C wrote:
>>
>> The sort I have for the hash so far is:
>>
>> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
>> print "$_;\t$sales{$_}\n";
>> }
>>
>> Thank you for any help you can give with this.
>
> You need to make your hash into an array of hashes, and sort that.

No, you don't.

> You cannot get the results you desire by sorting the keys;
> you must sort by considering the code/sold as a pair,
> using a primary/secondary comparison in the sort.

Which is trivial to do by augmenting the inline compare function with a
secondary comparison in case the first one yields equal:

$sales{$b} <=> $sales{$a} or $a cmp $b

This neat trick is mentioned in the docs, BTW:

If you need to sort on several fields, the following paradigm is
useful.
@sorted = sort { field1($a) <=> field1($b) ||
field2($a) cmp field2($b) ||
field3($a) cmp field3($b)
} @data;

jue

Re: Nested sorting of a hash

am 06.12.2007 18:00:07 von bugbear

Jürgen Exner wrote:
> bugbear wrote:
>> Justin C wrote:
>>> The sort I have for the hash so far is:
>>>
>>> foreach ( sort { $sales{$b} <=> $sales{$a} } keys %sales) {
>>> print "$_;\t$sales{$_}\n";
>>> }
>>>
>>> Thank you for any help you can give with this.
>> You need to make your hash into an array of hashes, and sort that.
>
> No, you don't.

You're right, of courtse; I was having a bad morning :-(

BugBear

Re: Nested sorting of a hash

am 06.12.2007 18:48:39 von Glenn Jackman

At 2007-12-06 11:36AM, "Jürgen Exner" wrote:
> If you need to sort on several fields, the following paradigm is
> useful.
> @sorted = sort { field1($a) <=> field1($b) ||
> field2($a) cmp field2($b) ||
> field3($a) cmp field3($b)
> } @data;

If the field lookup is expensive, you'd want the orcish maneuver or a
schwartzian transform:
http://www.perlmonks.org/?node_id=128722

--
Glenn Jackman
"You can only be young once. But you can always be immature." -- Dave Barry

Re: Nested sorting of a hash

am 06.12.2007 18:59:36 von jurgenex

Glenn Jackman wrote:
> At 2007-12-06 11:36AM, "Jürgen Exner" wrote:
>> If you need to sort on several fields, the following paradigm
>> is useful.
>> @sorted = sort { field1($a) <=> field1($b) ||
>> field2($a) cmp field2($b) ||
>> field3($a) cmp field3($b)
>> } @data;
>
> If the field lookup is expensive,

Which obviously is not the case for the OP.

> you'd want the orcish maneuver or a
> schwartzian transform:
> http://www.perlmonks.org/?node_id=128722

Which is actually mentioned in the lines above the except that I quoted with
an additional pointer just following my excerpt (see 'perldoc -q sort'):

This can be conveniently combined with precalculation of keys as
given above.

I don't think there is any need to emphasize the point in the FAQ even
further.

jue

Re: Nested sorting of a hash

am 06.12.2007 20:51:03 von Uri Guttman

>>>>> "GJ" == Glenn Jackman writes:

GJ> At 2007-12-06 11:36AM, "Jürgen Exner" wrote:
>> If you need to sort on several fields, the following paradigm is
>> useful.
>> @sorted = sort { field1($a) <=> field1($b) ||
>> field2($a) cmp field2($b) ||
>> field3($a) cmp field3($b)
>> } @data;

GJ> If the field lookup is expensive, you'd want the orcish maneuver or a
GJ> schwartzian transform:
GJ> http://www.perlmonks.org/?node_id=128722

the orcish is actually fairly slow. but you can compare all the sort
speedup techniques with Sort::Maker and even see the generated code. the
benchmark script in the tarball shows that the GRT is the fastest for
most multilevel sorting.

uri

--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org