Re: How to sort IP addresses
by johngg (Canon) on Jan 29, 2007 at 22:02 UTC
|
One way to do this is with a Schwartzian Transform. Reading the code from bottom to top, read the IPs into a map that chomps off the newline then constructs an anonymous array with the IP as first element and it's four parts as the next elements. Pass that anonymous array into the sort routine which does ascending numerical sort on the IP parts, passing the now sorted anonymous list into the next map. Finally, map the original IP address out in a quoting construct along with a newline and print.
use strict;
use warnings;
print
map { qq{$_->[0]\n} }
sort {
$a->[1] <=> $b->[1]
||
$a->[2] <=> $b->[2]
||
$a->[3] <=> $b->[3]
||
$a->[4] <=> $b->[4]
}
map { chomp; [ $_, split m{\.} ] }
<DATA>;
__END__
192.168.0.12
10.100.16.19
172.192.67.18
192.168.3.2
12.45.66.20
192.168.0.2
10.100.116.19
Here's the output
10.100.16.19
10.100.116.19
12.45.66.20
172.192.67.18
192.168.0.2
192.168.0.12
192.168.3.2
I hope this is of use. Cheers, JohnGG
Update: Fixed code indentation. | [reply] [d/l] [select] |
Re: How to sort IP addresses
by shmem (Chancellor) on Jan 29, 2007 at 22:15 UTC
|
IP addresses (v4, that is) are four bytes, separated by dots. A sequence like 123.456.789.12 is no valid IP address,
since 456 and 789 overflow a byte, which can be 0..255.
You can sort IP addresses easily converting them into numbers.
There are several ways to do that. You can e.g use the inet_aton routine form Socket:
use Socket;
my $ip = '127.0.0.1';
print unpack('N', inet_aton($ip)),"\n";
__END__
2130706433
An equivalent would be
my $ip = '127.0.0.1';
print unpack('N', pack 'C*', split /\./, $ip),"\n";
__END__
2130706433
Now, for sorting them, you don't want to split / pack / unpack them for every comparison of
two items during the sort. So make an list of anonymous arrays holding each [ $numeric, $ip ],
sort them via the first element and pull out the second from the sorted list:
use Socket;
chomp(my @ips = <DATA>);
my @sorted =
map { $_->[1] } # pull out second element
sort {$a->[0] <=> $b->[0]} # sort list of arrays by first
+element
map {
[ # construct an anonymous array
+with a
unpack('N',inet_aton($_)), # transformed IP address
$_ # and IP address
]
} @ips; # your input list
print "$_\n" for @sorted;
__DATA__
192.168.10.15
10.24.13.88
172.16.254.13
10.24.13.89
89.67.128.254
Result:
10.24.13.88
10.24.13.89
89.67.128.254
172.16.254.13
192.168.10.15
See Socket, map, sort.
Sorting IPs might well be a FAQ...
<update>
Wrapped inet_aton in unpack, s/chop/chomp/. Thanks, ikegami.
</update>
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] [d/l] [select] |
|
|
I have two bug fixes and a speed enhancement for you.
inet_aton doesn't return a number, yet you sort numerically.
sort { $a->[0] <=> $b->[0] }
should be
sort { $a->[0] cmp $b->[0] }
chop could drop the last char of the data in the file. (e.g. The file could be "10.0.0.1\n10.0.0.2\n10.0.0.5".) Use chomp instead of chop.
It's much faster to avoid creating numerous anonymous arrays and using the default lexical sort, and it's trivial to do here since inet_aton returns a fixed length string. See my post for details.
| [reply] [d/l] [select] |
Re: How to sort IP addresses
by ikegami (Patriarch) on Jan 29, 2007 at 22:27 UTC
|
inet_aton returns the address as a 4 byte string. Each byte of the string represents one of the numbers of the IP address. In this format, they can be sorted lexically.
use Socket qw( inet_aton );
use List::MoreUtils qw( apply );
my $file_name = "ipsvisiting.txt";
open (my $fh, '<', $file_name)
or die("Unable to open file \"$file_name\": $!\n");
my @sorted = map { substr($_, 4) }
sort
map { inet_aton($_) . $_ }
apply { chomp }
<$fh>;
print "$_\n"
foreach @sorted;
I started writing this when there were no replies, but I got sidetracked. Hopefully, this approach hasn't been suggested already.
I made some change to your code:
- I use the safer 3-arg open.
- I check the result of open.
- I used lexicals where possible.
| [reply] [d/l] [select] |
Re: How to sort IP addresses
by jettero (Monsignor) on Jan 29, 2007 at 21:27 UTC
|
The secret is to use a sort function. Er, my mistake, you're using one already ... you just need to do a few more comparisons. Perl doesn't have a built in octet comparator for some reason.
I use the following little guy a lot, so I just cut and pasted it here. It may help. I call it my ipsort_pipet.
| [reply] [d/l] |
|
|
| [reply] |
Re: How to sort IP addresses
by McDarren (Abbot) on Jan 30, 2007 at 04:44 UTC
|
Just for the record, I've been using the following little utility sub for handling IP addresses for a few years now.
sub ipto32bit {
#
# Convert a dotted quad c.d.e.f to a single unsigned 32bit number.
#
my ($ip, $c, $d, $e, $f);
$ip = shift;
($c,$d,$e,$f) = split(/\./,$ip);
return ($c << 24) + ($d << 16) + ($e << 8) + $f;
}
Then it's just a matter of doing a straight lexical numerical sort. An example of how this might be used:
#!/usr/bin/perl -wl
use strict;
chomp(my @ips = <DATA>);
my @sorted_ips = sort by_ip(@ips);
print for (@sorted_ips);
sub by_ip {
return ipto32bit($a) <=> ipto32bit($b);
}
sub ipto32bit {
#
# Convert a dotted quad c.d.e.f to a single unsigned 32bit number.
#
my ($ip, $c, $d, $e, $f);
$ip = shift;
($c,$d,$e,$f) = split(/\./,$ip);
return ($c << 24) + ($d << 16) + ($e << 8) + $f;
}
__DATA__
12.345.111.3
12.345.111.26
61.8.47.111
12.345.111.24
203.134.35.27
12.345.111.5
12.345.111.4
Which gives:
12.345.111.3
12.345.111.4
12.345.111.5
12.345.111.24
12.345.111.26
61.8.47.111
203.134.35.27
Note that I started using the above before I learned about the inet_aton goodness that was pointed out by shmem above. So if I was looking for a solution now, shmem's is probably the one I would choose.
Cheers,
Darren :)
| [reply] [d/l] [select] |
Re: How to sort IP addresses
by GrandFather (Saint) on Jan 29, 2007 at 22:34 UTC
|
use strict;
use warnings;
my @ips = qw(123.156.89.12 12.245.67.1 12.45.67.180 12.145.66.20);
@ips = map {sprintf "%d.%d.%d.%d", split /\./} sort
map {sprintf "%03d.%03d.%03d.%03d", split /\./} @ips;
print join "\n", @ips;
Prints:
12.45.67.180
12.145.66.20
12.245.67.1
123.156.89.12
DWIM is Perl's answer to Gödel
| [reply] [d/l] [select] |
Re: How to sort IP addresses
by AltBlue (Chaplain) on Jan 29, 2007 at 23:07 UTC
|
$ perl -le 'map { printf "%vd\n", $_ } sort map { eval "v$_" } @ARGV'
+\
1.2.1.1 1.1.1.1 1.11.1.1 1.1.1.66 1.1.1.9
1.1.1.1
1.1.1.9
1.1.1.66
1.2.1.1
1.11.1.1
| [reply] [d/l] |
|
|
That's cool! Admittedly, you might be restricted as to perl versions, since it relies on vstring functionality. It occurred to me that there might be a golf like solution involving some kind of GRT. It's probably quite fast as well.
I'll even name it for you: the Gutta Percha Transform :).
--
Oh Lord, won’t you burn me a Knoppix CD ?
My friends all rate Windows, I must disagree.
Your powers of persuasion will set them all free,
So oh Lord, won’t you burn me a Knoppix CD ? (Missquoting Janis Joplin)
| [reply] |
|
|
Hm, golfing with GRT? No idea how, but anyway, here's a possible starting point:
$ perl -le '$,=$/; print @ARGV[ map { unpack N, substr $_, -4 } \
sort map { eval("v$ARGV[$_]") . pack N, $_ } 0 .. $#ARGV ]' \
1.2.1.1 1.1.1.1 1.11.1.1 1.1.1.66 1.1.1.9
1.1.1.1
1.1.1.9
1.1.1.66
1.2.1.1
1.11.1.1
OTOH, that previous version could easily be golfed (ok, just a little) by dropping the initial map
(which is useful only when using this snippet "for real" - you know, using sprintf and returning a list):
$ perl -e 'printf "%vd\n", $_ for sort map { eval "v$_" } @ARGV' \
1.2.1.1 1.1.1.1 1.11.1.1 1.1.1.66 1.1.1.9
1.1.1.1
1.1.1.9
1.1.1.66
1.2.1.1
1.11.1.1
| [reply] [d/l] [select] |
Re: How to sort IP addresses
by davorg (Chancellor) on Jan 30, 2007 at 09:40 UTC
|
@out = sort {
pack('C4' => $a =~
/(\d+)\.(\d+)\.(\d+)\.(\d+)/)
cmp
pack('C4' => $b =~
/(\d+)\.(\d+)\.(\d+)\.(\d+)/)
} @in;
| [reply] [d/l] |
Re: How to sort IP addresses
by Roy Johnson (Monsignor) on Jan 30, 2007 at 02:10 UTC
|
| [reply] |
Re: How to sort IP addresses
by Limbic~Region (Chancellor) on Jan 29, 2007 at 22:31 UTC
|
| [reply] |
Re: How to sort IP addresses
by smahesh (Pilgrim) on Jan 30, 2007 at 13:19 UTC
|
Hi,
We had to do something similar on one of my earlier projects. This was not in Java not Perl. IIRC, the developer basically converted the IPv4 address from the dotted notation(4 octets) to a number (32 bit number - each octet contributing a byte) and just sorted the numbers and converted them back to IPv4 dotted notation.
Maybe you can try the same approach.
Hint: Refer to pack/unpack for the conversion from dotted to number and back.
Regards,
Mahesh
| [reply] |
Re: How to sort IP addresses
by marnix (Initiate) on Aug 10, 2019 at 01:38 UTC
|
I was actually here to look for some IPV6 sorting code, but reading various IPV4 sorting solutions made me want to add mine from 10 years ago (when I retired). I've forgotten much, but I remember back then being proud of my fairly simple and fast sort of IP addresses. I may have been deluding myself. I hope not, but I'm prepared if so.
#!/usr/bin/env perl
chomp(@IPs = <DATA>);
@Sorted = map { substr($_, 4) }
sort map { pack('C*', split(/\./, $_) ) . $_ } @IPs;
print join("\n", @Sorted) . "\n";
__DATA__
12.345.111.3
12.345.111.26
61.8.47.111
12.345.111.24
203.134.35.27
12.345.111.5
12.345.111.4
| [reply] [d/l] |
Re: How to sort IP addresses
by chakram88 (Pilgrim) on Jan 29, 2007 at 22:23 UTC
|
Have you tried the cmp operator to compare them as strings?
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my @ips = qw/
123.12.255.12
12.255.67.19
255.78.132.255
12.255.67.18
12.245.66.20
123.33.65.89
123.32.65.78
255.78.130.123
123.33.65.78
/;
my @sorted_ips = sort {$a cmp $b} @ips;
print Dumper \@sorted_ips;
----------
$VAR1 = [
'12.245.66.20',
'12.255.67.18',
'12.255.67.19',
'123.12.255.12',
'123.32.65.78',
'123.33.65.78',
'123.33.65.89',
'255.78.130.123',
'255.78.132.255'
];
| [reply] [d/l] [select] |
|
|
use strict;
use warnings;
my @ips = qw(123.156.89.12 12.245.67.1 12.45.67.180 12.145.66.20);
@ips = sort {$a cmp $b} @ips;
print join "\n", @ips;
Prints:
12.145.66.20
12.245.67.1
12.45.67.180
123.156.89.12
is probably not what OP was after.
DWIM is Perl's answer to Gödel
| [reply] [d/l] [select] |