Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

How to sort IP addresses

by Anonymous Monk
on Jan 29, 2007 at 21:20 UTC ( [id://597189]=perlquestion: print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a list of IP addresses that I am trying to sort. For example,

123.456.789.12 12.345.67.19 12.345.67.18 12.345.66.20

I am trying to get a Perl script that will sort these in terms of each set of numbers from smallest to largest. I.e., the first set of numbers before the first period are used to sort. Then, if there are matches, the second set of numbers are compared and sorted. And so on. So, in the above case, the sort would result in:

12.345.66.20 12.345.67.18 12.345.67.19 123.456.789.12

I tried the following:

#!/usr/bin/perl open (FILE,"ipsvisiting.txt"); while (<FILE>) { $j[$i++] = $_; } close FILE; sub nummer { $a <=> $b; } @sorted = sort nummer @j; foreach $s (@sorted) { print $s; }

This "seems" to do some sorting, but it results in things like this:

12.345.111.3 12.345.111.26 12.345.111.24 12.345.111.5 12.345.111.4

In other words, it seems to stop working after it reaches the last set of numbers.

Any idea how to fix this?

Replies are listed 'Best First'.
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.

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}

      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.

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.
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.

    -Paul

      -Paul
      Thank you very much for your excellent sort ip code --- did the trick for me in a project I was working on

      -David

Re: How to sort IP addresses
by GrandFather (Saint) on Jan 29, 2007 at 22:34 UTC

    Normalise/demormalise:

    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
Re: How to sort IP addresses
by AltBlue (Chaplain) on Jan 29, 2007 at 23:07 UTC
    TIMTOWTDI ;-)
    $ 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

      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)

        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
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 :)

Re: How to sort IP addresses
by davorg (Chancellor) on Jan 30, 2007 at 09:40 UTC
Re: How to sort IP addresses
by Roy Johnson (Monsignor) on Jan 30, 2007 at 02:10 UTC
    I think sort::naturally would take care of you.

    Caution: Contents may have been coded under pressure.
Re: How to sort IP addresses
by Limbic~Region (Chancellor) on Jan 29, 2007 at 22:31 UTC
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

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
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' ];
      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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://597189]
Approved by Joost
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (4)
As of 2024-07-25 10:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.