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

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

Hi,

I am tring to sort the hash with values below is the code:

sub mySort { $a =~ /(\d+)/; my $firstVal = $1; $b =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } my %IP_Store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); foreach my $Value (sort mySort (values (%IP_Store))) { print "$Value\n"; print "System_ip = '$IP_Store{$Value}' \n"; #print "PDN-IP = '$Values' \n"; }
when i run this script getting these warnings. and in "system_ip" values are also not coming.
UEH1_system_ip Use of uninitialized value within %IP_Store in concatenation (.) or st +ring at C:\Users\Desktop\IP_test\test123.pl line 30. System_ip = '' UEH11_system_ip System_ip = '' UEH25_system_ip System_ip = '' UEH111_system_ip System_ip = '' Use of uninitialized value within %IP_Store in concatenation (.) or st +ring at C:\Users\Desktop\IP_test\test123.pl line 30.
Thanks in advance

Replies are listed 'Best First'.
Re: Sort hash with values
by Preceptor (Deacon) on Jun 23, 2013 at 15:19 UTC

    You have your keys and values backwards in your hash. Your code prints (sorted) list of values. And then you try and access the hash using that value, which simply isn't going to work.

    Try instead:

    use strict; use warnings; my %IP_Store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); sub mySort { $IP_Store{$a} =~ /(\d+)/; my $firstVal = $1; $IP_Store{$b} =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } foreach my $Value (sort mySort (keys (%IP_Store))) { print "$IP_Store{$Value}\n"; print "System_ip = '$Value' \n"; #print "PDN-IP = '$Values' \n"; }

    The problem you've got is you grab the values out of the hash, and sort them into order. But at that point, you no longer have they key that references that value.

    An alternative would be to swap around the keys and values in your hash, e.g.:

    use strict; use warnings; my %IP_Store = ( "UEH1_system_ip" => "11.0.0.1" , "UEH11_system_ip" => "11.0.0.11", "UEH25_system_ip" => "11.0.0.3", "UEH111_system_ip" => "11.0.0.25", ); sub mySort { $a =~ /(\d+)/; my $firstVal = $1; $b =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } foreach my $Value (sort mySort (keys (%IP_Store))) { print "$Value\n"; print "System_ip = '$IP_Store{$Value}' \n"; #print "PDN-IP = '$Values' \n"; }

    This gives the same output each time, which is:

    UEH1_system_ip System_ip = '11.0.0.1' UEH11_system_ip System_ip = '11.0.0.11' UEH25_system_ip System_ip = '11.0.0.3' UEH111_system_ip System_ip = '11.0.0.25'
Re: Sort hash with values
by farang (Chaplain) on Jun 23, 2013 at 15:11 UTC

    Hash lookups typically involve searching keys for values, so your logic is backwards. Rearranging the hash order and changing values to keys gives something closer to what you probably want.

    sub mySort { $a =~ /(\d+)/; my $firstVal = $1; $b =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } my %IP_Store = ( "UEH1_system_ip" => "11.0.0.1", "UEH11_system_ip" => "11.0.0.11", "UEH25_system_ip" => "11.0.0.3", "UEH111_system_ip" => "11.0.0.25" ); foreach my $key (sort mySort (keys (%IP_Store))) { print "$key\n"; print "System_ip = '$IP_Store{$key}' \n"; }
    Update: The mySort routine looks like it's not doing it's job properly or efficiently. If you explain how you want the output sorted, I'm sure someone will have good advice.

      Hi farang,
      The mySort routine looks like it's not doing it's job properly or efficiently..
      Though, I don't know how the OP wants his/her data sorted, but really there is no need for the mySort subroutine. Because, all that that subroutine is doing can be done in a sort block like so:

      use strict; use warnings; my %IP_Store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); foreach my $Value ( sort { $IP_Store{$a} =~ /(\d+)/; my $firstVal = $1; $IP_Store{$b} =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } keys %IP_Store ) { print "$IP_Store{$Value}\n"; print "System_ip = '$Value' \n"; }
      Which will produce the same result....

      If you tell me, I'll forget.
      If you show me, I'll remember.
      if you involve me, I'll understand.
      --- Author unknown to me
        Hi,

        I don't know how the OP wants his/her data sorted, but really there is no need for the mySort subroutine. Because, all that that subroutine is doing can be done in a sort block...

        Yes, you are right, but I guess that Farang wanted to offer a fix while changing as little as possible from the original code. It is often a dilemma for me (here and on other forums) when I see a clear coding error as well as a number of more or less clumsy or suboptimal things around: should we just give the bug fix to make the program work, toi solve the OP's immediate problem, or should we try to refactor the whole shebang. Sometimes, when I have time, I take the trouble of saying something like this: "your error is there, on this line of code, this is what you need to fix; but, BTW, I would think that it would also be better to (follow the following best practices | use a hash instead of an array (or an array instead of a hash) | work the algorithm the other way around | use the following (cleaner|more robust|faster) syntax | use strict and use warnings | use Perlish loops rather than C-style loops | use tr/// instead of s///g | use index instead of a regex | use map and grep instead of foreach (or foreach instead of map and grep) | whatever | etc.). But I don't always have time and I don't always do it.

Re: Sort hash with values
by 2teez (Vicar) on Jun 23, 2013 at 15:59 UTC

    Hi Rahul Gupta,

    Really, I don't know how you wanted your hash sorted but I guess you would like to sort using "keys" since, your values from your OP are "kind" of sorted, following the numbers on the names like UEH1_system_ip, UEH11_system_ip,.. etc.
    The following shows one way of doing it:

    use warnings; use strict; my %ip_store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); my @ip_data = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { /\.([0-9]+)$/; [ $_, $1 ] } keys %ip_store; print $ip_store{$_}, $/, $_, $/ for @ip_data;
    ...produces ...
    UEH1_system_ip 11.0.0.1 UEH25_system_ip 11.0.0.3 UEH11_system_ip 11.0.0.11 UEH111_system_ip 11.0.0.25

    If you tell me, I'll forget.
    If you show me, I'll remember.
    if you involve me, I'll understand.
    --- Author unknown to me
