Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an elegant zip to interleave two lists in Perl 5?

Tags:

zip

perl

I recently "needed" a zip function in Perl 5 (while I was thinking about How do I calculate relative time?), i.e. a function that takes two lists and "zips" them together to one list, interleaving the elements.

(Pseudo)example:

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

Haskell has zip in the Prelude and Perl 6 has a zip operator built in, but how do you do it in an elegant way in Perl 5?

like image 575
asjo Avatar asked Sep 01 '08 19:09

asjo


3 Answers

Assuming you have exactly two lists and they are exactly the same length, here is a solution originally by merlyn (Randal Schwartz), who called it perversely perlish:

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

What happens here is that for a 10-element list, first, we find the pivot point in the middle, in this case 5, and save it in $p. Then we make a list of indices up to that point, in this case 0 1 2 3 4. Next we use map to pair each index with another index that’s at the same distance from the pivot point as the first index is from the start, giving us (in this case) 0 5 1 6 2 7 3 8 4 9. Then we take a slice from @_ using that as the list of indices. This means that if 'a', 'b', 'c', 1, 2, 3 is passed to zip2, it will return that list rearranged into 'a', 1, 'b', 2, 'c', 3.

This can be written in a single expression along ysth’s lines like so:

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

Whether you’d want to use either variation depends on whether you can see yourself remembering how they work, but for me, it was a mind expander.

like image 63
Aristotle Pagaltzis Avatar answered Oct 14 '22 07:10

Aristotle Pagaltzis


The List::MoreUtils module has a zip/mesh function that should do the trick:

use List::MoreUtils qw(zip);

my @numbers = (1, 2, 3);
my @fruit = ('apple', 'orange', 'grape');

my @zipped = zip @numbers, @fruit;

Here is the source of the mesh function:

sub mesh (\@\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
    my $max = -1;
    $max < $#$_  &&  ($max = $#$_)  for @_;

    map { my $ix = $_; map $_->[$ix], @_; } 0..$max; 
}
like image 32
Jason Navarrete Avatar answered Oct 14 '22 07:10

Jason Navarrete


I find the following solution straightforward and easy to read:

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

I believe it's also faster than solutions that create the array in a wrong order first and then use slice to reorder, or solutions that modify @a and @b.

like image 14
Frank Avatar answered Oct 14 '22 08:10

Frank