Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Sorting an Array of Hashes 1

Status
Not open for further replies.

DarrylLundy

Technical User
Nov 9, 2004
1
NZ
I am newish to Perl, and am struggling with sorting an array of hashes on multiple fields. I have used the code given in Programming Perl, 3rd ed., under the sort function.

So my code:

Code:
my @s; 
my @t;

sub bytitle {
   $b->{TITLE} cmp $a->{TITLE}
     ||
   $b->{SURNAME} cmp $a->{SURNAME}
     ||
   $b->{ORDINAL} cmp $a->{ORDINAL}}
@s = sort bytitle @t;

along with the following test data:

Code:
$t[0]{TITLE} = "Baronet";
$t[0]{SURNAME} = "Abyn";
$t[0]{ORDINAL} = " 1st";
$t[1]{TITLE} = "Duke";
$t[1]{SURNAME} = "Abercorn";
$t[1]{ORDINAL} = " 1st";
$t[2]{TITLE} = "Duke";
$t[2]{SURNAME} = "Abercorn";
$t[2]{ORDINAL} = " 3rd";
$t[3]{TITLE} = "Duke";
$t[3]{SURNAME} = "Abercorn";
$t[3]{ORDINAL} = " 2nd";

gives me the following warning message:

Code:
use of uninitialized value in string comparison (cmp) at xx.pl line x
- referencing the code '$b->{ORDINAL} cmp $a->{ORDINAL}'

and the data is not sorted in the right order. I get:

1st Baronet Abyn
3rd Duke Bedford
1st Duke Bedford
2nd Duke Bedford

rather than the intended:

1st Baronet Abyn
1st Duke Bedford
2nd Duke Bedford
3rd Duke Bedford

I don't see what values are uninitialized, and therefore why the sort fails to do anything. What am I doing wrong?

Regards
Darryl
 
Something is clearly not right here. According to the code you posted, the surnames in your @t array are Abyn and Abercorn, but the output you posted shows Abyn and Bedford. So either this is not your actual code, or it's not your actual output. Or perhaps neither?

However, the main problem seems to be that your sort is incorrect. To produce the output you want, you need to sort by ORDINAL, then by TITLE, then by SURNAME. And putting $b first sorts in reverse order. You need $a first. Like so:
Code:
#!perl
use strict;
use warnings;

my @s; 
my @t;

$t[0]{TITLE} = "Baronet";
$t[0]{SURNAME} = "Abyn";
$t[0]{ORDINAL} = " 1st";
$t[1]{TITLE} = "Duke";
$t[1]{SURNAME} = "Abercorn";
$t[1]{ORDINAL} = " 1st";
$t[2]{TITLE} = "Duke";
$t[2]{SURNAME} = "Abercorn";
$t[2]{ORDINAL} = " 3rd";
$t[3]{TITLE} = "Duke";
$t[3]{SURNAME} = "Abercorn";
$t[3]{ORDINAL} = " 2nd";

[b]sub bytitle {
   $a->{ORDINAL} cmp $b->{ORDINAL}
     ||
   $a->{TITLE} cmp $b->{TITLE}
     ||
   $a->{SURNAME} cmp $b->{SURNAME}
}[/b]

@s = sort bytitle @t;

for my $hr (@s) {
    print join " ", @$hr{qw(ORDINAL TITLE SURNAME)}, "\n";
}
Output:
Code:
 1st Baronet Abyn 
 1st Duke Abercorn 
 2nd Duke Abercorn 
 3rd Duke Abercorn
I ran this using strict and warnings and did not get the warning message you reported. Which leaves me wondering again if what you posted is your actual code.

HTH






 
Running the code you have as is, I get the following:

Code:
 3rd Duke Abercorn
 2nd Duke Abercorn
 1st Duke Abercorn
 1st Baronet Abyn

Like Mikevh said, you want to change the order of your sorting for $a and $b as follows:

Code:
my @s; 
my @t;

$t[0]{TITLE} = "Baronet";
$t[0]{SURNAME} = "Abyn";
$t[0]{ORDINAL} = " 1st";
$t[1]{TITLE} = "Duke";
$t[1]{SURNAME} = "Abercorn";
$t[1]{ORDINAL} = " 1st";
$t[2]{TITLE} = "Duke";
$t[2]{SURNAME} = "Abercorn";
$t[2]{ORDINAL} = " 3rd";
$t[3]{TITLE} = "Duke";
$t[3]{SURNAME} = "Abercorn";
$t[3]{ORDINAL} = " 2nd";

sub bytitle {
$a->{TITLE} cmp $b->{TITLE}
||
$a->{SURNAME} cmp $b->{SURNAME}
||
$a->{ORDINAL} cmp $b->{ORDINAL}}
@s = sort bytitle @t;


Michael Libeson
 
Thanks for the star. The code as given above sorts okay with the input you've supplied, but cmp is doing an alpha comparison on ORDINAL, not a true numeric comparison on the numbers, and this could cause problems down the line. (Try adding a 10th or 14th something-or-other to see what I mean.) To get around this, you would need to extract just the numeric parts of the ORDINAL fields and do a numeric comparison, using <=>.

In the following, we extract just the numeric parts of the $a->{ORDINAL} and $b->{ORDINAL} fields and store them in $x and $y, then do a numeric comparison on these using <=>.
Code:
sub bytitle {
[b]   my ($x, $y) = ($a->{ORDINAL}=~/(\d+)/, $b->{ORDINAL}=~/(\d+)/);[/b]
   $a->{TITLE} cmp $b->{TITLE}
     ||
   $a->{SURNAME} cmp $b->{SURNAME}
     ||
   [b]$x <=> $y[/b]
}
I've adopted mlibeson's sort order here, which is different from what I originally posted. This makes no difference in output with the data given above, but it will cause all the Baronets to come out before all the Dukes, etc., rather than all the "1st's" to come out before all the "2nd's", etc., which would happen with my original sort order. (Not clear from your post which you actually want.)



 
I'm very weak in Perl/PHP, spent the last 2 days figuring out passing multiple checkboxes to a PHP page and was able to figure it out with some help from online tutorials. But now I'm stuck, I've searched for any working solutions similar to my dilemma: how to pass the dynamic PHP array to Perl for emailing. The code doesn't throw any errors, but it doesn't send the info either - seems like the variables aren't being passed or not being read. Here is the code from the PHP form page:
<code>
<?php

foreach($_POST['ptitle'] as $key => $ptitle) {
print("<br><input type=\"checkbox\" name=\"ptitle[]\" value=\"$ptitle\" checked>" . $ptitle);
}

?>
</code>
This is the code from within a subroutine on my .pl page that emails the form contents:
<code>
print MAIL "PROPERTIES OF INTEREST: ";
foreach $ptitle (@ptitle)
{
print "$value ", "\n";
}
</code>
I've looked for any similar solutions and read perl books, but all it's maganged to do is confuse me even more! If I had a choice, I'd rewrite the .pl email in PHP, but that is not an option, so I need to make these 2 pages play nice together. I would appreciate any help you Perl and PHP experts can give me on this!
 
My apologies, I'm new to the forum and posted this incorrectly.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top