迭代 Perl 数组中最多前 n 个元素

3
什么是写作最愉悦的成语方式?
for (take(100,@array)) {...}

如果没有 take (取列表的前n个元素,但如果没有n个元素,则取少于n个元素),怎么办?

我考虑过以下几点:

  • for (@array[0..99]) {...}

    但如果@array的元素少于100个,则会失败。

  • for (@array[0..min(99,$#array)]) {...}

    min不是Perl中的标准函数。

  • for (splice @array,0,100) {...}

    但这会改变数组本身。


take 做什么? - simbabque
使用索引变量进行迭代是否能够实现你想要的效果? - pilcrow
不是真的;我正在寻找一种获取元素的方法。 - Joachim Breitner
7个回答

8

for (@array[0..min(99,$#array)]) {...}
但在Perl中min不是标准函数

minList::Util模块中的标准函数,自5.7.3起已经成为核心模块。

use List::Util qw(min);

for (@array[0..min(99,$#array)]) {  # generator in 5.8.8+
  ...
}

请注意,自perl 5.8.8版本以来(可能更早),它已经足够智能地理解该表达式作为生成器而不是切片。也就是说,从@array中逐个获取元素0到$terminus,而不是取一个匿名切片并复制它们。

谢谢,我本来希望有更优雅的解决方案,但我想这是目前可用的最佳解决方案了。 - Joachim Breitner
你知道有文档(如果有的话)记录了这个操作不会生成临时列表吗?我知道单独使用范围运算符在for循环中是被优化过的,尽管即使对于那个也只有简略的文档。 - ThisSuitIsBlackNot
2
@ThisSuitIsBlackNot,不,我没有。我使用了一个tie()数组,每次FETCH都会发出警告,以说服自己这个优化已经实现。(每个循环只有一个连续的FETCH,而不是一次性全部执行。) - pilcrow
不错的方法!我没想到那个。 - ThisSuitIsBlackNot

6
您需要使用CPAN模块List::Slice
use List::Slice 'head';

foreach my $elem ( head 100, @things ) { ... }

“head”听起来几乎像是一个基本的“实用程序”...用于列表;-) - G. Cito
更多的是“基本实用程序”,而不是像gather/take这样的花哨东西。 - G. Cito
1
@G.Cito - 是的。请参考 https://github.com/Scalar-List-Utils/Scalar-List-Utils/pull/23 - LeoNerd
是的,我希望这种东西能够在语言本身中轻松表达,或者至少默认情况下被安装。 - Joachim Breitner
1
唯一的缺点是,在 for 循环中,此函数将复制 n 个元素到一个匿名数组中,然后对其进行迭代。相比之下,在 for 循环中使用显式传统切片会在每次迭代中逐个获取元素。除非 n 变得很大,否则不要紧。 - pilcrow

2

您表示以下内容最清晰:

take(100, @array)

所以回答你的问题,什么是最干净的,那就是它!我不明白为什么你要寻找替代方案。

换句话说,编写一个子程序。 - ThisSuitIsBlackNot

2
使用 map 如何?
my @array = qw ( 1 2 3 4 );
print join "\n", map { $_ // () } @array[0..10]; 

这段代码从一个列表中取出10个元素,并对其进行“defined”测试,如果未定义,则返回一个空列表。

因此,您可以:

for ( map { $_ // () } @array[0..100] ) { 
   #do something
}

注意 - // 是一个定义或运算符,仅在Perl 5.10+及以上版本中可用。您可以使用一个defined三元运算符代替:
print join "\n", map { defined ? $_ : () } @array[0..10]; 

2
与@simbabque的解决方案相同的问题:如果没有定义所有元素,则会出现错误。可能是一个边缘情况,但我不认为代码不可靠的原因。 - Joachim Breitner
我认为boolean在这里也可以工作 :-D perl -Mboolean -E '@array = ("a" .. "g", "", undef, undef, "x", "0", "z"); print join "\n", map { boolean($_) ? $_ : () } @array[0..100]; ' - G. Cito
授予 - 它确实过滤了undef,作为它所做的结果。我认为这不是什么大问题,因为我很少在循环上下文中需要处理存在但未定义的情况。 - Sobrique

1
你可以在循环内添加一个额外的检查,一旦到达结尾就跳出循环。
my @arr = (1 .. 90);

for ( @arr[0..99]) {
    last unless defined $_;
    say;
}

但是这种方法对于数组中间有 undef 值的情况无效,比如下面这个例子:

my @foo = (1, 2, undef, 4);
my @bar;
$bar[2] = 'foo'; # (undef, undef, 'foo')

不仅仅是 undef... 任何被评估为假 (0, "") 的东西。 - Zaid
@zaid 当然可以。请随意编辑,我正在通话中。 - simbabque
1
“但是,如果数组中间有未定义的值,那么这种方法就行不通了,就像这些一样。” - 对我来说,这将是一个致命问题,因为它似乎非常不可靠。 - Joachim Breitner
如果您不想在子程序中添加很多检查,可以使用一个 CPAN 模块来完成。 boolean 是否适合?请参见我 gather/take 响应的末尾以获取示例。 - G. Cito

1
其他回答已经覆盖了这一点,但为了全面起见,CPAN上确实有几个“南瓜Perl”gather/take实现 :-)

还有达米安·康威(Damian Conway)的Perl6::Gather,它几乎相同,但需要Perl6::Export

它们让您以所需方式处理列表。 例如,“take”字母表的一半:

 perl -E 'use List::Gather; @lpha = ("a" .. "z"); 
         @half = gather { for (@lpha){ take $_ if gathered < 13 } } ; say @half'
 abcdefghijklm

或者更少,如果我们还没有达到一半的话:

(保留HTML标记)
 perl -E 'use List::Gather; @lpha = ("a" .. "c");  
         @half = gather { for (@lpha) { take $_ if gathered < 13 } } ; say @half'
 abc

使用List::Gathergather块可以使用循环(因为在gather{}内部存在词法作用域?),并且该块内需要使用主题$_

perl -E 'use List::Gather; @lpha = ("a" .. "g"); 
          @half = gather for (@lpha) { take $_ if gathered < 13 }; say @half'

使用 Syntax::Keyword::Gather,你可以在 gather{} 块内完成该操作(也可以使用 List::Gather):

perl -E 'use Syntax::Keyword::Gather; @lpha = ("a".."g"); 
        @half = gather { for (@lpha){ take if gathered < 13 } }; say @half'

我觉得使用gather/take是一种不错的处理列表的替代方式。它是否足够好以至于有一天可以随perl一起发布 - 比如在List::Util中 - 是你问题中一个有趣的隐含部分;-)但它们已经在CPAN上了。


附言

为了解决@simbabque、@zaid和@Joachim Breitner提出的关于defined性的一些问题,可以在take()例程中添加更多的检查。

这里我使用Ingy的boolean

perl -E 'use boolean; use List::Gather; 
        @lpha = ("a" .. "g", "", undef, undef, "x", "0", "z"); 
        @half = gather { for (@lpha){ take $_ if boolean($_) && gathered < 13 }};
        use DDP; p @half;'

输出:

[
    [0] "a",
    [1] "b",
    [2] "c",
    [3] "d",
    [4] "e",
    [5] "f",
    [6] "g",
    [7] "x",
    [8] "z"
]

0

我认为你应该使用迭代器模式,即

my $iterator = create_iterator(100);
while (my $element = $iterator->()) {
   ...;
}

limit 可能嵌入到迭代器创建中,例如

sub create_iterator {
     my $limit = shift;
     my @data = (0 x 1000);
     my $i = 0;
     return sub {
        return $data[$i++] if ($i < @data);
     }
}

注意:有一个限制,即undef不能成为@data的一部分。


这与其他答案共享的缺点是,如果列表中有undef,它就会中断。 - Joachim Breitner

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