Manoj Shekhawat
Manoj Shekhawat

Reputation: 449

Sorting an array of hashes by multiple keys in Perl

I have an array reference containing hashes (i.e., @AOH):

$arr_ref = [ { 'brand' => 'A',
               'supplier' => 'X',
               'PO' => '2'
              },
              { 'brand' => 'B',
                'supplier' => 'Y',
                'PO' => '1'
              },
              { 'brand' => 'B',
                'supplier' => 'X',
                'PO' => '2'
              },
              { 'brand' => 'A',
                'supplier' => 'X',
                'PO' => '1'
              },
              { 'brand' => 'B',
                'supplier' => 'X',
                'PO' => '1'
              }
];

I want to sort it on the basis of all three keys (i.e., brand, supplier and PO). The order of sorting should be brand first, then supplier and then finally on PO.

The array reference after sorting should be:

$arr_ref = [ { 'brand' => 'A',
                'supplier' => 'X',
                'PO' => '1'
              },
              { 'brand' => 'A',
               'supplier' => 'X',
               'PO' => '2'
              },
              { 'brand' => 'B',
                'supplier' => 'X',
                'PO' => '1'
              },
              { 'brand' => 'B',
                'supplier' => 'X',
                'PO' => '2'
              },
              { 'brand' => 'B',
                'supplier' => 'Y',
                'PO' => '1'
              },
];

Upvotes: 25

Views: 24845

Answers (3)

Flimm
Flimm

Reputation: 150573

You can use Sort::Key::Multi, distributed with Sort::Key.

In this case, we're using ssikeysort, which expects a block that returns a string, a string and an integer, and which sorts the values by that tuple. (The s in ssi stands for string and the i for integer.)

use Sort::Key::Multi qw(ssikeysort);

@$arr_ref = ssikeysort { $_->{brand}, $_->{supplier}, $_->{PO} } @$arr_ref;

You can also use the in-place variant, which uses less memory:

use Sort::Key::Multi qw(ssikeysort_inplace);

ssikeysort_inplace { $_->{brand}, $_->{supplier}, $_->{PO} } @$arr_ref;

Upvotes: 2

cjm
cjm

Reputation: 62089

Since <=> and cmp return 0 to indicate equality, and that's false, and because Perl's logical Boolean operators return the deciding value instead of just 0 or 1, sorting by multiple keys is as easy as stringing multiple comparisons together with or or ||:

@$arr_ref = sort { $a->{brand}    cmp $b->{brand}    or 
                   $a->{supplier} cmp $b->{supplier} or 
                   $a->{PO}       <=> $b->{PO} 
                 } @$arr_ref;

I'm assuming that PO is a numeric field, so you use <=> for it instead of cmp.

Upvotes: 57

mttrb
mttrb

Reputation: 8345

The following should sort the array reference and place the array back into the $arr_ref:

$arr_ref = [sort by_brand_supplier_PO @$arr_ref];

sub by_brand_supplier_PO {
    $a->{brand} cmp $b->{brand} ||
    $a->{supplier} cmp $b->{supplier} ||
    $a->{PO} <=> $b->{PO}
}

Upvotes: 6

Related Questions