Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

sorting an array with decimal points

by levW (Novice)
on Jan 14, 2018 at 07:51 UTC ( #1207205=perlquestion: print w/replies, xml ) Need Help??
levW has asked for the wisdom of the Perl Monks concerning the following question:

HI, need to sort the array with members that look like: Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2
Want to get rid of string "Patch_" and compare only by the decimal point numbers.
The following code does not sort it in perfect order, because of the decimal point :

sort  { substr($a, 6,2) <=> substr($b, 6,2)} @array

Would appreciate any ideas.thanks

Replies are listed 'Best First'.
Re: sorting an array with decimal points (updated)
by Athanasius (Bishop) on Jan 14, 2018 at 08:23 UTC

    Hello levW,

    Although each member of the sample data starts with Patch_, I would still play it safe and allow for the possibility of a different prefix:

    use strict; use warnings; my @array = qw(Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch +_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Apatch_11.0); @array = sort patch_sort @array; print "$_\n" for @array; sub patch_sort { my ($a_name, $a_version) = $a =~ / (\w+) _ ([\d.]+) $ /x or die 'Invalid lhs, stopped'; my ($b_name, $b_version) = $b =~ / (\w+) _ ([\d.]+) $ /x or die 'Invalid rhs, stopped'; return $a_name cmp $b_name || $a_version <=> $b_version; }

    Output:

    18:22 >perl 1860_SoPW.pl Apatch_11.0 Patch_1.0 Patch_2.0 Patch_3.1 Patch_4.2 Patch_5.0 Patch_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 18:22 >

    See sort.

    Update 1: Better yet, just use the Sort::Naturally module from CPAN:

    use strict; use warnings; use Sort::Naturally; my @array = qw(Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch +_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Apatch_11.0); @array = nsort @array; print "$_\n" for @array;

    for the same result.

    Update 2: I should have read the documentation for Sort::Naturally more carefully: :-(

    I define "numeric substring" just as sequences matching m/\d+/ -- scientific notation, commas, decimals, etc., are not seen.

    Thanks salva: good catch!

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Better yet, just use the Sort::Naturally module from CPAN

      Sort::Naturally does not handle numbers with decimals correctly. For instance, nsort '1.10', '1.31', '1.4' returns 1.4 1.10 1.31.

      But you can use Sort::Key::Natural instead which provides a set of functions for sorting strings embedding numbers with decimals:

      use Sort::Key::Natural qw(natwfsort); my @sorted = natwfsort @array;

      Update: Though, taking into account that the data in the OP says Patch_, maybe those numbers are actually version numbers and then the part after the decimal dot may be better sorted as an independent integer, which is what a regular natural sort does...

        Your comment is relevant and right, but I would tend to agree with your update and I assumed that these numbers are version numbers rather than decimals, so that 1.4 1.10 1.31 would probably be the correct order for such data.

        But we will be able to figure out for sure only if the OP comes back and tells us.

Re: sorting an array with decimal points
by syphilis (Bishop) on Jan 14, 2018 at 08:16 UTC
    Hi,

    Untested:
    sort { substr($a, 6) <=> substr($b, 6)} @array
    Cheers,
    Rob
      Hi syphilis

      I first thought it would never work because 10.1 would come before 2.1, but then, thinking back about it, I wondered whether it might actually work thanks to the coercion of version numbers into decimal numbers imposed by the <=> operator, but I wasn't quite sure. So, I just tried it, and yes, it does work properly:

      $ perl -e 'use strict; > use warnings; > use feature "say"; > > my @array = qw(Patch_11.4 Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Pa +tch_4.2 Patch_6.0 > Patch_11.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 P +atch_11.2); > my @array2 = sort { substr($a, 6) <=> substr($b, 6)} @array; > say for @array2; > ' Patch_1.0 Patch_2.0 Patch_3.1 Patch_4.2 Patch_5.0 Patch_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Patch_11.0 Patch_11.2 Patch_11.4
        Hi Laurent_R,

        it might actually work thanks to the coercion of version numbers into decimal numbers imposed by the <=> operator

        Yes, the spaceship operator will always compare in numeric context - and I was quite confident that if the only problem with levW's
        sort { substr($a, 6,2) <=> substr($b, 6,2)} @array
        was that it failed to sort in the correct order, then
        sort { substr($a, 6) <=> substr($b, 6)} @array
        would fix that.

        But levW seemed to think that there was a problem re the decimal point (which I don't see), and I was a little unsure about other aspects of the requirements, so I shied away from investing much time into my reponse.

        Cheers,
        Rob

        Thanks all for great solutions. Ended up with Athanasius one...Cheers.

Re: sorting an array with decimal points
by Laurent_R (Canon) on Jan 14, 2018 at 09:12 UTC
    Hi levW,

    this is a typical case for using the Schwartzian Transform (see https://en.wikipedia.org/wiki/Schwartzian_transform):

    use strict; use warnings; use feature "say"; my @array = qw(Patch_11.4 Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patc +h_4.2 Patch_6.0 Patch_11.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Pat +ch_11.2); @array = map { $_->[0] } sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } map { /Patch_(\d+)\.(\d+)/; [$_, $1, $2] } @array; say for @array;
    The map on the last line creates an anonymous array in which the items are array refs containing the whole string, the first number and the second number (for example: [Patch_11.4, 11, 4]). The items are then sorted according to the first number and then according to the second number. At the end, the map on the first line retrieves the original strings from the sorted array refs.

    Output:

    $ perl sort_versions.pl Patch_1.0 Patch_2.0 Patch_3.1 Patch_4.2 Patch_5.0 Patch_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Patch_11.0 Patch_11.2 Patch_11.4

      G'day Laurent,

      An alternative to this could be a Guttman-Rosler Transform. In the following, I've kept the same data, in the same order; and, I retained the same basic code layout for the transform.

      #!/usr/bin/env perl -l use strict; use warnings; my @array = qw{ Patch_11.4 Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch_6.0 Patch_11.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Patch_11.2 }; @array = map substr($_, 2), sort map pack("C2", /^Patch_(\d+)\.(\d+)/) . $_, @array; print for @array;

      The output is identical to what you show.

      Just as a side note, given the list context provided by "[ ... ]", regex captures will be evaluated in that context, and your second map would only need a single statement. Here's a quick one-liner to explain:

      $ perl -E 'my @x = qw{X1.2 X3.4}; say "@$_" for map { [ $_, /X(\d+)\.( +\d+)/ ] } @x' X1.2 1 2 X3.4 3 4

      — Ken

        Hi Ken,

        Thank you for your comment. In fact, I had briefly thought about the GRT, but did not find a nice implementation of it in that specific case, so I stuck to the more traditional ST.

        You're right about that fact that the initial map in the ST solution can be made in only one statement. Given that the ST is a bit complicated to understand for a beginner not knowing about it, I thought it would be a little clearer with two separate statements in the map, I'm no longer sure that this is really the case.

      thanks,will need a deeper look into this code...:)
Re: sorting an array with decimal points
by karlgoethebier (Monsignor) on Jan 14, 2018 at 16:17 UTC
    "...get rid of string "Patch_" and compare only by the decimal point numbers...any ideas..."

    Yep. Or i hope so. To me the output of this little TMTOWTDI looks "natural":

    #!/usr/bin/env perl use strict; use warnings; use feature qw(say); use Data::Dump; my @array = qw(Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch_6.0 Patch +_7.0 Patch_8.0 Patch_9.3 Patch_10.2); dd \@array; say for sort { $a <=> $b } map { /.+_(.+)/; $1 } @array; __END__ karls-mac-mini:playground karl$ ./levW.pl [ "Patch_1.0", "Patch_2.0", "Patch_3.1", "Patch_5.0", "Patch_4.2", "Patch_6.0", "Patch_7.0", "Patch_8.0", "Patch_9.3", "Patch_10.2", ] 1.0 2.0 3.1 4.2 5.0 6.0 7.0 8.0 9.3 10.2

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: sorting an array with decimal points
by salva (Abbot) on Jan 15, 2018 at 07:51 UTC
    You can also use Sort::Key:
    use Sort::Key qw(nkeysort); # the 'n' is for numeric my @sorted = nkeysort { substr $_, 6 } @array;
Re: sorting an array with decimal points
by tybalt89 (Vicar) on Jan 15, 2018 at 04:04 UTC

    Just delete what you don't want to sort by:

    #!/usr/bin/perl -l # http://perlmonks.org/?node_id=1207205 use strict; use warnings; my @array = qw{ Patch_11.4 Patch_1.0 Patch_2.0 Patch_3.1 Patch_5.0 Patch_4.2 Patch_6.0 Patch_11.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2 Patch_11.2 }; print for sort { $a =~ tr/0-9.//cdr <=> $b =~ tr/0-9.//cdr } @array;
Re: sorting an array with decimal points
by AnomalousMonk (Chancellor) on Jan 15, 2018 at 17:16 UTC

    The implicit specification of the OP seems unclear in one respect: How should a version number of, e.g., '4.20' compare to '4.2'? Is '4.20' equal to or greater than '4.2'? Is a version number like '4.20' even possible in levW's application? (Update: And are these even version numbers?)

    Except for kcott's solution (update: and Laurent_R's solution), all the solutions presented in this thread seem to use a numeric sort comparison, which will sort '4.2' adjacent to '4.20'.


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

      ... all the solutions presented in this thread seem to use a numeric sort comparison, which will sort '4.2' adjacent to '4.20'.
      Not the Schwartzian Transform solution I originally presented (Re: sorting an array with decimal points). Even though the OP is indeed unclear and speaks about decimal point numbers, I considered, given the data sample, that these were really version numbers, not decimal numbers, and sorted them accordingly. So that between 4.2 and 4.20, you might find, for example, 4.7, 4.8, and 4.18. This is again my ST solution with no change to the code except for the input data:
      use strict; use warnings; use feature "say"; my @array = qw(Patch_11.4 Patch_1.0 Patch_4.22 Patch_3.1 Patch_5.0 Pat +ch_4.2 Patch_6.0 Patch_4.8 Patch_4.7 Patch_4.20 Patch_9.3 Patch_10.2 Pat +ch_11.2 Patch_4.18); @array = map { $_->[0] } sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } map { /Patch_(\d+)\.(\d+)/; [$_, $1, $2]} @array; say for @array;
      And the output:
      $ perl sort_versions.pl Patch_1.0 Patch_3.1 Patch_4.2 Patch_4.7 Patch_4.8 Patch_4.18 Patch_4.20 Patch_4.22 Patch_5.0 Patch_6.0 Patch_9.3 Patch_10.2 Patch_11.2 Patch_11.4
      Now, of course, this assumption about version numbers may be wrong, only the OP can clarify that with certainty.
        Not the ... solution I originally presented (Re: sorting an array with decimal points).

        Indeed, I should have noticed that the comparison was a multi-key numeric and therefor a version-number-ish comparison. I've added an appropriate update here.


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

Re: sorting an array with decimal points
by Marshall (Abbot) on Jan 15, 2018 at 01:44 UTC
    To sort the input array, try this:
    Compare decimal numbers for each line.
    #!/usr/bin/perl use strict; my @names = qw (Patch_8.0 Patch_9.3 Patch_10.2 Patch_1.0 Patch_2.0 Pat +ch_3.1 Patch_5.0 Patch_4.2 Patch_6.0 Patch_7.0); my @patches = sort { my ($A) = $a=~ m/([\d\.]+)/; my ($B) = $b=~ m/([\d\.]+)/; $A <=> $B}@names; print "$_\n" for @patches; __END__ Patch_1.0 Patch_2.0 Patch_3.1 Patch_4.2 Patch_5.0 Patch_6.0 Patch_7.0 Patch_8.0 Patch_9.3 Patch_10.2
    A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (2)
As of 2019-04-19 05:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    I am most likely to install a new module from CPAN if:
















    Results (106 votes). Check out past polls.

    Notices?
    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!