6

Say for example the numbers are in format :

1.1.10
1.1.10.1
1.1.10.2
1.1.11
1.1.12
1.1.13
1.1.13.1
1.1.3
1.1.4

And the wat I am looking for output is :

1.1.3
1.1.4
1.1.10
1.1.10.1
1.1.10.2
1.1.11
1.1.12
1.1.13
1.1.13.1
iDev
  • 2,163
  • 10
  • 39
  • 64
  • Duplicates: http://stackoverflow.com/questions/6917314/how-can-i-sort-a-list-of-ip-addresses-in-perl – Andy Finkenstadt Jul 20 '12 at 19:00
  • Not a duplicate of 6917314. iDev's strings don't have exactly 4 parts, and he didn't say anything about them being limited to 255. – ikegami Jul 20 '12 at 19:02
  • 1
    Quick hack: `sort { eval $a cmp eval $b } @numbers` – TLP Jul 20 '12 at 19:06
  • These look remarkably like version numbers and thus [Sort::Versions](http://search.cpan.org/~edavis/Sort-Versions-1.5/Versions.pm) might be in consideration. –  Jul 20 '12 at 19:37
  • @TLP, that was just awesome! Can you explain how it works? Doing `print eval('1.1.1.10')` prints out some garbage value. What is the basis of comparison here? – SexyBeast Jul 20 '12 at 21:01
  • 1
    @Cupidvogel, `1.1.1.10` is the same as `v1.1.1.10`, which stringifies to `chr(1).chr(1).chr(1).chr(10)`. In other words, `eval "1.1.1.10"` is an expensive and risky way to do `pack "W*", split /\./, "1.1.1.10"`. Yes, you can change the "`C`" in the pack templates in my solutions to "`W`". Does numbers up to 4 billion on 32-bit Perls, and 2**64-1 on 64-bit machines. – ikegami Jul 20 '12 at 21:10
  • 1
    @Cupidvogel http://stackoverflow.com/a/7142844/725418 In the comments there, tchrist elaborates on this particular "trick". Basically it does what ikegami said, it turns the quasi-numerical string into a proper string, after which we can "safely" use it with the `cmp` operator. I named it a quick hack, because it is probably not a good way to solve this particular problem. – TLP Jul 23 '12 at 20:58

3 Answers3

11
use Sort::Key::Natural qw( natsort );
my @sorted = natsort @data;

or (no modules)

my @sorted =
   map $_->[0],
   sort { $a->[1] cmp $b->[1] }
   map [ $_, pack('C*', split /\./) ],
   @data;

or (no modules, faster, but requires an array rather than a list for input)

 my @sorted =
   map $data[unpack('N', $_)],
   sort
   map pack('NC*', $_, split /\./, $data[$_]),
   0..$#data;

In the pack templates, you can change C to n or N. C allows numbers up to 255. n allows numbers up to 65,535. N allows numbers up to 4 billion.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • cant figure out how the sort works when it gets the pack(...) data,pack in this case just creates a single octet value out of each version number... isnt comparing each number with 'cmp' will do the same? – snoofkin Jul 21 '12 at 12:46
  • @soulSurfer2010, `"1.2.1" cmp "1.16.1"` fails because the numbers have a variable width. Using `pack C`, makes them all the same width. It boils down to `"\x01\x02\x01" cmp "\x01\x10\x01"` which sorts correctly because of the padding added. – ikegami Jul 21 '12 at 17:35
4

Try the following:

use Modern::Perl;
use Sort::Naturally  qw{nsort};

my @numbers = nsort(qw{1.1.10 1.1.10.1 1.1.10.2 1.1.11 1.1.12 1.1.13 1.1.13.1 1.1.3});
say for @numbers;

Output:

1.1.3
1.1.10
1.1.10.1
1.1.10.2
1.1.11
1.1.12
1.1.13
1.1.13.1

Hope this helps!

Kenosis
  • 6,196
  • 1
  • 16
  • 16
0

This may be easier for you to comprehend:

sub min { $_[0] <= $_[1] ? $_[0] : $_[1] }
sub comp 
  {
  ($a1,$a2) = @_;
  @a1 = @{$a1};
  @a2 = @{$a2};
  for (0..min(scalar @a1,scalar @a2)) 
     {
     return 1 if $a1[$_] > $a2[$_];
     return -1 if $a2[$_] > $a1[$_];  #exit early if an inequality is found
     }
  return 0 if @a1 == @a2; #both array are equal, thus both numbers are equal
  return @a1 > @a2 ? 1 : -1; #arrays are equal upto a certain point, thus longer array is 'greater'
  }
@sourcearray = ('1.1.10','1.1.10.1','1.1.10.2','1.1.11','1.1.12','1.1.13','1.1.13.1','1.1.3','1.1.4');
@individual = map { [split /\./] } @sourcearray; #splits each number into arrays consisting of individual numbers
@sorted = sort {comp($a,$b)} @individual; #sort algo decides which of these two arrays are 'greater'
{
local $" = ".";
print "@{$sorted[$_]}\n" for 0..$#sorted;
}
SexyBeast
  • 7,913
  • 28
  • 108
  • 196