Re: Sort hash with values
by AnomalousMonk (Archbishop) on Jun 23, 2013 at 20:03 UTC

    I, too, do not understand very well what Rahul Gupta ultimately wants to achieve, but insofar as a numeric-ascending sort of the values of a hash according to a sub-string of decimal digits within each value is concerned, a number of useful answers have been given.

    A couple of intersting approaches have been touched on briefly or not at all: our old, work-horse friend the Schwartzian Transform (ST) exemplified by 2teez, and our exciting new friend the Guttman-Rosler Transform (GRT). Both of these techniques are thoroughly discussed in A Fresh Look at Efficient Perl Sorting. The advantage of GRT is that it uses no explicit sort-block at all, but depends on the default, hence fastest, lexicographic-ascending sort ordering of sort. (Update: Well, I'm assuming the implicit sort code has been optimized up the wazoo, so nothing you could do in an explicit block, with its inherent call overhead, could be faster.)

    In the following examples, the
        map { dd $_;  $_; }
    and
        map { print qq{=$_=};  $_; }
    expressions are intended to show what is being fed to the sort built-in. Do not include them in production code.

    >perl -wMstrict -le "use Data::Dump; ;; my %IP_Store = qw( 11.0.0.1 UEH1_system_ip 11.0.0.11 UEH11_system_ip 11.0.0.3 UEH25_system_ip 11.0.0.25 UEH111_system_ip ); ;; ;; print 'Schwartzian Transform'; my @sorted1 = map $_->[0], sort { $a->[1] <=> $b->[1] } map { dd $_; $_; } map [ $_, /\d+/g ], values %IP_Store ; print qq{'$_'} for @sorted1; ;; ;; print 'Guttman-Rosler Transform'; my $n = 10; my $fmt = qq{%0${n}d}; my $unp = qq{x$n a*}; ;; my @sorted2 = map unpack($unp, $_), sort map { print qq{=$_=}; $_; } map sprintf($fmt, /\d+/g) . $_, values %IP_Store ; print qq{'$_'} for @sorted2; " Schwartzian Transform ["UEH25_system_ip", 25] ["UEH1_system_ip", 1] ["UEH11_system_ip", 11] ["UEH111_system_ip", 111] 'UEH1_system_ip' 'UEH11_system_ip' 'UEH25_system_ip' 'UEH111_system_ip' Guttman-Rosler Transform =0000000025UEH25_system_ip= =0000000001UEH1_system_ip= =0000000011UEH11_system_ip= =0000000111UEH111_system_ip= 'UEH1_system_ip' 'UEH11_system_ip' 'UEH25_system_ip' 'UEH111_system_ip'
Re: Sort hash with values
by Cristoforo (Curate) on Jun 24, 2013 at 02:34 UTC
    An approach using Socket's inet_aton and inet_ntoa to sort the ips.
    #!/usr/bin/perl use strict; use warnings; use Socket qw /inet_aton inet_ntoa/; my %ip_store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); my @sorted = map {inet_ntoa $_} sort map {inet_aton $_} keys %ip_store +; foreach my $ip (@sorted) { print "$ip\n"; print "System_ip = '$ip_store{$ip}' \n"; }
    Output:
    C:\Old_Data\perlp>perl t9.pl 11.0.0.1 System_ip = 'UEH1_system_ip' 11.0.0.3 System_ip = 'UEH25_system_ip' 11.0.0.11 System_ip = 'UEH11_system_ip' 11.0.0.25 System_ip = 'UEH111_system_ip'
    Chris
      Hi Rahul,

      May be this will solve your problem.

      sub mySort { $a =~ /(\d+)/; my $firstVal = $1; $b =~ /(\d+)/; my $secVal = $1; $firstVal <=> $secVal; } my %IP_Store = ( "11.0.0.1" => "UEH1_system_ip", "11.0.0.11" => "UEH11_system_ip", "11.0.0.3" => "UEH25_system_ip", "11.0.0.25" => "UEH111_system_ip" ); %new_hash = reverse(%IP_Store); foreach my $key (sort mySort (keys(%new_hash))) { chomp($key); print "$key\n"; print "System_ip = $new_hash{$key}\n"; #print "PDN-IP = '$Values' \n"; }
      The only change i have done is reversing the hash.

      Output:

      -------

      UEH1_system_ip

      System_ip = 11.0.0.1

      UEH11_system_ip

      System_ip = 11.0.0.11

      UEH25_system_ip

      System_ip = 11.0.0.3

      UEH111_system_ip

      System_ip = 11.0.0.25

        The only change i have done is reversing the hash.

        A naive hash reversal works just fine if the hash values are unique to begin with. If they're not, somebody's gonna get (silently) clobbered. Maybe add a pre/post reversal size check:

        >perl -wMstrict -le "my %hash = qw(a aye b bee c see eh aye); ;; my $pre_k = keys %hash; %hash = reverse %hash; $pre_k == keys %hash or die 'somebody got clobbered'; " somebody got clobbered at -e line 1.
Re: Sort hash with values
by sundialsvc4 (Abbot) on Jun 24, 2013 at 12:52 UTC

    I think that I see the problem.   Ordinarily, you would be able to iterate over something like foreach ( sort keys(%IP_store) ) ..., but IP-address strings of varying lengths won’t sort correctly as strings.

    The sort verb does allow for a subroutine to be specified, which must return a value that is less than, equal or greater than zero as appropriate, so we do have the mechanism by which to sort the strings the way we want:   as a left-to-right hierarchical collection of four integers.   So, let’s define a separate sub to do this ... something along these (untested) lines:

    # 'sort' COMPARISON FUNCTION FOR IP-ADDRESSES. sub ip_compare { # GRAB THE TWO ARGUMENTS PASSED BY 'SORT' my ($a, $b) = @_; # SPLIT THE IP-ADDRESSES INTO FOUR =STRINGS= BY # THE PERIOD CHARACTER. my ($a1, $a2, $a3, $a4) = split('\.', @$a); my ($b1, $b2, $b3, $b4) = split('\.', @$b); # TYPECAST EACH COMPONENT AS INTEGERS, THEN # USE INTEGER '<=>' OPERATOR TO COMPARE NUMBERS. # USE A STACK OF SHORT-CIRCUIT '||' OPS WHICH WILL # STOP AT (AND RETURN) THE FIRST NON-ZERO RESULT # IN THE CHAIN. return (int($a1) <=> int($b1)) || (int($a2) <=> int($b2)) || (int($a3) <=> int($b3)) || (int($a4) <=> int($b4)); }

    And I would put that subroutine (when debugged), with its comments, into my program.   There are many ways to write it:   this one is reasonably clear.

    Now we can do something like sort (ip_compare, keys(%ip_addr)), or something like that, and it should work.   You iterate over the returned list of keys and retrieve the values from the hash by key.

      # SPLIT THE IP-ADDRESSES INTO FOUR =STRINGS= BY # THE PERIOD CHARACTER. my ($a1, $a2, $a3, $a4) = split('.', @$a);

      The split built-in function does not use single-quotes around a  /PATTERN/ argument to 'meta-quote' the argument: the  '.' in the above really is trying to split on "any character except newline". Use  '\.' to split on a literal period.

      >perl -wMstrict -le "my $s = 'a.bb.ccc.d'; ;; my @ra = split '.', $s; print 'naked dot'; printf qq{ '$_'} for @ra; print '@ra elements: ', scalar @ra; ;; @ra = split '\.', $s; print 'escaped dot'; printf qq{ '$_'} for @ra; " naked dot @ra elements: 0 escaped dot 'a' 'bb' 'ccc' 'd'
      A reply falls below the community's threshold of quality. You may see it by logging in.