Connecting Tech Pros Worldwide Forums | Help | Site Map

Hash sort weirdness

Newbie
 
Join Date: Apr 2008
Posts: 2
#1: Apr 1 '08
I am having trouble sorting secondary keys in a multi-level hash. I have a sample program that exhibits the problem...the following code is sorting New 6 Month before New 1 Month. What am I doing wrong?

Thanks in advance.

Expand|Select|Wrap|Line Numbers
  1. #!/usr/bin/perl
  2.  
  3.  
  4. my %data = (
  5.        "2008-03-24" => {
  6.                  "New 1 Month Subscription" => {
  7.                      id => 1,
  8.                      value => 10, 
  9.                  },
  10.                  "New 12 Month Subscription" => {
  11.                      id => 5,
  12.                      value => 12,
  13.                  },
  14.                  "New 14 Day Trial Subscription" => {
  15.                      id => 3,
  16.                      value => 15,
  17.                  },
  18.                  "New 6 Month Subscription" => {
  19.                      id => 6,
  20.                      value => 20,
  21.                  },
  22.        },
  23.        "2008-03-25" => {
  24.                  "New 1 Month Subscription" => {
  25.                      id => 1,
  26.                      value => 10, 
  27.                  },
  28.                  "New 12 Month Subscription" => {
  29.                      id => 5,
  30.                      value => 12,
  31.                  },
  32.                  "New 3 Month Subscription" => {
  33.                      id => 7,
  34.                      value => 8,
  35.                  },
  36.        },
  37. );
  38.  
  39. foreach my $this_date (sort keys %data) {
  40.   print "$this_date\n";
  41.   foreach my $name ( sort {lc($data{$a}) cmp lc($data{$b})} keys %{ $data{$this_date} } ) {
  42.      print "$name\t";
  43.      print "$data{$this_date}{$name}{id}\n";
  44.   }
  45. }
  46.  

KevinADC's Avatar
Expert
 
Join Date: Jan 2007
Location: Southern California USA
Posts: 4,091
#2: Apr 1 '08

re: Hash sort weirdness


This line is wrong:

Expand|Select|Wrap|Line Numbers
  1. foreach my $name ( sort {lc($data{$a}) cmp lc($data{$b})} keys %{ 
should be:

Expand|Select|Wrap|Line Numbers
  1.   foreach my $name ( sort { $data{$this_date}{$a} cmp $data{$this_date}{$b} } keys %{ $data{$this_date} } ) {
but don't expect 6 to come after 12 when you sort using "cmp" because the sort is what is called asciibetical (sort of like alphabetical), in which case 12 comes before 6 like 'AC' comes before 'D'.

Note: there is no need to use lc() in your sort because all the characters are the same mix of upper and lower case characters. Use lc() or uc() when the strings being sorted are not all the same case.
Newbie
 
Join Date: Apr 2008
Posts: 2
#3: Apr 1 '08

re: Hash sort weirdness


Ahh...I missed that. Now that I have that part corrected...I have tried to sort using both cmp and <=>...both are returning the same results which is:

2008-03-24
New 1 Month Subscription 1
New 12 Month Subscription 5
New 14 Day Trial Subscription 3
New 6 Month Subscription 6

I understand that is it using the ASCII value to sort right now, which is not what I want. How can I get it to sort the string part and then the numbers as numbers?

Thanks again...

Quote:

Originally Posted by KevinADC

This line is wrong:

Expand|Select|Wrap|Line Numbers
  1. foreach my $name ( sort {lc($data{$a}) cmp lc($data{$b})} keys %{ 
should be:

Expand|Select|Wrap|Line Numbers
  1.   foreach my $name ( sort { $data{$this_date}{$a} cmp $data{$this_date}{$b} } keys %{ $data{$this_date} } ) {
but don't expect 6 to come after 12 when you sort using "cmp" because the sort is what is called asciibetical (sort of like alphabetical), in which case 12 comes before 6 like 'AC' comes before 'D'.

Note: there is no need to use lc() in your sort because all the characters are the same mix of upper and lower case characters. Use lc() or uc() when the strings being sorted are not all the same case.

Ganon11's Avatar
Moderator
 
Join Date: Oct 2006
Location: New York, United States of America
Posts: 3,428
#4: Apr 1 '08

re: Hash sort weirdness


You can write your own short subroutine to sort them. In the subroutine, $a will be the first element and $b will be the second element. You take $a and $b and parse them for the number (if you know that's the only place they will differ), and then return the comparison of those numbers.

The easiest way to parse the strings will be a regex - just match for the proper pattern and capture each number in $1.
KevinADC's Avatar
Expert
 
Join Date: Jan 2007
Location: Southern California USA
Posts: 4,091
#5: Apr 1 '08

re: Hash sort weirdness


Quote:

Originally Posted by cici

Ahh...I missed that. Now that I have that part corrected...I have tried to sort using both cmp and <=>...both are returning the same results which is:

2008-03-24
New 1 Month Subscription 1
New 12 Month Subscription 5
New 14 Day Trial Subscription 3
New 6 Month Subscription 6

I understand that is it using the ASCII value to sort right now, which is not what I want. How can I get it to sort the string part and then the numbers as numbers?

Thanks again...


This is a job for the Schwartzian Transform
Reply