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

Hi perlmonks,

I am having a array of hash as below. In that I want to sort decending order based on the value 'hyundai'. Any one have any idea about this ?

my $ArrayofHash = [ { 'TaskA' => { 'maruti' => '20', 'honda' => '25', 'zen' => '25', 'hyundai' => '35', 'ford' => '22', 'toyota' => '11' } }, { 'TaskB' => { 'maruti' => '11', 'honda' => '22', 'zen' => '33', 'hyundai' => '33', 'ford' => '24', 'toyota' => '16' } }, { 'TaskC' => { 'maruti' => '12', 'honda' => '22', 'zen' => '33', 'hyundai' => '44', 'ford' => '55', 'toyota' => '66' } } ];

Replies are listed 'Best First'.
Re: need to sort array of hash
by dsheroh (Monsignor) on Dec 22, 2018 at 11:19 UTC
    I have a solution, included below.

    It is an ugly solution. It is a fragile solution.

    This is because you have a horrible data structure[1] and have not fully specified the problem[2]. If you give it data where a "Task*" key has any sibling keys, you will get undefined results because there's no expectation that the correct inner hash will be the first one returned by values.

    But, with those caveats:

    #!/usr/bin/env perl use strict; use warnings; use 5.010; my $original = [ { 'TaskA' => { 'maruti' => '20', 'honda' => '25', 'zen' => '25', 'hyundai' => '35', 'ford' => '22', 'toyota' => '11' } }, { 'TaskB' => { 'maruti' => '11', 'honda' => '22', 'zen' => '33', 'hyundai' => '33', 'ford' => '24', 'toyota' => '16' } }, { 'TaskC' => { 'maruti' => '12', 'honda' => '22', 'zen' => '33', 'hyundai' => '44', 'ford' => '55', 'toyota' => '66' } } ]; my $sorted = [ sort { (values(%$b))[0]->{hyundai} <=> (values(%$a))[0]->{hyundai} } @$original ]; use Data::Printer; p $sorted;
    Output:
    \ [ [0] { TaskC { ford 55, honda 22, hyundai 44, maruti 12, toyota 66, zen 33 } }, [1] { TaskA { ford 22, honda 25, hyundai 35, maruti 20, toyota 11, zen 25 } }, [2] { TaskB { ford 24, honda 22, hyundai 33, maruti 11, toyota 16, zen 33 } } ]

    [1] The "Task*" layer is superfluous. It really looks like your sub-hashes with the actual data should be stored directly in the outer array, without the intermediate single-key hashes. Any time you think you want a hash with keys named "Foo1", "Foo2", "Foo3"..., odds are you actually want an array, not a hash.

    [2] What should be done if one of the intermediate-layer hashes has more than one "Task*" key, or any keys other than the single "Task*" key? (If that's never allowed to happen, then see [1].)

Re: need to sort array of hash
by kcott (Archbishop) on Dec 22, 2018 at 11:52 UTC

    I agree with ++dsheroh's assessment. Simplifying the data structure results in a simpler sort (i.e. easier to read and maintain).

    Here's a quick-and-dirty example to show the simplified sort logic:

    $ perl -E ' my %x = (A=>{h=>2}, B=>{h=>1}, C=>{h=>3}); say for sort { $x{$b}{h} <=> $x{$a}{h} } keys %x; ' C A B

    — Ken

Re: need to sort array of hash
by BillKSmith (Monsignor) on Dec 22, 2018 at 15:28 UTC
    I agree with the other monks that you probably should use a better data structure. However, you can use the Schwartzian Transform described in the FAQ to solve the original problem.
    $type 1227601.pl use strict; use warnings; use Data::Dumper; my $ArrayofHash = [ { 'TaskA' => { 'maruti' => '20', 'honda' => '25', 'zen' => '25', 'hyundai' => '35', 'ford' => '22', 'toyota' => '11' } }, { 'TaskB' => { 'maruti' => '11', 'honda' => '22', 'zen' => '33', 'hyundai' => '33', 'ford' => '24', 'toyota' => '16' } }, { 'TaskC' => { 'maruti' => '12', 'honda' => '22', 'zen' => '33', 'hyundai' => '44', 'ford' => '55', 'toyota' => '66' } } ]; my $sorted = [ map {$_->[1]} sort {$b->[0] <=> $a->[0]} #map {(my $h) = values %$_; [$h->{hyundai}, $_]} map {[(values %$_)[0]{hyundai}, $_]} @$ArrayofHash ]; print Dumper($sorted); $perl 1227601.pl $VAR1 = [ { 'TaskC' => { 'zen' => '33', 'toyota' => '66', 'maruti' => '12', 'hyundai' => '44', 'honda' => '22', 'ford' => '55' } }, { 'TaskA' => { 'ford' => '22', 'toyota' => '11', 'maruti' => '20', 'hyundai' => '35', 'honda' => '25', 'zen' => '25' } }, { 'TaskB' => { 'ford' => '24', 'honda' => '22', 'hyundai' => '33', 'maruti' => '11', 'toyota' => '16', 'zen' => '33' } } ]; $

    UPDATE: Modified code to simplify dereference. Note: Using the first (and only) element of values avoids the problem with not knowing the name of the key.

    Bill
      Note: Using the first (and only) element of values avoids the problem with not knowing the name of the key.
      ...but that only applies to the given sample data because the intermediate-level hash only contains a single key/value pair. If there are multiple keys/values in those hashes, you're screwed because values will return the values in a random order. (But the order is stable within a single run of the program, so long as the hash isn't modified, so at least it shouldn't throw sort into an infinite loop. You'll just potentially get results sorted on the wrong sub-hash.)
        If there were more key/value pairs in the intermediate-level hash, we would need additional specifications to tell us which one to use in the sort. You have provided a detailed explanation of why this design could not meet such a requirement. (I am not even sure it could be modified to do so.) Even if the recommended redesign of the data structure could not be justified for current structure, one more requirement should surely tip the balance.
        Bill
Re: need to sort array of hash
by hippo (Archbishop) on Dec 22, 2018 at 10:57 UTC

      yes, i red, but didn't any clue.

      need to do something like this:

      $Arrayofhash[$key]->{$a}->{hyundai} <=>  $Arrayofhash[$key]->{$b}->{hyundai}

        Brother dsheroh has shown the correct comparison for the dataset you have provided. However, I agree with my fellow monk that the data structure is horrible. If I had to deal with this I would probably refactor it to be something more like

        my $ArrayofHash = [ { name => 'TaskA', attrs => { 'maruti' => '20', 'honda' => '25', 'zen' => '25', 'hyundai' => '35', 'ford' => '22', 'toyota' => '11' } }, { name => 'TaskB', attrs => { 'maruti' => '11', 'honda' => '22', 'zen' => '33', 'hyundai' => '33', 'ford' => '24', 'toyota' => '16' } }, { name => 'TaskC', attrs => { 'maruti' => '12', 'honda' => '22', 'zen' => '33', 'hyundai' => '44', 'ford' => '55', 'toyota' => '66' } } ];

        This way all of your hashrefs have known keys so it's an easier comparison to codify. eg. $a->{attrs}->{hyundai} <=> $b->{attrs}->{hyundai}

        It depends on what your real dataset looks like whether or not this is an advisable approach.