Interleaves two lists to form a shuffled list. Use like %hash = interleave(keys, values) (Update: thanks to all for the feedback. renamed and retitled as interleave, removed explicit arrays from example.)
sub interleave { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), 1..@_] }

Replies are listed 'Best First'.
Re: perfect shuffle
by Zaxo (Archbishop) on Nov 23, 2003 at 08:47 UTC

    It took me a while to work out what you were getting at here. I'd call this interleave, not shuffle. The two arrays as arguments are a blind: they may or may not have the same number of elements. If warnings are active, they should add to an even number of elements.

    This is terribly obfuscated, if it works at all. What exactly do you mean it to do?

    Update: The Storm King reminds me that 'perfect shuffle' is the term for a uniform distribution of order. Not what your function produces.

    After Compline,
    Zaxo

      I believe a magician or card cheat would call it a perfect shuffle.

      I agree, however, that it should be given a different name in this context. I'd also prefer if the code was clearer.

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: perfect shuffle
by bart (Canon) on Nov 23, 2003 at 08:58 UTC
    I think this may be a bit more understandable (as it uses one fewer tricks):
    sub zip { my $half = @_>>1; return @_[map { ($_&1 && $half) + ($_>>1) } 0 .. $#_]; }
    or
    sub zip { my $half = @_>>1; return @_[map { $_, $_+$half } 0 .. $half-1]; }

    p.s. A common name for this kind of functionality is "zip". See Language::Functional for an implementation. SO I changed the name for the function to reflect this.

Re: perfect shuffle
by duff (Parson) on Nov 23, 2003 at 17:54 UTC

    You might want to change the description to be slightly more accurate. I mean, what it really does is take one list, halves it, then interleaves the halfs. Sure you can give it two arrays or lists as arguments, but it's not going to do the right thing if the they are different sizes (at least not what I would expect given the description).

Re: interleave (single shuffle)
by Roger (Parson) on Nov 24, 2003 at 01:22 UTC
    Very nice trick. :-)

    But no error checking though, it assumes that both arrays are of the same size, as many monks have pointed out already. (I know that you only did it for fun and in real life would not do without error checkings.) Personally I would favour a more conservative approach like the following -
    use strict; use Data::Dumper; my @array1 = qw/ a b c d e /; my @array2 = qw/ 1 2 3 4 5 /; my $hash = build_hash(\@array1, \@array2); print Dumper($hash); # ----------------------------------------------------------- =pod =head1 FUNCTION sub build_hash ( \@array1, \@array2 ) =head1 SYNOPSIS my $hash = build_hash ( \@array1, \@array2 ); =head1 DESCRIPTION The function 'build_hash' takes 2 array references and returns reference to a hash. The hash is build by taking the elements from the first array as keys, and the corresponding elements from the second array as values. =cut # ----------------------------------------------------------- sub build_hash { my ($array1, $array2) = @_; return undef if $#$array1 ne $#$array2; # return \%{{map{$array1[$_]=>$array2[$_]}0..$#$array1}}; my %hash; # thanks ysth, yes I knew something was not right # with my map, I just couldn't remember the syntax # in the morning. :-) @hash{@$array1} = @$array2; return \%hash; }
      The building a hash was just an example, not the point of the function. For the function in general, no error checking is possible because there are no error conditions. Every input has a well-defined output. For instance, interleave() returns an empty list, interleave('a'..'e') returns ('a','d','b','e','c').

      Given actual arrays, I would be more likely to say:

      %h = (); @h{@keys} = @values;
Re: interleave (single shuffle)
by Aristotle (Chancellor) on Nov 26, 2003 at 11:32 UTC
    Nice, but why the bit ops? This isn't assembler.
    sub interleave { @_[ map $_ % 2 ? $_ / 2 : ( $_ / 2 ) + ( $#_ / 2 ), 1 + .. @_ ] }
    Now there are several things that irk me. One is the duplication of $_ / 2. The other is 1 .. @_ which surprised me for 1/10th of a second.
    sub interleave { @_[ map $_ / 2 + ( $_ % 2 ? $#_ / 2 : 0 ), 0 .. $#_ ] + }
    Now IMHO it's much easier to see what's going on. But I'd write it differently:
    sub interleave { @_[ map +( $_, $_ + @_ / 2 ), 0 .. $#_ / 2 ] }

    That gets us rid of the constant evaluation of a condition and reduces the number of iterations by half. You might want to move the @_ / 2 invariant out of the loop as a final optimization. The inner loop is now extremly simple: two additions.

    This is faster than your cryptic bit ops version and still clear as day to read. Don't microoptimize, pick a good algorithm first. You'll always get more readable as well as faster code.

    Makeshifts last the longest.

      Nice, but why the bit ops? This isn't assembler. sub interleave { @_[ map $_ % 2 ? $_ / 2 : ( $_ / 2 ) + ( $#_ / 2 ), 1 .. @_ ] }
      Perl is not assembler, only assembler code uses bit ops, ergo perl code never uses bit ops? There are as many valid subsets of Perl as there are Perl programmers, and then some. Every programmer should use the parts with which he/she is familiar, with due allowance for whatever audience, coworkers, etc. exist. Here at perlmonks, I'm only likely to veer from my "native dialect" when answering a newbie's question.

      To more directly address your question, this is an integer transformation problem and bit ops are part of my mental toolbox for that kind of problem. It didn't even occur to me to use /2 instead. Note that my code produces indices 0,5,1,6,2,7,3,8,4,9 for a list of 10, while your first version produces 0.5,5.5,1.5,6.5,2.5,7.5,3.5,8.5,4.5,9.5. They are equivalent, since array subscripts are converted to int, and your version is just as good as mine, but I was thinking only of integer operations and your thinking was more unbounded.

      Now there are several things that irk me. One is the duplication of $_ / 2. The other is 1 .. @_ which surprised me for 1/10th of a second. sub interleave { @_[ map $_ / 2 + ( $_ % 2 ? $#_ / 2 : 0 ), 0 .. $#_ ] }
      I agree that 0 .. $#_ may be a little clearer. (Side thought: how about @_[ sort { ... } 0..$#_ ].) But you've introduced an error when there are an odd number of parameters. Try @_/2 instead. Also see Bart's version where he has a&&b instead of a?b:0.
      IMHO it's much easier to see what's going on. But I'd write it differently: sub interleave { @_[ map +( $_, $_ + @_ / 2 ), 0 .. $#_ / 2 ] } That gets us rid of the constant evaluation of a condition and reduces the number of iterations by half. You might want to move the @_ / 2 invariant out of the loop as a final optimization. The inner loop is now extremly simple: two additions. This is faster than your cryptic bit ops version and still clear as day to read. Don't microoptimize, pick a good algorithm first. You'll always get more readable as well as faster code.
      Thanks, that is a fair amount faster. But again, I wasn't actually trying to optimize, and your last example only works for an even number of parameters.

        Yes, bit ops are available in Perl, but I don't find them a natural way to express this particular problem. It'd've taken a lot more effort to figure out your code if I hadn't used them extensively in assembler, back in the day. Just about every use of bit ops I've ever seen obscures rather than clarifies what's going on. In nearly all cases the motivation is microoptimization. That's what I got the impression was what you were trying to do, from the looks of your code.

        As for the other points: apologies, I did more than just replace the bit ops by equivalents and didn't test sufficiently. I even wrote a note that the code probably contains off-by-one errors, but then tested it a bit and it seemed to be ok. The two issues are orthogonal though, it's the other changes in the structure I made that introduced the problems, not the bit op replacement.

        As for performance in general, I think it would be worthwhile to do this in XS and stick it into List::Util.

        Makeshifts last the longest.