Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Sort an array of strings based on two fields

by luca76 (Initiate)
on Mar 14, 2013 at 10:06 UTC ( #1023442=perlquestion: print w/ replies, xml ) Need Help??
luca76 has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I have an array of strings:
xxxxxx.2012-50.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2012-52.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-2.yyyyy xxxxxx.2013-3.yyyyy xxxxxx.2013-4.yyyyy ... xxxxxx.2013-10.yyyyy xxxxxx.2013-11.yyyyy
how can I sort it with Perl, by year and the second number? There is no beggining 0 in the second number.

Comment on Sort an array of strings based on two fields
Download Code
Re: Sort an array of strings based on two fields
by choroba (Abbot) on Mar 14, 2013 at 10:18 UTC
    The simple way is to write a code for sort which tells Perl how to compare two elements of the list:
    #!/usr/bin/perl use warnings; use strict; use feature qw(say); my @strings = qw(xxxxxx.2012-50.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2012-52.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-2.yyyyy xxxxxx.2013-3.yyyyy xxxxxx.2013-4.yyyyy xxxxxx.2013-10.yyyyy xxxxxx.2013-11.yyyyy ); my @sorted = sort { my ($ay, $an) = $a =~ /\.([0-9]+)-([0-9]+)\./; my ($by, $bn) = $b =~ /\.([0-9]+)-([0-9]+)\./; $ay <=> $by or $an <=> $bn } @string; say for @sorted;

    As this might be rather slow for larger lists, various technics exist to speed it up. One of them is called Orcish Maneuvre:

    #!/usr/bin/perl use warnings; use strict; use feature qw(say); my @strings = qw(xxxxxx.2012-50.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2012-52.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-2.yyyyy xxxxxx.2013-3.yyyyy xxxxxx.2013-4.yyyyy xxxxxx.2013-10.yyyyy xxxxxx.2013-11.yyyyy ); my %cache; my @sorted = sort { ($cache{$a} //= generate_key($a)) cmp ($cache{$b} //= generate_key($b)) } @strings; say for @sorted; sub generate_key { my $s = (split /\./, shift)[1]; my ($year, $number) = split /-/, $s; return $year . sprintf '-%03d', $number; }

    Update: Simple solution added.

    لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
      As a small variant, you can calculate a key by 100*year+month, assuming that month will always be two digit at most...
      my @sorted = sort { ($a =~ /\.([0-9]+)-([0-9]+)\./, 100*$1+$2) <=> ($b =~ /\.([0-9]+)-([0-9]+)\./, 100*$1+$2) } @strings;
      or would it be ok to insert the missing zero?
      map { s/-(\d)\./-0\1./ } @strings; my @sorted = sort @strings;
      (Make sure the regexp does not match elsewhere in your strings.)
        I assumed 3 digits in the second solution. 50 seems too big for a months to me (maybe a week?)
        لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Sort an array of strings based on two fields
by BrowserUk (Pope) on Mar 14, 2013 at 10:18 UTC

    Using the GRT:

    print for @a;; xxxxxx.2013-2.yyyyy xxxxxx.2013-11.yyyyy xxxxxx.2013-10.yyyyy xxxxxx.2012-52.yyyyy xxxxxx.2013-3.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2012-50.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-4.yyyyy @b = map{ unpack 'x[NN]a*', $_ } sort map{ m[(\d{4})-(\d+)]; pack 'NNa*', $1, $2, $_ } @a;; print for @b;; xxxxxx.2012-50.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2012-52.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-2.yyyyy xxxxxx.2013-3.yyyyy xxxxxx.2013-4.yyyyy xxxxxx.2013-10.yyyyy xxxxxx.2013-11.yyyyy

    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.
Re: Sort an array of strings based on two fields
by Neighbour (Friar) on Mar 14, 2013 at 10:22 UTC
    Edit: Need to refresh before posting, choroba beat me to it :)
Re: Sort an array of strings based on two fields
by sundialsvc4 (Abbot) on Mar 14, 2013 at 13:21 UTC

    As perldoc -f sort will tell you, the sort verb in Perl takes a subroutine name or an in-line code block as its argument.   That piece of code is handed two variables named $a and $b corresponding to the two items now being compared, and it must return a value that is less than, equal to, or greater than zero.   Perl provides a special operator, <=>, specifically to assist with this.   Also cmp ... which compares strings.   (Do not get tripped-up by this peculiarity of Perl!)

    The || operator is also useful because it is a “short-circuit logical-OR.”   If the expression on the left side is nonzero (“true”), the right side won't be evaluated.   Perfect for chaining comparisons together to sort by more than one field.

    For anything but very simple cases, I prefer to define a separate sort-comparison subroutine, because an in-line code block can easily devolve into “chicken scratches.”   The code, however you choose to write it, should be extremely obvious, able to be tested in isolation, and also very easily changed.   It will be executed tens of thousands of times.

      As perldoc -f sort will tell you, weak description of it, and stories about how I'm cool like that in the obviousness of my code I say I write but never post on perlmonks
Re: Sort an array of strings based on two fields
by kcott (Abbot) on Mar 18, 2013 at 00:46 UTC

    G'day luca76,

    Your test data already appears to be sorted. Here's my take on a solution (with unsorted input):

    $ perl -Mstrict -Mwarnings -E ' my @input = qw{ xxxxxx.2013-2.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2013-10.yyyyy xxxxxx.2012-50.yyyyy xxxxxx.2013-1.yyyyy }; say $_->[0] for sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } map { [ $_, split /-/, (split /\./)[1] ] } @input; ' xxxxxx.2012-50.yyyyy xxxxxx.2012-51.yyyyy xxxxxx.2013-1.yyyyy xxxxxx.2013-2.yyyyy xxxxxx.2013-10.yyyyy

    -- Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1023442]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (12)
As of 2014-09-19 17:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (143 votes), past polls