27

我最近在 Perl 5 中“需要”了一个 zip 函数(当时我正在考虑如何计算相对时间?),即一个函数,它采用两个列表并将它们“压缩”到一个列表中,将元素交错。

(伪)示例:

@a=(1, 2, 3);
@b=('apple', 'orange', 'grape');
zip @a, @b; # (1, 'apple', 2, 'orange', 3, 'grape');

Haskell 在 Prelude 中有 zip,Perl 6 有一个内置的 zip 操作符,但是如何在 Perl 5 中以优雅的方式做到这一点?

4

7 回答 7

37

假设您恰好有两个列表并且它们的长度完全相同,这是 merlyn (Randal Schwartz) 最初提出的一个解决方案,他将其称为 perverse 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)] }

你是否想使用任何一种变体取决于你是否能看到自己记住它们是如何工作的,但对我来说,这是一个思维扩展器。

于 2008-09-16T12:56:05.490 回答
30

List::MoreUtils模块有一个 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; 
}
于 2008-09-01T20:13:58.903 回答
14

我发现以下解决方案简单易读:

@a = (1, 2, 3);
@b = ('apple', 'orange', 'grape');
@zipped = map {($a[$_], $b[$_])} (0 .. $#a);

我相信它也比首先以错误顺序创建数组然后使用 slice 重新排序的解决方案或修改@a@b.

于 2009-02-13T01:37:10.220 回答
10

对于相同长度的数组:

my @zipped = ( @a, @b )[ map { $_, $_ + @a } ( 0 .. $#a ) ];
于 2009-01-28T01:41:56.483 回答
2
我的@l1 = qw/1 2 3/;
我的@l2 = qw/7 8 9/;
我的@out;
推@out,转移@l1,转移@l2 而(@l1 || @l2);

如果列表的长度不同,这会将“undef”放在额外的插槽中,但如果您不希望这样做,您可以轻松解决此问题。像 ( @l1[0] && shift @l1 ) 这样的东西会做到这一点。

希望这可以帮助!

于 2008-09-16T09:12:13.220 回答
2

Algorithm::Loops如果你做很多这样的事情真的很好。

我自己的代码:

sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), 1..@_] }
于 2008-09-19T06:42:19.170 回答
0

这完全不是一个优雅的解决方案,也不是任何想象中的最佳解决方案。但这很有趣!

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是留给读者的练习。

于 2008-10-05T02:47:56.070 回答