What decides the order of keys when I print a Perl

2019-01-12 07:26发布

问题:

activePerl 5.8 based

#!C:\Perl\bin\perl.exe
use strict;
use warnings;

# declare a new hash
my %some_hash;

%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
      "wilma", 1.72e30, "betty", "bye\n");

my @any_array;
@any_array = %some_hash;

print %some_hash;
print "\n";
print @any_array;
print "\n";
print $any_array[0];
print "\n";
print $any_array[1];
print "\n";
print $any_array[2];
print "\n";
print $any_array[3];
print "\n";
print $any_array[4];
print "\n";
print $any_array[5];
print "\n";
print $any_array[6];
print "\n";
print $any_array[7];
print "\n";
print $any_array[8];
print "\n";
print $any_array[9];

Output as this

D:\learning\perl>test.pl
bettybye
bar12.4wilma1.72e+030foo352.5hello
bettybye
bar12.4wilma1.72e+030foo352.5hello
betty
bye

bar
12.4
wilma
1.72e+030
foo
35
2.5
hello
D:\learning\perl>

What decided the elements print order in my sample code?

Any rule to follow when print a mixed(strings, numbers) hash in Perl? Thank you.

bar12.4wilma1.72e+030foo352.5hello

[Updated]

With you guys help, i updated the code as below.

#!C:\Perl\bin\perl.exe
use strict;
use warnings;

# declare a new hash
my %some_hash;

%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
      "wilma", 1.72e30, "betty", "bye");

my @any_array;
@any_array = %some_hash;

print %some_hash;
print "\n";
print "\n";
print @any_array;
print "\n";
print "\n";

my @keys;
@keys = keys %some_hash;
for my $k (sort @keys)
{
    print $k, $some_hash{$k};
}

output

D:\learning\perl>test.pl
bettybyebar12.4wilma1.72e+030foo352.5hello

bettybyebar12.4wilma1.72e+030foo352.5hello

2.5hellobar12.4bettybyefoo35wilma1.72e+030
D:\learning\perl>

Finially, after called keys and sort functions. The hash keys print followed the rule below

2.5hellobar12.4bettybyefoo35wilma1.72e+030

回答1:

Elements of a hash are printed out in their internal order, which can not be relied upon and will change as elements are added and removed. If you need all of the elements of a hash in some sort of order, sort the keys, and use that list to index the hash.

If you are looking for a structure that holds its elements in order, either use an array, or use one of the ordered hash's on CPAN.

the only ordering you can rely upon from a list context hash expansion is that key => value pairs will be together.



回答2:

From perldoc -f keys:

The keys of a hash are returned in an apparently random order. The actual random order is subject to change in future versions of Perl, but it is guaranteed to be the same order as either the values or each function produces (given that the hash has not been modified). Since Perl 5.8.1 the ordering is different even between different runs of Perl for security reasons (see Algorithmic Complexity Attacks in perlsec).

...

Perl has never guaranteed any ordering of the hash keys, and the ordering has already changed several times during the lifetime of Perl 5. Also, the ordering of hash keys has always been, and continues to be, affected by the insertion order.

Also note that while the order of the hash elements might be randomised, this "pseudoordering" should not be used for applications like shuffling a list randomly (use List::Util::shuffle() for that, see List::Util, a standard core module since Perl 5.8.0; or the CPAN module Algorithm::Numerical::Shuffle), or for generating permutations (use e.g. the CPAN modules Algorithm::Permute or Algorithm::FastPermute), or for any cryptographic applications.


Note: since you are evaluating a hash in list context, you are at least guaranteed that each key is followed by its corresponding value; e.g. you will never see an output of a 4 b 3 c 2 d 1.



回答3:

I went over your code and made some notes that I think you will find helpful.

use strict;
use warnings;

# declare a new hash and initialize it at the same time
my %some_hash = (
    foo   => 35,       # use the fat-comma or '=>' operator, it quotes the left side
    bar   => 12.4,     
    2.5   => "hello",
    wilma => 1.72e30,
    betty => "bye",    # perl ignores trailing commas, 
                       # the final comma makes adding items to the end of the list less bug prone.
);

my @any_array = %some_hash; # Hash is expanded into a list of key/value pairs.

print "$_ => $some_hash{$_}\n" 
    for keys %some_hash;

print "\n\n",              # You can print multiple newlines in one string.
      "@any_array\n\n";    # print takes a list of things to print.

# In print @foo; @foo is expanded into a list of items to print.  
# There is no separator between the members of @foo in the output.

# However print "@foo"; interpolates @foo into a string. 
# It inserts spaces between the members of the arrays.


# This is the block form of 'for'
for my $k (sort keys %some_hash)
{
    # Interpolating the variables into a string makes it easier to read the output.
    print "$k => $some_hash{$k}\n";
}

Hashes provide unordered, access to data by a string key.

Arrays provide access to ordered data. Random access is available by using a numerical index.

If you need to preserve the order of a group of values, use an array. If you need to look up members of the group by an associated name, use a hash.

If you need to do both, you can use both structures together:

# Keep an array of sorted hash keys.
my @sorted_items = qw( first second third fourth );

# Store the actual data in the hash.
my %item;
@item{ @sorted_items } = 1..4;  # This is called a hash slice.  
                                # It allows you to access a list of hash elements.
                                # This can be a very powerful way to work with hashes.

# random access
print "third => $item{third}\n";


# When you need to access the data in order, iterate over
# the array of sorted hash keys.  Use the keys to access the
# data in the hash.

# ordered access
for my $name ( @sorted_items ) {
    print "$name => $item{$name}\n";
}

Looking at your code samples, I see a couple of things you might want to work on.

  • how looping structures like for and while can be used to reduce repeated code.
  • how to use variable interpolation

BTW, I am glad to see you working on basics and improving your code quality. This investment of time will pay off. Keep up the good work.



回答4:

The elements are (almost certainly) printed out in the order they appear (internally) in the hash table itself -- i.e. based on the hash values of their keys.

The general rule to follow is to use something other than a hash table if you care much about the order.



回答5:

Hashes are not (necessarily) retrieved in a sorted manner. If you want them sorted, you have to do it yourself:

use strict;
use warnings;

my %hash = ("a" => 1, "b" => 2, "c" => 3, "d" => 4);

for my $i (sort keys %hash) {
    print "$i -> $hash{$i}\n";
}

You retrieve all the keys from a hash by using keys and you then sort them using sort. Yeah, I know, that crazy Larry Wall guy, who would've ever thought of calling them that? :-)

This outputs:

a -> 1
b -> 2
c -> 3
d -> 4


回答6:

For most practical purposes, the order in which a hash table (not just Perl hash variables, but hash tables in general) can be considered random.

In reality, depending on the hashing implementation, the order may actually be deterministic. (i.e., If you run the program multiple times putting the same items into the hash table in the same order each time, they'll be stored in the same order each time.) I know that Perl hashes used to have this characteristic, but I'm not sure about current versions. In any case, hash key order is not a reliable source of randomness to use in cases where randomness is desirable.

Short version, then:

Don't use a hash if you care about the order (or lack of order). If you want a fixed order, it will be effectively random and if you want a random order, it will be effectively fixed.



回答7:

A hash defines no ordering properties. The order in which things come out will be unpredictable.



回答8:

And if you are crazy and have no duplicate values in your hash, and you need the values sorted, you can call reverse on it.

my %hash = ("a" => 1, "b" => 2, "c" => 3, "d" => 4);
my %reverse_hash = reverse %hash;

print $_ for sort keys %reverse_hash;

Caveat is the unique values part, duplicates will be overwritten and only one value will get in.



标签: perl hash