in reply to interleave (single shuffle)

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.

Replies are listed 'Best First'.
Re: Re: interleave (single shuffle)
by ysth (Canon) on Nov 27, 2003 at 02:03 UTC
    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.