How can I maintain the order of actual list after counting its occurrence using a hash in the following program? For example, <DATA>
are
a
b
e
a
c
d
a
c
d
b
etc.
Using hash, i counted the occurrence of each element.
and what i want is:
a 3
b 2
e 1
c 2
d 2
but the following program shows me otherwise.
my (%count, $line, @array_1, @array_2);
while ($line = <DATA>) {
$count{$line}++ if ( $line =~ /\S/ );
}
@array_1 = keys(%count);
@array_2 = values(%count);
for(my $i=0; $i<$#array_1; $i++)
{
print "$array_1[$i]\t $array_2[$i]";
}
Hashes are not ordered, but as usual, CPAN offers a solution: Tie::IxHash
use Tie::IxHash;
my %count;
tie %count, 'Tie::IxHash';
while ($line = <DATA>) {
$count{$line}++ if ( $line =~ /\S/ );
}
while( my( $key, $value)= each %count) {
print "$key\t $value";
}
Data in a hash table is stored in order of the keys' hash code, which for most purposes is like a random order. You also want to store the order of the first appearance of each key. Here's one way to approach this problem:
my (%count, $line, @display_order);
while ($line = <DATA>) {
chomp $line; # strip the \n off the end of $line
if ($line =~ /\S/) {
if ($count{$line}++ == 0) {
# this is the first time we have seen the key "$line"
push @display_order, $line;
}
}
}
# now @display_order holds the keys of %count, in the order of first appearance
foreach my $key (@display_order)
{
print "$key\t $count{$key}\n";
}
From perlfaq4's answer to "How can I make my hash remember the order I put elements into it?"
How can I make my hash remember the order I put elements into it?
Use the Tie::IxHash from CPAN.
use Tie::IxHash;
tie my %myhash, 'Tie::IxHash';
for (my $i=0; $i<20; $i++) {
$myhash{$i} = 2*$i;
}
my @keys = keys %myhash;
# @keys = (0,1,2,3,...)
Simply:
my (%count, @order);
while(<DATA>) {
chomp;
push @order, $_ unless $count{$_}++;
}
print "$_ $count{$_}\n" for @order;
__DATA__
a
b
e
a
c
d
a
c
d
b
Or as oneliner
perl -nlE'$c{$_}++or$o[@o]=$_}{say"$_ $c{$_}"for@o'<<<$'a\nb\ne\na\nc\nd\na\nc\nd\nb'
Another option is David Golden's (@xdg) simple pure perl Hash::Ordered
module. You gain order but it is slower since the hash becomes an object behind the scenes and you use methods for accessing and modifying hash elements.
There are probably benchmarks that can quantify just how much slower the module is than regular hashes but it's a cool way to work with key/value data structures in small scripts and fast enough for me in that sort of application. The documentation mentions several other approaches to ordering a hash as well.
I'm not convinced that this is always a better technique, but I have used it sometimes. Instead of just having the "seen" type of hash, it can store both the count and order noticed.
Basically, instead of $count{$line}
having the number of times seen, $count{$line}{count}
is the times seen and $count{$line}{order}
is the order in which it was seen.
my %count;
while (my $line = <DATA>) {
chomp $line;
if ($line =~ /\S/) {
$count{$line} ||= { order => scalar(keys(%count)) };
$count{$line}{count}++;
}
}
for my $line (sort { $count{$a}{order} <=> $count{$b}{order} } keys %count ) {
print "$line $count{$line}{count}\n";
}
Hashes are just arrays until they're assigned in Perl, so if you cast it as an array, you can iterate over it in its original order:
my @array = ( z => 6,
a => 8,
b => 4 );
for (my $i=0; $ar[$i]; ++$i) {
next if $i % 2;
my $key = $ar[$i];
my $val = $ar[$i+1];
say "$key: $val"; # in original order
}
You lose the benefits of hash indexing if you do that obviously. But since a hash is just an array, you can create one just by assigning the array to a hash:
my %hash = @array;
say $hash{z};
This is maybe just a variation on the "use an array as an index" solution, but I think it's neater because instead of typing out your index manually (or in some other way), you're creating it directly from the source array.
来源:https://stackoverflow.com/questions/1558625/how-can-i-maintain-the-order-of-keys-i-add-to-a-perl-hash