Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

What is the most efficient (or most elegant, for that matter) way of interleaving lists in Perl? For example, let's say I have two lists:

( 'blorke', 'gromble', 'poomba' ) and ( 'snork', 'dumble', 'gronke' ). I'd like to end up with:

( 'blorke', 'snork', 'gromble', 'dumble', 'poomba', 'gronke' )

In my case, I'm filling a hash, but I can think of other reasons to do this as well.

I'd prefer if it were self-contained (i.e. had no need for temporary or index variables).

One obvious solution:

my @list1 = ( 'blorke', 'gromble', 'poomba' ); my @list2 = ( 'snork', 'dumble', 'gronke' ); my %index = map { $_ => pop @list2 } (@list1);

...but that's destructive of @list2 (and I rather feel like it violates the spirit of map ... should probably have used for). Are there better (maybe non-destructive) ways?

Replies are listed 'Best First'.
Re: interleaving lists
by chipmunk (Parson) on Jan 23, 2001 at 02:52 UTC
    For filling a hash, assignment to a hash slice is probably the best way:
    my %index; @index{@list1} = @list2;
    If you actually want a list as the result, rather than a hash, a for loop would work:
    for (my $i = 0; $i <= $#list1; $i++) { push @result, $list1[$i], $list2[$i]; }
    The push could also be done as a nested loop, if there are lots of arrays to interleave.

    Anyway, that's a very basic solution; much fancier solutions could be written to satisfy more requirements for the interleaving.

Re (tilly) 1: interleaving lists
by tilly (Archbishop) on Jan 23, 2001 at 05:34 UTC
    This will handle any number of lists at once without destroying the originals. Pass them in by reference and it will return by reference or value, your choice.
    sub interleave { my @lists = map [@$_], @_; my @res; while (my $list = shift @lists) { if (@$list) { push @res, shift @$list; push @lists, $list; } } wantarray ? @res : \@res; }
(tye)Re: interleaving lists
by tye (Sage) on Jan 23, 2001 at 04:32 UTC
Re: interleaving lists
by Fastolfe (Vicar) on Jan 23, 2001 at 05:15 UTC
    I might do it:
    @combined = map { $list1[$_], $list2[$_] } 0 .. ($#list1 > $#list2 ? $#list1 : $#list2);
    This inserts 'undef's in the event one array is longer than the other.
      Ahhh, elegant. Thanks, this is exactly what I was looking for.
        Of course if you're filling a hash, it's already been mentioned that this is probably the best solution:
        my %hash; @hash{@keys} = @values;
      Nice!
Re: interleaving lists
by Coyote (Deacon) on Jan 23, 2001 at 03:13 UTC
    I'm sure there better ways of doing this, but ...

    use strict; use warnings; my @array_1 = qw(blorke gromble poomba); my @array_2 = qw(snork dumble gronke); my (@array_3); foreach (@array_1){ push @array_3, $_, shift(@array_2); } foreach (@array_3){ print "$_\n"; }
    This yields:

    blorke
    snork
    gromble
    dumble
    poomba
    gronke
    

    I am assuming here that your arrays are going to be the same size. You might want to check the size first. This also kills @array_2, so work on a copy if you need to keep the original.

    ----
    Coyote

Re: interleaving lists
by InfiniteSilence (Curate) on Jan 23, 2001 at 19:44 UTC
    Hey, how about this:

    perl -e "@m=(1,2,3); @s=(4,5,6,7,8); for (0 ..scalar(@m + @s)) {print +$m[$_], $s[$_] };"
    Prints out: 14253678

    Celebrate Intellectual Diversity