http://qs321.pair.com?node_id=895046

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

Hi Monks,

One Quick Question. I sorted a hash by value. If i print the sorted hash in the loop, it is displayed as sorted.

If i print the same hash, out of the loop, results are not sorted. Hence, i want to save the results of the sorted hash to another hash table, so that i could print the correct sorted hash even, out of the loop

Please tell me, what additional steps are to be added for the given code

foreach $key( sort {$ipaddr {$a} cmp $ipaddr {$b}} keys %ipaddr ) { print OUT "$ipaddr{$key}\t$keyn"; }

Replies are listed 'Best First'.
Re: Save the resultant of " hash sorted by values "
by toolic (Bishop) on Mar 23, 2011 at 17:19 UTC
Re: Save the resultant of " hash sorted by values "
by CountZero (Bishop) on Mar 23, 2011 at 17:28 UTC
    It is a property of a hash that it knows no inherent "order", at least not one we, puny humans, would recognize as such.

    One can sort a hash according to its keys or values or whatever rule one likes, but this is entirely external to the hash and making a new hash on basis of this sorted version of the original hash, will immediately loose that sort order.

    If you need to keep your data in a certain order, either use an array (or an Array of Hashes, or similar structure) or use Tie::IxHash which implements Perl hashes that preserve the order in which the hash elements were added. Note that once an element is added to this type of hash, its place is fixed. Changing its key or value will NOT re-sort the hash! It would therefore be very wrong and misleading to call this some form of "sorted" hash. It is an hash with a fixed and predictable order of key retrieval only.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: Save the resultant of " hash sorted by values "
by johngg (Canon) on Mar 23, 2011 at 17:20 UTC

    Hashes are inherently unsorted so save your sorted keys in an array and reuse it whenever you want to print the hash in sorted order.

    my @sortedKeys = sort {$ipaddr {$a} cmp $ipaddr {$b}} keys %ipaddr; ... print OUT "$ipaddr{$_}\t$_\n" for @sortedKeys;

    Update: Corrected error in print statememnt, s/$key/$_/g, pointed out by wind, thanks.

    Cheers,

    JohnGG

Re: Save the resultant of " hash sorted by values "
by citromatik (Curate) on Mar 23, 2011 at 17:21 UTC

    When you sort a hash to output, you are really sorting temporarily the keys or the values, not the underlying hash.

    To get a sorted hash try for example Tie::Hash::Sorted

    citromatik

Re: Save the resultant of " hash sorted by values "
by sundialsvc4 (Abbot) on Mar 23, 2011 at 19:36 UTC

    This reminds me of a situation that I encountered a few years ago while doing some work for an online bookstore.   They wanted to extract a sorted list of book-IDs and do something with them.   But the “tied hash” was actually a Berkeley DB.   (They loved Berkeley DB’s, for some reason.)   Trouble was, there were literally millions of records in that file.   Logic which began with sort keys promptly fell-over and died, because the computer was attempting to build an in-memory list of all those records.

    I pointed out that, in the specific case of a file of this kind, the each() function will reliably return the keys in sorted order, because it is simply walking through the B-tree structure.   Thus, the entire approach of “fetching all the keys, sorting them in memory, and querying for records using these keys” could be eliminated in favor of a while loop.   The memory-load evaporated and hours of processing time were saved.

    Of course, this only works for the (one) key under which the records are stored, in such a file.   And, generally speaking, it only works because the “hash” in this particular case was not, in fact, a true hash at all.   The each() function does not exhibit this behavior generally.   This was a special case, and an uncommonly-useful optimization that could be brought to bear in that special case.

    (Today, given a similar situation, I would recommend using SQLite database files, although that technology did not exist at that time.)

Re: Save the resultant of " hash sorted by values "
by Anonymous Monk on Mar 23, 2011 at 17:18 UTC
    Hashes are by design unsorted. Probably the best thing to do is whenever you need do have something sorted (like output), do your for each again.
Re: Save the resultant of " hash sorted by values "
by wind (Priest) on Mar 23, 2011 at 18:09 UTC

    As johngg and all others pointed out, %hashes are inherently unsorted so you simply must save an array of keys sorted however you like.

    However, your IPs might not be sorted how you want. By using string compare cmp, you're going to end up with the ip 1.0.0.100 coming before 1.0.0.2. Is that what you want? If it isn't, then you simply need to do a Schwartzian Transform

    use strict; my %ipaddr = ( a => '192.168.0.1', b => '192.168.0.3', c => '192.168.0.200', d => '192.168.2.1', e => '192.168.100.1', f => '192.168.1.1', ); # The old method: # my @sortedKeys = sort {$ipaddr{$a} cmp $ipaddr{$b}} keys %ipaddr; my @sortedKeys = map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_, join '.', map {sprintf "%03d", $_} split '\.', $ipaddr{$ +_}]} keys %ipaddr; print "$ipaddr{$_}\t$_\n" for @sortedKeys;
    Output
    192.168.0.1 a 192.168.0.3 b 192.168.0.200 c 192.168.1.1 f 192.168.2.1 d 192.168.100.1 e
    Versus the old method of
    192.168.0.1 a 192.168.0.200 c 192.168.0.3 b 192.168.1.1 f 192.168.100.1 e 192.168.2.1 d
    - Miller

      Is that what you want? If it isn't, then you simply need to do a Schwartzian Transform

      Using the Schwartzian Transform doesn't help at all. It's the generation of a comparable key that made the difference.

      sub ip_to_key { sprintf "%03d", split /\./, $_[0] } my @sortedKeys = sort { ip_to_key($ipaddr{$a}) cmp ip_to_key($ipaddr{$b}) } keys %ipaddr;

      The ST is an optimisation to get more speed out of it. If you really needed speed, this is much faster:

      my @sortedKeys = map substr($_, 4), sort map pack('C4', split(/\./, $ipaddr{$_})) . $_, keys %ipaddr;

        I feel your nitpicking, but fair enough. :) Yes, the point is that he's not sorting on the correct values (most likely) and he can certainly use a function to translate the keys just as well as using a ST. I personally would prefer to keep the code in one place given this is just an example though.

        Also, your function doesn't actually work as is. Fixed:
        sub ip_to_key { join '', map {sprintf "%03d", $_} split /\./, $_[0] }

        Finally, thanks for sharing the pack method of doing a ST as it is definitely snazzier. I use it sometimes as well, but don't feel that it's as immediately decypherable by coders learning about sorts. Although, certainly the easiest method would probably be the functional translation sort {func($a) cmp func($b)}.

Re: Save the resultant of " hash sorted by values "
by Anonymous Monk on Mar 23, 2011 at 17:53 UTC
    The whole idea of a hash structure is that you can find any key, randomly, very fast ... but the trade-off that you always pay, is that the keys occur in no particular order, and that the perceived order of keys can change at any time. You can, however, "sort keys" and capture the result in another array... just be aware that you are gobbling up memory in doing so, and if the hash is in fact a tied-hash (i.e. not a memory data structure at all) your approach would basically be scroo'd. Seriously consider using an SQL table, even an SQLite (therefore, single file) database. "Memory" is sometimes highly over-rated.