By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
454,278 Members | 1,502 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 454,278 IT Pros & Developers. It's quick & easy.

Hash sort weirdness

P: 2
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.  
Apr 1 '08 #1
Share this Question
Share on Google+
4 Replies


KevinADC
Expert 2.5K+
P: 4,059
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.
Apr 1 '08 #2

P: 2
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 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.
Apr 1 '08 #3

Ganon11
Expert 2.5K+
P: 3,652
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.
Apr 1 '08 #4

KevinADC
Expert 2.5K+
P: 4,059
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
Apr 1 '08 #5

Post your reply

Sign in to post your reply or Sign up for a free account.