Perl的FIRSTKEY和NEXTKEY是如何工作的?

7

Tie::Hash拥有以下特点:

sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} }
sub NEXTKEY  { each %{$_[0]} }

NEXTKEY需要两个参数,其中一个是最后一个键,但该参数从未被引用?

各种Tie文档并没有揭示这一点,除了在perltie中提到:

my $a = keys %{$self->{LIST}};      # reset each() iterator

查看每个文档并不能解决这个问题。

发生了什么事?

2个回答

12

如果你只关心最后访问的键是哪个,那么你只需要担心 NEXTKEY 的第二个参数。默认情况下,哈希表不关心顺序,因此这个参数不会被使用。

至于第二部分,在标量上下文中,keys 函数返回哈希表中项目的数量。任何对 keys 的调用都会重置 keyseach 使用的迭代器,因为它耗尽了迭代器。

对 keys 的调用实际上是对 FIRSTKEY 的调用,并调用 NEXTKEY,直到没有未返回的项为止。

对 each 的调用是对 FIRSTKEY 的调用(如果尚未调用 FIRSTKEY)或对 NEXTKEY 的调用(如果已经调用了 FIRSTKEY)。

#!/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}};
}

1
在数组上下文中,什么情况下会调用NEXTKEY?我从未见过该方法返回多个值的逻辑。 - pilcrow
@pilcrow,这并不是我误解了 my ($k, $v) = each %h; 将以列表上下文调用 FIRSTKEYNEXTKEY,实际上它将调用 FETCH 来获取值。 我会修改代码的。 - Chas. Owens
哇,谢谢。你刚才让我对perltie的理解完全颠倒了一下。:) - pilcrow
你能否评论一下如何处理对同一对象的多个同时循环。只有一个_list。 - mmccoo
@mmccoo Perl默认哈希不支持多个迭代器,但是可以通过使用自定义方法来实现,我将创建第二个答案并提供一种解决方案。 - Chas. Owens

2

这个方法使用自定义的 each 方法,允许您对排序后的哈希表进行多次迭代。但是,所有关于不允许添加或删除键的标准规则仍然有效。在调用 STOREDELETE 时,很容易添加一个警告,提示迭代器仍在使用中。

#!/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}};
}

感谢您的跟进。似乎应该有一种方法可以在不偏离正常循环语法的情况下完成此操作。我想到的一个问题是,'each'如何在迭代之间存储其状态?'each'似乎具有Perl的其他部分没有的“yield”功能。 - mmccoo
@mmccoo 请参考 perl 源代码或者 perlapi 中的 hv.c 文件中的 hv_iternext 函数 (http://perldoc.perl.org/perlapi.html#hv_iternext)。 - Chas. Owens
@mmccoo 如果没有额外的信息,each 怎么知道使用哪个迭代器呢?当您想要以不同速率迭代相同哈希时,通常要做的是使用 keys 获取键列表并对其进行迭代。出于好奇,您为什么想要能够拥有两个哈希迭代器呢? - Chas. Owens
@mmccoo Schwern正在研究使each能够同时迭代多个项目。 计划使用Devel::Declare将每个each函数修改为safe_each UNIQUE_ID。 在此处查看:http://github.com/schwern/perl5i/issues/issue/142 - Chas. Owens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接