在Perl 5中是否有一个优雅的zip可以插入两个列表?

时间:2020-03-05 18:46:06  来源:igfitidea点击:

最近,我在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中以一种优雅的方式来实现呢?

解决方案

回答

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; 
}

回答

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)之类的东西可以做到。

希望这可以帮助!

回答

假设我们有两个列表,并且它们的长度完全相同,这是梅林(Randal Schwartz)最初提出的一种解决方案,他称其为perly perly:

sub zip2 {
    my $p = @_ / 2; 
    return @_[ map { $_, $_ + $p } 0 .. $p - 1 ];
}

这里发生的是,对于一个包含10个元素的列表,首先,我们在中间找到枢轴点,在本例中为5,并将其保存在$ p中。然后,我们列出直至该点的索引列表,在这种情况下为0 1 2 34. 接下来,我们使用" map"将每个索引与另一个索引配对,该另一个索引与枢轴点的距离相同,因为第一个索引与索引点的距离相同。首先,给我们(在这种情况下)0 5 1 6 2 7 3 8 49. 然后,我们从@ _中获取一个切片,并将其用作索引列表。这意味着,如果将''a','b','c',1、2、3'传递给zip2,则它将返回重新排列为'a',1,'b',2的列表。 'c',3`。

可以按照ysths线将其写在一个表达式中,如下所示:

sub zip2 { @_[map { $_, $_ + @_/2 } 0..(@_/2 - 1)] }

我们是否要使用任何一种变体取决于我们是否可以看到自己记得它们的工作原理,但是对我而言,这是一个扩展思维的方法。

回答

如果我们做很多这样的事情,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是读者的练习。

回答

对于相同长度的数组:

my @zipped = ( @a, @b )[ map { $_, $_ + @a } ( 0 .. $#a ) ];

回答

我发现以下解决方案简单明了:

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

我认为它比先以错误顺序创建数组然后使用slice重新排序的解决方案或者修改@ a@ b的解决方案还要快。