Подтвердить что ты не робот

Есть ли элегантный zip для чередования двух списков в Perl 5?

Я недавно "нуждался" в zip-функции в Perl 5 (пока я думал о Как рассчитать относительное время?), то есть функцию, которая принимает два списка и "застегивает" их вместе в один список, перемежая элементы.

Пример

(псевдо):

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

Haskell имеет zip в прелюдии и Perl 6 имеет zip operator, но как вы это сделаете элегантным способом в Perl 5?

4b9b3361

Ответ 1

Предполагая, что у вас ровно два списка, и они имеют одинаковую длину, вот решение изначально 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 для объединения каждого индекса с другим индексом, который находится на том же расстоянии от точки поворота, что и первый индекс из start, давая нам (в данном случае) 0 5 1 6 2 7 3 8 4 9. Затем берем срез из @_, используя это как список индексов. Это означает, что если 'a', 'b', 'c', 1, 2, 3 передается на zip2, он вернет этот список, перегруппированный в 'a', 1, 'b', 2, 'c', 3.

Это может быть записано в одном выражении вдоль строк ysths следующим образом:

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

Если вы хотите использовать любой вариант, зависит от того, сможете ли вы вспомнить, как они работают, но для меня это был расширитель ума.

Ответ 2

Модуль 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; 
}

Ответ 3

Я нахожу следующее решение простым и легким для чтения:

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

Я считаю, что это также быстрее, чем решения, которые сначала создают массив в неправильном порядке, а затем используют срез для изменения порядка или решения, которые изменяют @a и @b.

Ответ 4

Для массивов одинаковой длины:

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

Ответ 5

Algorithm::Loops действительно приятно, если вы делаете многое из этого.

Мой собственный код:

sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), [email protected]_] }

Ответ 6

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) сделало бы это.

Надеюсь, это поможет!

Ответ 7

Это абсолютно не изящное решение, и это не лучшее решение для любой части воображения. Но это весело!

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 - это упражнение, оставленное читателю.