我有一个固定大小的数组,其中数组的大小始终为3。
my @array = ('foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5);
如何聚类数组成员以便我们可以获得 数组3的数组:
$VAR = [ ['foo','bar','qux'],
['foo1','bar','qux2'],
[3, 4, 5] ];
答案 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循环或map
或grep
一起使用的输出。
我喜欢在我的代码中链接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)
我尝试的第一种也许是最简单的方法是使用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的数组。