http://www.perlmonks.org?node_id=970879

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

Hi New here, I am after some help.

I cant for the life of me work out how to do it.

I need to sort the below:

$VAR1 = { '127' => { 'network' => '10.182.48.0/24', 'VLAN' => '3509' }, '32' => { 'network' => '10.182.12.0/25', 'VLAN' => '2121' }, '90' => { 'network' => '10.183.243.128/25', 'VLAN' => '3494' } }

I would it to be sorted by the Network. Is this possible, could someone please post the code to do this.

Thanks in Advance.

Nick

Replies are listed 'Best First'.
Re: Sorting Hash / Array
by cdarke (Prior) on May 16, 2012 at 17:51 UTC
    Not sure which part you are puzzling over. Simplistically:
    use strict; use warnings; my $VAR1; $VAR1 = { '127' => { 'network' => '10.182.48.0/24', 'VLAN' => '3509' }, '32' => { 'network' => '10.182.12.0/25', 'VLAN' => '2121' }, '90' => { 'network' => '10.183.243.128/25', 'VLAN' => '3494' } }; sub custom { # $a and $b are keys my $anet = $VAR1->{$a}{network}; my $bnet = $VAR1->{$b}{network}; return $anet cmp $bnet; } my @result = sort custom keys %$VAR1;
    However it would probably be better to use Net::IPAddress to convert the IP address to a 32-bit int, then use <=> to compare them, and compare the ports if the addresses are the same.

      I'd rather see that sort routine inlined in this case. As it stands, nasty subtle bugs can creep in with some sloppy maintenance. Say at some point someone has to add $VAR2 and perform similar sorting on it... and make the all too simple mistake of thinking they can add a line that reads:

      my @result2 = sort custom keys %$VAR2;
      Oops. And that's just one example.

      This is perfectly readable:

      my @result = sort { $VAR1->{$a}{network} cmp $VAR1->{$b}{network} } keys %$VAR1;

      If it was something you had to do repeatedly, it would be better to wrap the whole sort in a sub...

      sub keys_sorted_by_network { my $h = shift; sort { $h->{$a}{network} cmp $h->{$b}{network} } keys %$h; } my @result = keys_sorted_by_network($VAR1);
      -sauoq
      "My two cents aren't worth a dime.";
        Accepted, but...
        the string comparison is not accurate for IP addresses. Originally I was going to do a cascade comparison on each octet, but lost the will to live and suggested Net::IPAddress instead. Inline code can be messy, but I accept your argument about the hard-coded reference.
Re: Sorting Hash / Array
by kcott (Archbishop) on May 16, 2012 at 18:00 UTC

    Here's one way of doing it.

    #!/usr/bin/env perl use strict; use warnings; my $VAR1 = { '127' => { 'network' => '10.182.48.0/24', 'VLAN' => '3509' }, '32' => { 'network' => '10.182.12.0/25', 'VLAN' => '2121' }, '90' => { 'network' => '10.183.243.128/25', 'VLAN' => '3494' } }; my @sorted_keys = sort { $VAR1->{$a}{network} cmp $VAR1->{$b}{network} } keys %$VA +R1; print qq{Key\tNetwork\t\t\tVLAN\n}; for my $key (@sorted_keys) { print qq{$key\t}, $VAR1->{$key}{network}, qq{\t\t}, $VAR1->{$key}{VLAN}, qq{\n}; }

    Here's the output:

    $ pm_sort_hash_array.pl Key Network VLAN 32 10.182.12.0/25 2121 127 10.182.48.0/24 3509 90 10.183.243.128/25 3494

    -- Ken

      What happens if you add another network to the hashref with the address 10.19,72.0/25? That will sort lexically after the others even though in numerical network order it should come first!

      You need to convert the address (excluding the netmask) into something that will produce the right result when sorting lexically. The Socket module provides inet_aton() to achieve this.

      Cheers,

      JohnGG

Re: Sorting Hash / Array
by Cristoforo (Curate) on May 16, 2012 at 20:46 UTC
    If each of the quartets was 3 digits long, (010.182.012.000/25, for example), an alphanumeric sort, cmp, would be fine. The Socket module provides a function, inet_aton(), that transforms the quartet into a sortable entity.

    I added one more address to test the sort function, (with a key of 36 and value 10.182.2.0/25) Adding this record will show if the sorts are working correctly. It should sort before 10.182.12.0/25 if the sort is correct.

    #!/usr/bin/env perl use strict; use warnings; use 5.014; use Socket qw/ inet_aton /; my $data = { '127' => { 'network' => '10.182.48.0/24', 'VLAN' => '3509' }, '32' => { 'network' => '10.182.12.0/25', 'VLAN' => '2121' }, '36' => { 'network' => '10.182.2.0/25', 'VLAN' => '2222' }, '90' => { 'network' => '10.183.243.128/25', 'VLAN' => '3494' } }; my @sorted_keys = map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[ $_, inet_aton( $data->{$_}{network} =~ /^([.\ +d]+)/) ]} keys %$data; for my $key (@sorted_keys) { printf "%4d%20s%6s\n", $key, $data->{$key}{network}, $data->{$key} +{VLAN}; }

    The output is,

    C:\Old_Data\perlp>perl 970882.pl 36 10.182.2.0/25 2222 32 10.182.12.0/25 2121 127 10.182.48.0/24 3509 90 10.183.243.128/25 3494 C:\Old_Data\perlp>

    If I add the additional line to kcott's code, it gives the following output:

    C:\Old_Data\perlp>perl t.pl Key Network VLAN 32 10.182.12.0/25 2121 36 10.182.2.0/25 2222 127 10.182.48.0/24 3509 90 10.183.243.128/25 3494

    Hope this helps,

    Chris

    Update: I see that johngg beat me to the answer and his explanation of why the lexical sort won't sort correctly and why Socket should be used instaed.

      This is why I should have asked the experts at the beginning.

      I really appreciate all your help

      Thanks again

      Cheers

      Nick

      Hi Chris

      I Have a bit of an issue in that it doesn't seem to sort subnets correctly.

      10.182.8.0/26 10.182.8.0/21 10.182.8.64/26 10.182.8.128/26 10.182.8.192/26
      10.182.32.0/24 10.182.32.0/19 10.182.33.0/24

      I would expect 10.182.8.0/21 to be before 10.182.8.0/26

      Could you help?

      Thanks

      Nick

        Split the network and netmask into separate terms and sort on netmask within network.

        knoppix@Microknoppix:~$ perl -MSocket -Mstrict -wE ' my @networks = qw{ 10.182.8.0/26 10.182.8.0/21 10.182.8.64/26 10.182.8.128/26 10.182.8.192/26 }; say for map { $_->[ 0 ] } sort { $a->[ 1 ] cmp $b->[ 1 ] || $a->[ 2 ] <=> $b->[ 2 ] } map { my( $network, $netmask ) = split m{/}; [ $_, inet_aton( $network ), $netmask ]; } @networks;' 10.182.8.0/21 10.182.8.0/26 10.182.8.64/26 10.182.8.128/26 10.182.8.192/26 knoppix@Microknoppix:~$

        I hope this is helpful.

        Cheers,

        JohnGG

Re: Sorting Hash / Array
by salva (Canon) on May 21, 2012 at 14:32 UTC
    I have just uploaded to CPAN a new version of Sort::Key::IPv4 that can sort network addresses in the format xxx.xxx.xxx.xxx/xxx:
    use Sort::Key::IPv4 qw(netipv4keysort); my %hash = ( '127' => { 'network' => '10.182.48.0/24', 'VLAN' => '3509' }, ...); my @sorted_keys = netipv4keysort { $hash{$_}{network} } keys %hash;