题
我最近“需要”Perl 5 中的 zip 函数(当时我正在考虑 如何计算相对时间?), IE。一种函数,它接受两个列表并将它们“压缩”到一个列表中,并交错元素。
(伪)示例:
@a=(1, 2, 3);
@b=('apple', 'orange', 'grape');
zip @a, @b; # (1, 'apple', 2, 'orange', 3, 'grape');
Haskell 在 Prelude 中拉开了序幕 和 Perl 6 有一个 zip 运算符 内置的,但是如何在 Perl 5 中以优雅的方式做到这一点呢?
解决方案
假设你有两个列表,并且它们的长度完全相同,下面是 merlyn (Randal Schwartz) 最初提出的解决方案,他称其为 perversely perlish:
sub zip2 {
my $p = @_ / 2;
return @_[ map { $_, $_ + $p } 0 .. $p - 1 ];
}
这里发生的情况是,对于 10 个元素的列表,首先,我们找到中间的枢轴点,在本例中为 5,并将其保存在 $p
. 。然后我们创建一个到该点的索引列表,在本例中为 0 1 2 3 4。接下来我们使用 map
将每个索引与另一个索引配对,该索引与枢轴点的距离与第一个索引从开始处的距离相同,给我们(在本例中)0 5 1 6 2 7 3 8 4 9。然后我们从中取出一片 @_
使用它作为索引列表。这意味着如果 'a', 'b', 'c', 1, 2, 3
被传递给 zip2
, ,它将返回该列表重新排列成 'a', 1, 'b', 2, 'c', 3
.
这可以按照 ysth 的方式写在单个表达式中,如下所示:
sub zip2 { @_[map { $_, $_ + @_/2 } 0..(@_/2 - 1)] }
你是否想使用任何一种变体取决于你是否能记住它们是如何工作的,但对我来说,这是一种思维扩展。
其他提示
这 列表::更多实用工具 模块有一个 zip/mesh 函数可以解决这个问题:
use List::MoreUtils qw(zip);
my @numbers = (1, 2, 3);
my @fruit = ('apple', 'orange', 'grape');
my @zipped = zip @numbers, @fruit;
这是网格函数的来源:
sub mesh (\@\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
my $max = -1;
$max < $#$_ && ($max = $#$_) for @_;
map { my $ix = $_; map $_->[$ix], @_; } 0..$max;
}
我发现以下解决方案简单易读:
@a = (1, 2, 3);
@b = ('apple', 'orange', 'grape');
@zipped = map {($a[$_], $b[$_])} (0 .. $#a);
我相信它也比首先以错误的顺序创建数组然后使用切片重新排序的解决方案或修改的解决方案更快 @a
和 @b
.
对于相同长度的数组:
my @zipped = ( @a, @b )[ map { $_, $_ + @a } ( 0 .. $#a ) ];
my @l1 = qw/1 2 3/; my @l2 = qw/7 8 9/; my @out; push @out, shift @l1, shift @l2 while ( @l1 || @l2 );
如果列表的长度不同,这会将“undef”放入额外的插槽中,但如果您不想这样做,您可以轻松解决此问题。像 ( @l1[0] && shift @l1 ) 这样的东西就可以了。
希望这可以帮助!
Algorithm::Loops
如果你经常做这样的事情,那就太好了。
我自己的代码:
sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), 1..@_] }
这完全不是一个优雅的解决方案,也不是任何想象的最佳解决方案。但这很有趣!
package zip;
sub TIEARRAY {
my ($class, @self) = @_;
bless \@self, $class;
}
sub FETCH {
my ($self, $index) = @_;
$self->[$index % @$self][$index / @$self];
}
sub STORE {
my ($self, $index, $value) = @_;
$self->[$index % @$self][$index / @$self] = $value;
}
sub FETCHSIZE {
my ($self) = @_;
my $size = 0;
@$_ > $size and $size = @$_ for @$self;
$size * @$self;
}
sub CLEAR {
my ($self) = @_;
@$_ = () for @$self;
}
package main;
my @a = qw(a b c d e f g);
my @b = 1 .. 7;
tie my @c, zip => \@a, \@b;
print "@c\n"; # ==> a 1 b 2 c 3 d 4 e 5 f 6 g 7
如何处理 STORESIZE
/PUSH
/POP
/SHIFT
/UNSHIFT
/SPLICE
是留给读者的练习。