martin_87 has asked for the wisdom of the Perl Monks concerning the following question:

Hi PerlMonks people!

I have a rather tricky sort question that I've been trying to solve. In my array of hashes I've ID, Distance and RouteDistance. I want to sort on RouteDistance but this value is sometimes 0 and here comes the tricky part. When RouteDistance is 0 I want the sorting to be done on the Distance for this hash instead.

This example might make my problem clearer:

ID = 3, Distance = 2.3, RouteDistance = 4.3
ID = 2, Distance = 1.5, RouteDistance = 2.8
ID = 5, Distance = 2.1, RouteDistance = 0
ID = 1, Distance = 1.7, RouteDistance = 2.5

I want the sorting to be presented as this when the sorting is done:

ID = 1, Distance = 1.7, RouteDistance = 2.5
ID = 2, Distance = 1.5, RouteDistance = 2.8
ID = 5, Distance = 2.1, RouteDistance = 0
ID = 3, Distance = 2.3, RouteDistance = 4.3

Example code that I've been trying to make this work with...

From an XMLpost I'm receiving an array of hash references which im calling $xml_post_array{'data'}

In this first example I manage to sort by RouteDistance and in cases where RouteDistance is equal with another RouteDistance it will sort by Distance.
my @unsortedArray; foreach my $received_xml_post (@ {$xml_post_array{'data'}}) { push @unsortedArray, { 'ID' => $received_xml_post->{'ID'}, 'Distance => $received_xml_post->{'Dis +tance'}, 'RouteDistance' => $received_xml_post- +>{'RouteDistance'}}; } my @sortedArray = sort { $a->{RouteDistance} <=> $b->{RouteDistance || + $a->{Distance} <=> $b->{Distance} } @unsortedArray;
In the next example I want to get around the RouteDistance cases where RouteDistance is equal to 0.
my @unsortedArray; foreach my $received_xml_post (@ {$xml_post_array{'data'}}) { push @unsortedArray, { 'ID' => $received_xml_post->{'ID'}, 'Distance => $received_xml_post->{'Distance'} +, 'RouteDistance' => $received_xml_post->{'Rout +eDistance'}}; } my @sortedArray = sort { if( $a->{RouteDistance} == 0) {return -1} els +e {$a->{RouteDistance} <=> $b->{RouteDistance} $a->{Distance} <=> $b- +>{Distance} } @unsortedArray;

The 2nd code example will just sort everything on Distance. I've done an ugly version of this sorting that first sorts on Distance. Then I loop trough this and save current RouteDistance and in cases where RouteDinstance is equal to 0 I just pick up that previous RouteDistance and add 0.001 to it. However, this solution does not work with exreame cases and therefore I want to find out if there is a better way to handle this type of sorting. I'm sorry in advance for my English (not my native language). This is my first post here by the way so I might have done something wrong with this post but just let me know and I will now to next time.

Update 1: One of the suggestions that was close to achiveing this was this peace of code:

sort { $a->{routedistance} && $b->{routedistance} ? $a->{routedistance} <=> $b->{routedistance} : $a->{distance} <=> $b->{distance} } @ar;

unfortunately, this turned out to not work as I wanted when the RouteDistance was pre-ordered where one of the RouteDistance was zero.

I can add one thing to the table. The value of RouteDistance is not important, as long as it is sorted as I've described above. What I mean by this is that cases where RouteDistance is zero, this value can be replaces to something else, for example previous RouteDistance or nearest + 0.01 / -0.01. The value of RouteDistance will not be used to anything else then just sorting which makes this one option.

Update 2: The problem is not yet solved and and have no good solution for it.. :/

Update 3: The problem is solved. It might be some extreme cases where this all falls apart but with the tests I've done it works.

The solution:

#!/usr/bin/perl use warnings; use strict; my @unsorted_array = ( { ID => 3, Distance => 2.3, RouteDistance => + 4.3 }, { ID => 2, Distance => 1.5, RouteDistance => 2 +.8 }, { ID => 5, Distance => 2.1, RouteDistance => 0 + }, { ID => 1, Distance => 1.7, RouteDistance => 2 +.5 }); my @sorted = sort { if($a->{RouteDistance} == 0 || $b->{RouteDistance} == 0) { return ($a->{Distance} < $b->{Distance}) ? -1 : 1; } elsif ($a->{RouteDistance} == $b->{RouteDistance}) { return 0; } else { return ($a->{RouteDistance} < $b->{RouteDistance}) ? -1 : 1; } } @unsorted_array; foreach my $sorted_row (@sorted) { print "ID: ".$sorted_row->{'ID'}." Distance: ".$sorted_row->{'Dist +ance'}." RouteDistance: ".$sorted_row->{'RouteDistance'}."\n"; }

Replies are listed 'Best First'.
Re: Sorting an array of hashes with an exeption
by choroba (Cardinal) on May 08, 2013 at 16:48 UTC
    Just tell sort that if the RouteDistance is zero, it should use Distance. Note that the result is different to the one you gave.
    #!/usr/bin/perl use warnings; use strict; my @ar = ( { ID => 3, Distance => 2.3, RouteDistance => 4.3 }, { ID => 2, Distance => 1.5, RouteDistance => 2.8 }, { ID => 5, Distance => 2.1, RouteDistance => 0 }, { ID => 1, Distance => 1.7, RouteDistance => 2.5 }, ); print $_->{ID}, "\n" for sort { ( $a->{RouteDistance} || $a->{Distance} ) <=> ( $b->{RouteDistance} || $b->{Distance} ) } @ar;

    Upate : Fixed precedence.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      That's not quite right.

      If one routedistance is 0 and the other not, you are comparing a routedistance with a distance, which is probably the cause of the disparity between your results and the OPs expectations.

      Something like this ought to do it:

      sort { $a->{routedistance} && $b->{routedistance} ? $a->{routedistance} <=> $b->{routedistance} : $a->{distance} <=> $b->{distance} } @ar;

      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        Thanks. I misunderstood "for this hash" in the specification.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
        BrowserUK you're the boss! It works great, thank you so much!
      Hi, thanks for the fast reply. The code which you provided gave me this result:

      ID = 5, Distance = 2.1, RouteDistance = 0
      ID = 1, Distance = 1.7, RouteDistance = 2.5
      ID = 2, Distance = 1.5, RouteDistance = 2.8
      ID = 3, Distance = 2.3, RouteDistance = 4.3

      It looks like it sorts by RouteDistance only here.

      The answer which BrowserUK provided works like a charm but I would like to know if you could provide an example where i add an argument as you where hinting about?
        It looks like it sorts by RouteDistance only here.
        Not really. Try setting Distance to 3.1 at ID 5.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
      Hi again, it does not change the output though? I'm still getting this as the result:

      ID: 5 Distance: 2.1 RouteDistance: 0
      ID: 1 Distance: 1.7 RouteDistance: 2.5
      ID: 2 Distance: 1.5 RouteDistance: 2.8
      ID: 3 Distance: 2.3 RouteDistance: 4.3

        Sure. 2.1 < 2.5, therefore 5 gets first. Try setting Distance of 5 to 3.1.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Sorting an array of hashes with an exeption
by Laurent_R (Canon) on May 08, 2013 at 20:28 UTC

    Have you considered that this problem might not have a solution for some inputs? At least not with the rules that you have given us (as I understood them).

    Consider these data records, already pre-sorted according to route distance.

    ID = 5, Distance = 1,6, RouteDistance = 0

    ID = 1, Distance = 1.7, RouteDistance = 2.5

    ID = 2, Distance = 1.5, RouteDistance = 2.8

    ID = 3, Distance = 2.3, RouteDistance = 4.3

    IDs 1 2 and 3 are well sorted and need to stay in this order. Then, of course, the first line needs to be moved to be inserted somewhere else according to the Distance. But where are you going to insert it? Because IDs 1, 2 and 3 are not ordered for distance, it is not possible to insert ID 5 anywhere. For example, if you put it after ID 2, OK, it will be OK compared to ID 2 and 3, but it will be after ID 1, although its distance is smaller. Any other solution will fail likewise.

      Hi Laurent_R,

      When using your example data I get the following result:

      ID: 2 Distance: 1.5 RouteDistance: 2.8
      ID: 5 Distance: 1.6 RouteDistance: 0
      ID: 1 Distance: 1.7 RouteDistance: 2.5
      ID: 3 Distance: 2.3 RouteDistance: 4.3

      As you thought this is not what I want, hmm. Need to think something else out.