Stacky
Stacky

Reputation: 33

Perl sort numbers naturally

I need to sort numbers and I am not able to get it to work the way I want.

Example input:

15.12
16.1
15.2
15.1

Expected output:

15.1
15.2
15.12
16.1

I tried normal sort and also Sort::Naturally for this. Neither works giving me the expected output.

I am also aware I can do something like the following to sort it the way I want.

my @sorted =
map sprintf('%vd', $_),
sort
map join('', map chr, split /\./),
@data;

I am wondering if there are some pre existent modules that I can use though.

Thanks in advance.

Upvotes: 3

Views: 2250

Answers (4)

mjgommo
mjgommo

Reputation: 11

Regarding the answer provided by @stevieb, value "15.2" was not dropped: it was missing in the original array. Operators <=> and cmp are not exchangeable, though. They are used to compare numbers and strings, respectively.

Upvotes: 1

ThisSuitIsBlackNot
ThisSuitIsBlackNot

Reputation: 24063

It looks like you're trying to sort version numbers, where each component is sorted independently. Sort::Naturally won't work because it ignores non-alphanumeric characters, but there are several other modules that can do this.

Sort::Versions splits its input on periods or hyphens and sorts each group either alphabetically or numerically, depending on whether non-digit characters are present:

use strict;
use warnings 'all';
use 5.010;

use Sort::Versions;

my @versions = (
    15.12,
    16.1,
    15.2,
    15.1
);

say for sort { versioncmp($a, $b) } @versions;

Output:

15.1
15.2
15.12
16.1

Sort::Versions expects input to match certain common version string formats; if you need to sort different formats than what you've shown, check the rules in the documentation to make sure it will work for you.


Sort::Key::Natural is more flexible because it splits on all word boundaries, not just periods and hyphens, but in this case it works the same:

use strict;
use warnings 'all';
use 5.010;

use Sort::Key::Natural qw(natsort);

my @versions = (
    15.12,
    16.1,
    15.2,
    15.1
);

say for natsort @versions;

(Output is the same as for Sort::Versions)

Sort::Key::Natural has some nice additional features, like the ability to sort in place and to customize the sort order. It was also significantly faster than Sort::Versions in my benchmarks, although that will only matter if you're sorting large arrays.

Upvotes: 4

sidyll
sidyll

Reputation: 59287

If I understood it right, you want to sort the numbers by integer value first and then consider the decimal part as an integer itself, so .12 is greater than .2 like in your example (12 > 2).

I think the most self explanatory way is to use a custom sort, after splitting them as you said:

@sorted = sort {
    my ($a1, $a2) = split /\./, $a;
    my ($b1, $b2) = split /\./, $b;
    $a1 <=> $b1 or $a2 <=> $b2
} @numbers;

Upvotes: 6

stevieb
stevieb

Reputation: 9296

You need to compare (<=>) the numbers with the special $a and $b variables. Note that you can do the same with strings by replacing <=> with cmp.

use warnings;
use strict;

my @nums = qw(15.1 16.1 15.12);

@nums = sort {$a <=> $b} @nums;

print "$_\n" for @nums;

__END__
15.1
15.12
16.1

Upvotes: 0

Related Questions