如何在List::Util中编写与sort或reduce具有相同签名的子程序

我希望能够像这样使用一个函数:

my @grouped = split_at {
      $a->{time}->strftime("%d-%b-%Y %H") ne 
      $b->{time}->strftime("%d-%b-%Y %H") 
} @files;

wheresplit_at根据函数将数组拆分为arrayrefs数组,如下所示:

sub split_at(&@) {
# split an array into an arrayrefs based on a function
    my $cb = shift;

    my @input = @_;

    my @retval = ( [  ] );

    $i = 0;
    while ($i <= $#input) {
        push @{$retval[$#retval]}, $input[$i];
        $i++;
        if (($i < $#input) && $cb->($input[$i-1], $input[$i])) { push @retval, [] }
    }
    pop @retval unless @{$retval[$#retval]};
    return @retval;
}

现在我只能这样称呼它:

my @grouped = split_at { 
          $_[0]->{time}->strftime("%d-%b-%Y %H") ne 
          $_[1]->{time}->strftime("%d-%b-%Y %H") 
} @files;

这里使用Time::Piece.

我试图找出能够将其称为(简化)的方法:

my @foo = split_at { $a <=> $b } @foo;

以类似的方式sortList::Util::reduce

我已经检查了List::Util::PP 中的reduce 代码以供参考,但我对它的理解不足以将其移植到我的案例中。

回答

您需要做的主要事情是将词法第一个和第二个值的值分配到调用者的命名空间中作为$aand $breduce使用一个值执行此操作:

#     /- 1
#     |  /-3
#     |  |             /-4 
# 0   |  |             |
local *{"${pkg}::a"} = $a;
#      -----------/
#             2

让我们快速看一下:

  1. local覆盖此范围内的全局变量以及临时包含在其中的所有范围。因此,当我们调用回调时,该变量将具有不同的值。请记住,$a并且$b是特殊的全局变量。

  2. 它使用 glob*分配到该命名空间中的符号表中,并且它足够聪明,可以在符号表中找到正确的插槽。想象一个抽屉,一个抽屉用于标量,一个用于数组,一个用于哈希等等。您可能也见过这种安装 subs 的语法。

  3. {""}语法允许从多个部分和其他变量中构建一个变量名称。a例如,我们使用包和变量名称来获取main::a. 这需要strict 'refs'关闭,否则perl会抱怨,所以no strict 'refs'代码中有更高的。在*从1表明,我们正在使用这个变量名称类型水珠。

  4. 这类似于 1,但这次使用的是标量槽。在这种情况下,我们不必禁用严格引用,因为它只是一个普通字符串,Perl 认为这是安全的。这是告诉解释器变量名结束的语法。比较这两个:

    my $foo, $foo_bar;
    "$foo_bar_baz";    # variable doesn't exist error
    "${foo}_bar_baz";  # prints value of $foo and string _bar_baz
    "${foo_bar}_baz";  # prints value of $foo_bar and string _baz
    

    我们需要这个,这样我们就不会$a在 package 中获得 a 的值pkg

  5. 我们$a从 1 开始为类型 glob 中的那个插槽分配一个对词法的引用。这将是一个标量引用。本质上$a,我们的变量现在有了另一个名字$pkg::a。当您说use Foo 'bar'.

看了这个,我们可以更新你的代码。

sub split_at(&@) {
    my $cb = shift;
    my @input = @_;

    my @retval = [];

    my $pkg = caller;
    my $max = $#input;

    for my $i (0 .. $max) {
        push @{$retval[$#retval]}, $input[$i];
        no strict 'refs';
        local *{"${pkg}::a"} = $input[$i];
        local *{"${pkg}::b"} = $input[$i + 1]; # autovivification!
        if (($i < $max) && $cb->()) {
            push @retval, [];
        }
    }
    pop @retval unless @{$retval[$#retval]};
    return @retval;
}


my @files = map { { foo => $_ } } qw/ a a b b c d /;

my @grouped = split_at {
          $a->{foo} ne
          $b->{foo}
} @files;

我已经为我们的例子简化了数据结构。

我们既需要$a$b为循环的每个迭代。因为$b通过类型 glob将下一个元素分配给我们正在寻找的元素会导致自动激活,所以我不得不将循环类型更改为计数器并引入一个新变量$max。我在构建它时遇到了一个无限循环,因为它不断将undef元素放在@input.

除此之外,它几乎是相同的代码。我们不再需要向回调传递参数,我们需要有no strict 'refs'. 您通常会strict关闭尽可能小的范围。我们还需要使用caller的命名空间,以便我们可以将变量放在那里。

您还需要注意一件事。List::Util::PP 在调用者的命名空间中设置$a$b,因为否则我们可能会导致警告,因此如果您想将此函数放入库中,您可能应该使用它使用的相同代码。

sub import {
  my $pkg = caller;
 
  # (RT88848) Touch the caller's $a and $b, to avoid the warning of
  #   Name "main::a" used only once: possible typo" warning
  no strict 'refs';
  ${"${pkg}::a"} = ${"${pkg}::a"};
  ${"${pkg}::b"} = ${"${pkg}::b"};
 
  goto &Exporter::import;
}
  • @simone, Technically, the only one 🙂

以上是如何在List::Util中编写与sort或reduce具有相同签名的子程序的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>