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

后端 未结 7 675
一向
一向 2020-12-02 15:45

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 togethe

相关标签:
7条回答
  • 2020-12-02 16:24
    my @l1 = qw/1 2 3/;
    my @l2 = qw/7 8 9/;
    my @out; 
    push @out, shift @l1, shift @l2 while ( @l1 || @l2 );
    

    If the lists are a different length, this will put 'undef' in the extra slots but you can easily remedy this if you don't wish to do this. Something like ( @l1[0] && shift @l1 ) would do it.

    Hope this helps!

    0 讨论(0)
  • 2020-12-02 16:26

    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.

    0 讨论(0)
  • 2020-12-02 16:27

    This is totally not an elegant solution, nor is it the best solution by any stretch of the imagination. But it's fun!

    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
    

    How to handle STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE is an exercise left to the reader.

    0 讨论(0)
  • 2020-12-02 16:30

    Algorithm::Loops is really nice if you do much of this kind of thing.

    My own code:

    sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), 1..@_] }
    
    0 讨论(0)
  • 2020-12-02 16:31

    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; 
    }
    
    0 讨论(0)
  • 2020-12-02 16:41

    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.

    0 讨论(0)
提交回复
热议问题