如何将Perl数组分区为相同大小的块?

时间:2009-09-29 06:28:51

标签: perl

我有一个固定大小的数组,其中数组的大小始终为3。

my @array = ('foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5);

如何聚类数组成员以便我们可以获得 数组3的数组:

$VAR = [ ['foo','bar','qux'],
         ['foo1','bar','qux2'],
         [3, 4, 5] ];

9 个答案:

答案 0 :(得分:30)

my @VAR;
push @VAR, [ splice @array, 0, 3 ] while @array;

或者您可以使用natatime

中的List::MoreUtils
use List::MoreUtils qw(natatime);

my @VAR;
{
  my $iter = natatime 3, @array;
  while( my @tmp = $iter->() ){
    push @VAR, \@tmp;
  }
}

答案 1 :(得分:6)

我非常喜欢List :: MoreUtils并经常使用它。但是,我从未喜欢natatime功能。它不会产生可与for循环或mapgrep一起使用的输出。

我喜欢在我的代码中链接map / grep / apply操作。一旦你理解了这些功能如何工作,它们就会非常富有表现力并且非常强大。

但很容易使函数像natatime一样工作,返回数组引用列表。

sub group_by ($@) {
    my $n     = shift;
    my @array = @_;

    croak "group_by count argument must be a non-zero positive integer"
        unless $n > 0 and int($n) == $n;

    my @groups;
    push @groups, [ splice @array, 0, $n ] while @array;

    return @groups;
}

现在你可以这样做:

my @grouped = map [ reverse @$_ ],
              group_by 3, @array;

**更新Chris Lutz的建议**

Chris,我可以看到你建议在界面中添加代码引用的优点。这样就构建了类似地图的行为。

# equivalent to my map/group_by above
group_by { [ reverse @_ ] } 3, @array;

这很简洁。但为了保持良好的{}代码引用语义,我们将count参数3置于难以看清的位置。

我觉得我最喜欢的东西就像我最初写的那样。

链式地图并不比我们使用扩展API获得的更详细。 使用原始方法,可以使用grep或其他类似函数,而无需重新实现它。

例如,如果将代码引用添加到API,则必须执行以下操作:

my @result = group_by { $_[0] =~ /foo/ ? [@_] : () } 3, @array;

得到相当于:

my @result = grep $_->[0] =~ /foo/,
             group_by 3, @array;

由于我为了方便链接而建议这样做,我更喜欢原版。

当然,允许任何一种形式都很容易:

sub _copy_to_ref { [ @_ ] }

sub group_by ($@) {
    my $code = \&_copy_to_ref;
    my $n = shift;

    if( reftype $n eq 'CODE' ) {
        $code = $n;
        $n = shift;
    }

    my @array = @_;

    croak "group_by count argument must be a non-zero positive integer"
        unless $n > 0 and int($n) == $n;

    my @groups;
    push @groups, $code->(splice @array, 0, $n) while @array;

    return @groups;
}

现在任何一种形式都应该有效(未经测试)。我不确定我是否喜欢原始API,或者更好的内置地图功能。

有人想到吗?

**再次更新**

Chris指出,可选的代码引用版本会强制用户执行以下操作:

group_by sub { foo }, 3, @array;

哪个不太好,违反了期望。由于没有办法拥有一个灵活的原型(我所知道的),这使得kibosh在扩展的API上,并且我坚持使用原始的。

另一方面,我开始在备用API中使用匿名子,但我将其更改为命名子,因为我对代码的外观略有不满。没有真正的理由,只是直观的反应。我不知道这两种方式是否重要。

答案 2 :(得分:5)

或者这个:

my $VAR;
while( my @list = splice( @array, 0, 3 ) ) {
    push @$VAR, \@list;
}

答案 3 :(得分:5)

另一个答案(Tore的变体,使用拼接,但避免使用while循环而支持更多的Perl-y映射)

my $result = [ map { [splice(@array, 0, 3)] } (1 .. (scalar(@array) + 2) % 3) ];

答案 4 :(得分:4)

作为一种学习经历,我决定在Perl6

中做到这一点

我尝试的第一种也许是最简单的方法是使用map

my @output := @array.map: -> $a, $b?, $c? { [ $a, $b // Nil, $c // Nil ] };
.say for @output;
foo bar qux
foo1 bar qux2
3 4 5

这似乎不太可扩展。如果我想一次take列表10中的项目,那将会非常烦人。 ......嗯,我刚才提到了"take",并且有一个名为take的关键字,让我们在子程序中尝试使其更有用。

sub at-a-time ( Iterable \sequence, Int $n where $_ > 0 = 1 ){
  my $is-lazy = sequence.is-lazy;
  my \iterator = sequence.iterator;

  # gather is used with take
  gather loop {
    my Mu @current;
    my \result = iterator.push-exactly(@current,$n);

    # put it into the sequence, and yield
    take @current.List;

    last if result =:= IterationEnd;
  }.lazy-if($is-lazy)
}

对于踢球,让我们尝试对抗无限的斐波纳契序列列表

my $fib = (1, 1, *+* ... *);
my @output = at-a-time( $fib, 3 );
.say for @output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)

请注意,我使用的是$fib而不是@fib。这是为了防止Perl6缓存Fibonacci序列的元素 每次需要时,将它放入子程序中创建一个新的序列可能是一个好主意,这样当你完成它们时,这些值就会被垃圾收集。
如果输入序列是,我还使用.is-lazy.lazy-if标记输出序列lazy。由于它进入了一个数组@output,它会尝试从无限列表中生成所有元素,然后再继续下一行。


等一下,我记得.rotor

my @output = $fib.rotor(3);

.say for @output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)

.rotor实际上比我演示的要强大得多。

如果您希望它在结尾处返回部分匹配,则需要添加:partial to the arguments of .rotor

答案 5 :(得分:3)

试试这个:

$VAR = [map $_ % 3 == 0 ? ([ $array[$_], $array[$_ + 1], $array[$_ + 2] ]) 
                        : (),
            0..$#array];

答案 6 :(得分:3)

另一种通用解决方案,对原始数组无损:

use Data::Dumper;

sub partition {
    my ($arr, $N) = @_; 

    my @res;
    my $i = 0;

    while ($i + $N-1 <= $#$arr) {
        push @res, [@$arr[$i .. $i+$N-1]];
        $i += $N; 
    }   

    if ($i <= $#$arr) {
        push @res, [@$arr[$i .. $#$arr]];
    }   
    return \@res;
}

print Dumper partition(
    ['foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5], 
    3   
);

输出:

$VAR1 = [
          [
            'foo',
            'bar',
            'qux'
          ],
          [
            'foo1',
            'bar',
            'qux2'
          ],
          [
            3,
            4,
            5
          ]
        ];

答案 7 :(得分:2)

perl -e '
use List::NSect qw{spart};
use Data::Dumper qw{Dumper};
my @array = ("foo", "bar", "qux", "foo1", "bar", "qux2", 3, 4, 5);
my $var = spart(3, @array);
print Dumper $var;
'

$VAR1 = [
      [
        'foo',
        'bar',
        'qux'
      ],
      [
        'foo1',
        'bar',
        'qux2'
      ],
      [
        3,
        4,
        5
      ]
    ];

答案 8 :(得分:1)

下面是一个更通用的问题解决方案:

my @array = ('foo', 'bar', 1, 2);
my $n = 3;
my @VAR = map { [] } 1..$n;
my @idx = sort map { $_ % $n } 0..$#array;

for my $i ( 0..$#array ){
        push @VAR[ $idx[ $i ] ], @array[ $i ];
}

当数组中的项数不是3时,这也适用。 在上面的例子中,其他解决方案包括例如splice将生成两个长度为2且长度为0的数组。