Re: sorting an array with decimal points (updated)
by Athanasius (Archbishop) 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,
| [reply] [d/l] [select] |
|
|
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... | [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
Re: sorting an array with decimal points
by syphilis (Archbishop) on Jan 14, 2018 at 08:16 UTC
|
sort { substr($a, 6) <=> substr($b, 6)} @array
Cheers, Rob | [reply] [d/l] |
|
|
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
| [reply] [d/l] [select] |
|
|
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 | [reply] [d/l] [select] |
|
|
|
|
| [reply] |
|
|
Re: sorting an array with decimal points
by Laurent_R (Canon) on Jan 14, 2018 at 09:12 UTC
|
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
| [reply] [d/l] [select] |
|
|
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
| [reply] [d/l] [select] |
|
|
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.
| [reply] [d/l] |
|
|
thanks,will need a deeper look into this code...:)
| [reply] |
Re: sorting an array with decimal points
by karlgoethebier (Abbot) on Jan 14, 2018 at 16:17 UTC
|
#!/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
| [reply] [d/l] [select] |
Re: sorting an array with decimal points
by salva (Canon) on Jan 15, 2018 at 07:51 UTC
|
use Sort::Key qw(nkeysort); # the 'n' is for numeric
my @sorted = nkeysort { substr $_, 6 } @array;
| [reply] [d/l] |
Re: sorting an array with decimal points
by tybalt89 (Monsignor) on Jan 15, 2018 at 04:04 UTC
|
#!/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;
| [reply] [d/l] |
Re: sorting an array with decimal points
by AnomalousMonk (Archbishop) 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: <%-{-{-{-<
| [reply] [d/l] |
|
|
... 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.
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
Re: sorting an array with decimal points
by Marshall (Canon) 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
| [reply] [d/l] |
| A reply falls below the community's threshold of quality. You may see it by logging in. |