How do Perl FIRSTKEY and NEXTKEY work
Tie::Hash has these:
sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} }
sub NEXTKEY { each %{$_[0]} }
NEXTKEY takes two arguments, one of which i开发者_开发问答s the last key but that arg is never referenced?
The various Tie docs don't shed any light on this other than this in perltie:
my $a = keys %{$self->{LIST}}; # reset each() iterator
looking at the doc for each doesn't add to this.
What's going on?
You only need to worry about the second argument to NEXTKEY
if you care about which key was accessed last. By default, hashes don't care about the order, so it is not used.
As for the second part, the keys
function in scalar context returns the number of items in the hash. Any call to keys resets the iterator used by keys
and each
because it exhausts the iterator.
A call to keys
is really a call to FIRSTKEY
and calls to NEXTKEY
until there are no more items left in that haven't been returned.
A call to each
is a call to FIRSTKEY
(if FIRSTKEY
hasn't been called yet) or a call to NEXTKEY
(if FIRSTKEY
has been called).
#!/usr/bin/perl
use strict;
use warnings;
my $i = 0;
tie my %h, "HASH::Sorted", map { $_ => $i++ } "a" .. "g";
for my $key (keys %h) {
print "$key => $h{$key}\n";
}
print "\n";
my $first = each %h;
print "first $first => $h{$first}\n";
my ($second_key, $second_value) = each %h;
print "second $second_key => $second_value\n";
print "\nall of them again:\n";
for my $key (keys %h) {
print "$key => $h{$key}\n";
}
package HASH::Sorted;
sub TIEHASH {
my $class = shift;
return bless { _hash => { @_ } }, $class;
}
sub FETCH {
my ($self, $key) = @_;
return $self->{_hash}{$key};
}
sub STORE {
my ($self, $key, $value) = @_;
return $self->{_hash}{$key} = $value;
}
sub DELETE {
my ($self, $key) = @_;
return delete $self->{_hash}{$key};
}
sub CLEAR {
my $self = shift;
%{$self->{_hash}} = ();
}
sub EXISTS {
my ($self, $key) = @_;
return exists $self->{_hash}{$key};
}
sub FIRSTKEY {
my $self = shift;
#build iterator
$self->{_list} = [ sort keys %{$self->{_hash}} ];
return $self->NEXTKEY;
}
sub NEXTKEY {
my $self = shift;
return shift @{$self->{_list}};
}
sub SCALAR {
my $self = shift;
return scalar %{$self->{_hash}};
}
This one uses a custom each method to allow you to iterate over the sorted hash more than one time. All of the standard rules about not being allowed to add or remove keys are still in effect though. It would be trivial to add a warning that iterators were still in use on a call to STORE
or DELETE
.
#!/usr/bin/perl
use strict;
use warnings;
my $i = 0;
tie my %h, "HASH::Sorted", map { $_ => $i++ } "a" .. "g";
for my $key (keys %h) {
print "$key => $h{$key}\n";
}
print "\n";
my $first = each %h;
print "first $first => $h{$first}\n";
my ($second_key, $second_value) = each %h;
print "second $second_key => $second_value\n";
print "\nall of them again:\n";
for my $key (keys %h) {
print "$key => $h{$key}\n";
}
print "\nmultiple iterators\n";
my $o = tied %h;
while (my ($k, $v) = $o->each("outer")) {
print "$k => $v\n";
while (my ($k, $v) = $o->each("inner")) {
print "\t$k => $v\n";
}
}
print "\nhybrid solution\n";
while (my ($k, $v) = each %h) {
print "$k => $v\n";
#the iter_name is an empty string
while (my ($k, $v) = $o->each) {
print "\t$k => $v\n";
}
}
package HASH::Sorted;
sub each {
my ($self, $iter_name) = (@_, "DEFAULT");
#each has not been called yet for this iter
unless (exists $self->{_iters}{$iter_name}) {
$self->{_iters}{$iter_name} = [ sort keys %{$self->{_hash}} ];
}
#end of list
unless (@{$self->{_iters}{$iter_name}}) {
delete $self->{_iters}{$iter_name};
return;
}
my $key = shift @{$self->{_iters}{$iter_name}};
if (wantarray) {
return $key, $self->{_hash}{$key};
}
return $key;
}
sub TIEHASH {
my $class = shift;
return bless {
_hash => { @_ },
_iters => {},
}, $class;
}
sub FETCH {
my ($self, $key) = @_;
return $self->{_hash}{$key};
}
sub STORE {
my ($self, $key, $value) = @_;
return $self->{_hash}{$key} = $value;
}
sub DELETE {
my ($self, $key) = @_;
return delete $self->{_hash}{$key};
}
sub CLEAR {
my $self = shift;
%{$self->{_hash}} = ();
}
sub EXISTS {
my ($self, $key) = @_;
return exists $self->{_hash}{$key};
}
sub FIRSTKEY {
my $self = shift;
#build iterator
$self->{_list} = [ sort keys %{$self->{_hash}} ];
return $self->NEXTKEY;
}
sub NEXTKEY {
my $self = shift;
return shift @{$self->{_list}};
}
sub SCALAR {
my $self = shift;
return scalar %{$self->{_hash}};
}
精彩评论