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

I have a hash which is full of "objects", with the current key being the hostname of an object, and the IPv4 address being a value within the object. I need to sort the hash based on the IPv4 address, and I think I found all of the parts, but I can't seem to put them together. Currently I am just doing a common sort based on the IPv4 address, but this is just based on ASCII values of the characters, not on the 4x octets in an IPv4 address. I have the subroutine which can sort IPv4 addresses, I just do not know the syntax to use it in this situation. What am I missing?
foreach my $hostname (sort {$hash{$a}->ip_address() cmp $hash{$b}->ip_ +address() or $a cmp $b} keys %hash) {...} sub ip_sort { my @a = split /\./, $a; my @b = split /\./, $b; return $a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2] || $a[3] <=> $b[3]; }

Replies are listed 'Best First'.
Re: Sorting Hash Value Object's IPv4 Address
by johngg (Canon) on Jan 29, 2016 at 12:17 UTC

    You could use inet_aton() from Socket to transform the IP into a sortable string.

    use strict; use warnings; use 5.014; use Socket; my %hosts = ( host0 => { ip => q{17.198.26.3} }, host1 => { ip => q{113.34.87.9} }, host2 => { ip => q{23.6.8.136} }, host3 => { ip => q{10.45.18.207} }, host4 => { ip => q{192.168.31.8} }, host5 => { ip => q{10.17.227.3} }, host6 => { ip => q{192.168.1.6} }, host7 => { ip => q{172.16.1.1} }, host8 => { ip => q{3.77.45.22} }, host9 => { ip => q{17.216.1.1} }, ); say qq{$_ - $hosts{ $_ }->{ ip }} for map { $_->[ 0 ] } sort { $a->[ 1 ] cmp $b->[ 1 ] } map { [ $_, inet_aton($hosts{ $_ }->{ ip } ) ] } keys %hosts;

    The output.

    host8 - 3.77.45.22 host5 - 10.17.227.3 host3 - 10.45.18.207 host0 - 17.198.26.3 host9 - 17.216.1.1 host2 - 23.6.8.136 host1 - 113.34.87.9 host7 - 172.16.1.1 host6 - 192.168.1.6 host4 - 192.168.31.8

    I hope this is helpful.

    Cheers,

    JohnGG

      Here's a GRT variant on the ST approach above that will, for sufficiently large datasets, be faster because it relies on the default sort comparison:

      c:\@Work\Perl\monks>perl -wMstrict -le "use Socket; ;; my %hosts = ( host0 => { ip => q{17.198.26.3} }, host1 => { ip => q{113.34.87.9} }, host2 => { ip => q{23.6.8.136} }, host3 => { ip => q{10.45.18.207} }, host4 => { ip => q{192.168.31.8} }, host5 => { ip => q{10.17.227.3} }, host6 => { ip => q{192.168.1.6} }, host7 => { ip => q{172.16.1.1} }, host8 => { ip => q{3.77.45.22} }, host9 => { ip => q{17.216.1.1} }, ); ;; my $aton = 'a4'; ;; print qq{$_ - $hosts{$_}->{ip}} for map unpack(qq{x[$aton] a*}), sort map pack(qq{$aton a*}, inet_aton($hosts{$_}->{ip}), $_), keys %hosts ; " host8 - 3.77.45.22 host5 - 10.17.227.3 host3 - 10.45.18.207 host0 - 17.198.26.3 host9 - 17.216.1.1 host2 - 23.6.8.136 host1 - 113.34.87.9 host7 - 172.16.1.1 host6 - 192.168.1.6 host4 - 192.168.31.8


      Give a man a fish:  <%-{-{-{-<

      Good solution , and this is what I was too lazy to code, in my earlier post.

      However, shouldn't the sort use the spaceship operator (<=>) rather than cmp, since the items being compared are numeric ?

              "I can cast out either one of your demons, but not both of them." -- the XORcist

        That's what I initially assumed when I started coding this but you get a shed load of

        Argument "^CM-^V" isn't numeric in numeric comparison (<=>) at ...

        warnings when you use <=>. That's because inet_aton() is actually returning a packed string. Consider the following:-

        johngg@shiraz:~/perl/Monks > perl -MSocket -E ' > say ord for split m{}, inet_aton( q{192.168.45.92} );' 192 168 45 92 johngg@shiraz:~/perl/Monks > perl -E ' > say ord for split m{}, pack q{C4}, split m{\.}, q{192.168.45.92};' 192 168 45 92 johngg@shiraz:~/perl/Monks >

        Therefore the cmp comparison is more appropriate.

        Cheers,

        JohnGG

Re: Sorting Hash Value Object's IPv4 Address
by salva (Canon) on Jan 29, 2016 at 08:39 UTC
    Your ip_sort function is not well suited for what you want to do, as it is designed to be called directly from sort without any intermediate layer (i.e. sort ip_sort, @ips). But you can modify it easily to also support your case (look for $$ inside the documentation for the sort builtin to understand how it works):
    sub ip_cmp ($$) { my @a = split /\./, $_[0]; my @b = split /\./, $_[1]; return $a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2] || $a[3] <=> $b[3]; }
    And then, you will also be able to use it as follows:
    my @sorted_keys = sort { ip_cmp($hash{$a}->ip_address, $hash{$b}->ip_a +ddress) } keys %hash;

    Finally, you can also use the module Sort::Key::IPv4, specially if the hash is big and sorting speed matters:

    use Sort::Key::IPv4 qw(ipv4keysort); my @sorted_keys = ipv4keysort { $hash{$_}->ip_address } keys %hash;
Re: Sorting Hash Value Object's IPv4 Address
by NetWallah (Canon) on Jan 29, 2016 at 06:59 UTC
    According to the docs, Any of these should work:
    sort { ip_sort() } keys %hash sort ip_sort(keys %hash) sort(ip_sortkeys %hash) sort(ip_sort (keys %hash))
    Alshough the compare routine is not particularly efficient.

    You may want to consider converting the IP to binary, and/or using the schwartzian transform.

            "I can cast out either one of your demons, but not both of them." -- the XORcist