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

Hello Monks, I would like to sort @food by putting all non-fruit at the end. More foods could be added.
my @food; push @food, { name => "apple", number => 1 }; push @food, { name => "banana", number => 2 }; push @food, { name => "orange", number => 5 }; push @food, { name => "broccoli", number => 9 }; push @food, { name => "grape", number => 10}; my %ranking = ( 1 => 0, 2 => 1, 5 => 2, 10 =>3 ); my @sorted = sort { $ranking{$a->{number}} <=> $ranking{$b->{number}} + } @food; say Dumper(\@sorted); $VAR1 = [ { 'name' => 'apple', 'number' => 1 }, { 'number' => 9, 'name' => 'broccoli' }, { 'number' => 2, 'name' => 'banana' }, { 'name' => 'orange', 'number' => 5 }, { 'number' => 10, 'name' => 'grape' } ];
Why is broccoli not at the end of the list? I know there's no entry for 9, but why is it inserted there? Thanks

Replies are listed 'Best First'.
Re: sort an array of hashes by value
by BrowserUk (Patriarch) on Jul 14, 2016 at 00:35 UTC
    Why is broccoli not at the end of the list? I know there's no entry for 9, but why is it inserted there?

    Because the lookup of 9 in %ranking returns undef, and undef (issues a warning and) acts as 0 in a numerical comparison. Hence broccoli gets sorted as if its ranking was 0.

    If you want unranked foods to sort to the end of the list, you could do something like this:

    print %{$_} for sort { ( $ranking{$a->{number}} // 1e308 ) <=> ( $ran +king{ $b->{number}} // 1e308 ) } @food;; number 1 name apple number 2 name banana number 5 name orange number 10 name grape number 9 name broccoli

    Note: the parens are required.


    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". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Ah, I never would have thought of that. Works beautifully, thanks!
Re: sort an array of hashes by value
by Marshall (Canon) on Jul 14, 2016 at 01:05 UTC
    Another idea for you... Maintaining the two tables in sync is a hassle. I was just keying upon I would like to sort @food by putting all non-fruit at the end. Doesn't solve the ranking problem within FRUIT, I just used alpha sort.
    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use constant FRUIT => 0; use constant VEGGIE =>1; my @food; push @food, { name => "apple", type => FRUIT }; push @food, { name => "banana", type => FRUIT }; push @food, { name => "orange", type => FRUIT }; push @food, { name => "broccoli", type => VEGGIE }; push @food, { name => "grape", type => FRUIT }; my @sorted = sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @food; print Dumper(\@sorted); __END__ $VAR1 = [ { 'name' => 'apple', 'type' => 0 }, { 'type' => 0, 'name' => 'banana' }, { 'type' => 0, 'name' => 'grape' }, { 'name' => 'orange', 'type' => 0 }, { 'type' => 1, 'name' => 'broccoli' } ];
    Update: As a practical matter, part of my thinking is that a data structure like this:
    my %ranking = ( 1 => 0, 2 => 1, 5 => 2, 10 =>3 );
    will be very hard to maintain manually. Here it is possible. But if the tables grow to any size, it won't be feasible and some other structure will be needed to describe the rankings that a human can deal with.
      Great tips, you are right about the maintainability. Thank-you!
Re: sort an array of hashes by value
by AnomalousMonk (Archbishop) on Jul 14, 2016 at 06:20 UTC

    You have a list of items that are both ordered and unordered with respect to each other, and you want to sort this list to an ordered list. The definition of "ordered" is that the value of an item's  number key exists as a key in the  %ranking hash.

    Sorting ordered and | ordered items that are promiscuously mingled with unordered items is tricky. I think I would approach the problem in two steps: first, extract and sort all "ordered" (per the definition above) items; second, tack all the unordered items on to the end of the sorted list.

    c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -le "my @food = ( { name => 'apple', number => 1 }, { name => 'banana', number => 2 }, { name => 'orange', number => 5 }, { name => 'broccoli', number => 9 }, { name => 'grape', number => 10 }, { name => 'kale', number => 8 }, { name => 'lemon', number => 5 }, ); ;; my %ranking = ( 1 => 0, 2 => 1, 5 => 2, 10 => 3, ); ;; my @ordered_then_unordered = sort { $ranking{$a->{number}} <=> $ranking{$b->{number}} } grep { exists $ranking{$_->{number}} } @food ; push @ordered_then_unordered, grep { ! exists $ranking{$_->{number}} } @food ; ;; dd \@ordered_then_unordered; " [ { name => "apple", number => 1 }, { name => "banana", number => 2 }, { name => "orange", number => 5 }, { name => "lemon", number => 5 }, { name => "grape", number => 10 }, { name => "broccoli", number => 9 }, { name => "kale", number => 8 }, ]


    Give a man a fish:  <%-{-{-{-<

      Very interesting approach - good ideas. Thanks for thinking about my problem.