#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2005
    Posts
    54
    Rep Power
    10

    Sorting an array based on sort order of another array


    Let's say you have an array of names and a corresponding array of salaries, i.e. the first name earns the first salary, etc. The salaries can have multiple identical entries. Now I want to know who earns the most, the second most, etc. I can sort the salaries:

    Code:
    @salaries = sort { $a <=> $b } @salaries;
    Is there a simple way that I can then ask that the names array sort itself in the same order that the salaries array sorted itself?

    Thank you.

    Eric
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Apr 2009
    Posts
    1,940
    Rep Power
    1225
    Sounds like you're using the wrong data structure. You should be using 1 hash instead of 2 arrays.
    Code:
    use Data::Dumper;
    
    my @names = qw(mary bill sue randy);
    my @salaries = qw(150 175 100 200);
    
    my %wages;
    @wages{@names} = @salaries;
    
    print Dumper \%wages;
    From there you can sort by either name or salary or a combination of both.
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2012
    Posts
    836
    Rep Power
    496
    And, having done what Fishmonger proposed, when you want to sort the hash keys according to the hash values, you can use something like this:

    Perl Code:
    my @sorted_persons = sort {$wages{$a} <=> $wages{$b} } keys  %wages;

    Or, if you prefer:

    Perl Code:
    print map {"$_: $wages{$_} \n"} sort {$wages{$a} <=> $wages{$b} } keys  %wages;


    which will print:

    Code:
    sue: 100
    mary: 150
    bill: 175
    randy: 200
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2005
    Posts
    54
    Rep Power
    10
    Hi Fishmonger and Laurent R,

    Thanks so much. This is enormously helpful. Also, I wasn't aware of some of the syntax, like this:

    Code:
    @wages{@names} = @salaries;
    I really appreciate the help.

    Best wishes,

    Eric
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Apr 2009
    Posts
    1,940
    Rep Power
    1225
    That is a hash slice.

    perldoc perldata
    Hash slices can replace loops
    google search

    I only used the hash slice because you already had the 2 arrays but didn't show us how you built them. It would be best to build the hash directly instead of building the arrays.
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2005
    Posts
    54
    Rep Power
    10
    I'm still a bit confused here. This chunk of code says "sort the keys of %wages based on going through all of the values of wages and sorting those values numerically (<=>)."

    Code:
    my @sorted_persons = sort {$wages{$a} <=> $wages{$b} } keys  %wages;
    But here, I'm confused:

    Code:
    print map {"$_: $wages{$_} \n"} sort {$wages{$a} <=> $wages{$b} } keys  %wages;
    map is going to apply some function across each item in an array. The array is the same as @sorted_persons above. The function is put in curly brackets, and it's just to take each key for %wages and stick that into $_ and then create (?) the appropriate phrase. Or something like that? And then print prints out the whole thing? Sorry for my ignorance here, but I'm a bit confused and any guidance would be appreciated.

    Thanks.

    Eric
  12. #7
  13. !~ /m$/
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    May 2004
    Location
    Reno, NV
    Posts
    4,262
    Rep Power
    1810
    I guess you could say that a code fragment like:

    Code:
    print map {"$_: $wages{$_} \n"} sort {$wages{$a} <=> $wages{$b} } keys  %wages;
    is a bit too clever.

    It's not completely obfuscated, but it's doing a lot in a single line, that's for sure.

    Map is a function that works on an array. Actually, all the functions in that line work with lists, or arrays. Print prints a list of strings. You generally only see a single item, but it works with lists, so you can print an array with one line for example.

    Map works with a list. Here it is building an array of strings for the print function. Sort works on an array obviously, and 'keys' is a function that returns a list.

    So this perhaps could have been written more clearly as four lines.

    Code:
    #!/usr/bin/perl
    use strict;
    use warnings;
    
    use Data::Dumper;
    
    my %wages = ('jimmy' => 20000, 'don' => 8000, 'doug' => 10000 );
    
    # give me the keys in the hash
    my @keys = keys %wages;
    # create an array of keys sorted by the values they point to
    # in ascending order
    my @sorted = sort {$wages{$a} <=> $wages{$b} } @keys;
    # create an array of strings which show both
    # the keys and values in this sorted order
    my @strings = map {"$_: $wages{$_} \n"} @sorted;
    # print all the strings
    print @strings;
    Last edited by keath; May 9th, 2013 at 10:00 PM. Reason: added comments
  14. #8
  15. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2012
    Posts
    836
    Rep Power
    496
    From her or his question, I think that the main point that EJF did not understand is not so much the chaining up of list functions, but the way the map function works.

    Map is taking each element of the array or list passed to it, aliases successivly each of these elements to $_ and applies the code block to return another list.

    So that, in:

    Perl Code:
    my @strings = map {"$_: $wages{$_} \n"} @sorted;


    each element of the @sorted array is aliased to $_ and the map functions creates a string made of the array element ($_), a colon and a space, the hash value corresponding to $_, and a new line character, and return that string into the @string array.

    It could be rewritten as follows:

    Perl Code:
    my @strings;
    push @strings, "$_: $wages{$_} \n" foreach @sorted;


    or:
    Perl Code:
    my @strings;
    foreach my $value (@sorted) {
         push @strings, "$value: $wages{$value} \n" ;
    }
  16. #9
  17. Banned ;)
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2001
    Location
    Woodland Hills, Los Angeles County, California, USA
    Posts
    9,641
    Rep Power
    4247
    Heh, are we going to demonstrate the schwartzian transform next . I covered this technique on this very forum many years ago. That thread also has a discussion of exactly how the sorting works.
    Up the Irons
    What Would Jimi Do? Smash amps. Burn guitar. Take the groupies home.
    "Death Before Dishonour, my Friends!!" - Bruce D ickinson, Iron Maiden Aug 20, 2005 @ OzzFest
    Down with Sharon Osbourne

    "I wouldn't hire a butcher to fix my car. I also wouldn't hire a marketing firm to build my website." - Nilpo
  18. #10
  19. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2012
    Posts
    836
    Rep Power
    496
    I do not think that my code above was obfuscated. I can understand that it is not obvious for someone not used for this type of LISP-like chaining up of list functions, but the construct is actually very natural, somewhat similar with successions of pipes under Unix, in fact simpler that the "decorate-sort-undecorate" concept of the Schwaztian transform".

    Also, I gave that code only as a second option, as a slightly more fancy alternative to a simpler and typical pure sort example.

    This is nowhere as contrived syntax as the following example:

    Perl Code:
    while (my $line = <$FILE_IN>) { 
            my ($start, $end) = split /##/, $line;
            my $out_line = $start . "##" . join ";",
                map {$_->[0]}
                sort {$a->[1] <=> $b->[1]} 
                map { my ($id, @values) = split /;/, $_;
                    @values = map { (split /-/, $_)[0]} @values;
                    map {[$id, $_]} @values;}
                split /@/, $end;
        }


    which I posted on this forum about 3 weeks ago.

    Using three split's, four map's, one sort and one join in the same instruction is a performance that leaves the Schwartzian transform far behind. ;-)

    And the aim was not to illustrate the power of such constructs with a crazy example made for that purpose, this code actually solved a real question really asked by someone on this forum.

    But, then, to tell the truth, I certainly did not recommend such a construct (here, I think I would accept the word obfuscated, even though obfuscation was certainly not the intent).

    It was just a somewhat extreme example that I gave (on the second page of this post: http://forums.devshed.com/perl-programming-6/data-parsing-was-need-help-in-perl-943498.html?pp=15), after explaining a similar construct made in two successive instructions instead of just one. As I explained on that post, I originally really needed to do it in two parts to check that the constructed data structure was what I wanted.
  20. #11
  21. !~ /m$/
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    May 2004
    Location
    Reno, NV
    Posts
    4,262
    Rep Power
    1810
    My apologies. I did say "It's not completely obfuscated", when I did mean "It's not obfuscated."

    But it is a busy line. By breaking it out, I hope it will be clearer to the poster, or perhaps he can single out one line as the source of his confusion.
  22. #12
  23. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2012
    Posts
    836
    Rep Power
    496
    Originally Posted by keath
    My apologies. I did say "It's not completely obfuscated", when I did mean "It's not obfuscated."

    But it is a busy line. By breaking it out, I hope it will be clearer to the poster, or perhaps he can single out one line as the source of his confusion.
    Don't worry Keath, I did not feel offended and you don't need to apologize.

    When I said that I did not think it was obfuscated, I only meant to say that using chained list operators is a very powerful feature of Perl (not only Perl, BTW, but Perl does is quite neatly), and that I do think that when the chaining-up is very linear (one list is just fed to the next operator, and so on) as in my suggested solution, it is in my view actually quite easy to understand with a minimal amount of explanation.

    I think it is a very useful and powerful feature of Perl lists operators, and that it can be used more broadly without falling into the trap of obfuscation.

    The other example I gave, the new one in my earlier reply, is definitely starting to get quite obfuscated, I agree, although it is written as cleanly as possible and without trying to obfuscate at all, it is just getting slightly too complicated. But, as I said before, this was just an example of how far the paradigm could be pushed, even on a real life example. I would not use such a hairy construct in real life, and my original solution was spliting it into two parts, one to create the data structure in memory (and check under the debugger that it was correct), and a second one to output the result.
  24. #13
  25. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2005
    Posts
    54
    Rep Power
    10
    I, the original poster, am really grateful for this discussion. Not only did it answer my original question, but I learned a lot of other stuff. For example, I wasn't even aware of the concept of perl lists. Googling that taught me something. Anyway, thanks all for the responses and discussion. It really helped me.

    Eric

IMN logo majestic logo threadwatch logo seochat tools logo