home :: computers :: programming :: perl :: stepped series

Stepped Series of Numbers in Perl

In working on a Perl validation function for GTINs (recipe here), I found a need to generate a series of numbers with a step of two. For example, I in the series 1-10, I first want 1, 3, 5, 7, and 9. And then later I want 2, 4, 6, 8, 10. Here’s how I went about creating those series in my GTIN function to create hash slices:

sub isa_gtin {
    my @nums = reverse split q{}, shift;
    (
        sum( @nums[ grep {   $_ % 2  } 0..$#nums ] ) * 3
      + sum( @nums[ grep { !($_ % 2) } 0..$#nums ] )
    ) % 10 == 0;
}

But it seems wasteful to generate the series of numbers twice and to calculate whether they’re odd or even twice. Surely there’s a more efficient way to do this in Perl, perhaps even more expressive? Python seems to have a useful syntax for creating array slices that step. In Python, I’d do something like this:

  sum( nums[1:10:2] ) * 3 + sum( nums[2:10:2])

But barring such a slice feature in Perl is there some cleaner way than the ugly grep approach I created to generate a stepped series in Perl?

Comments & Trackbacks

Chris Dolan wrote:

map?

Surely a map would suffice? Untested code below

sub isa_gtin {
    my @nums = reverse split q{}, shift;
    (
        sum( @nums[ map { $_ * 2 + 1 } 0..$#nums/2 ] ) * 3
      + sum( @nums[ map { $_ * 2     } 0..$#nums/2 ] )
    ) % 10 == 0;
}

Adrian Howard wrote:

List::[More]Util[s]?

use List::MoreUtils qw( part );
use List::Util qw( sum );

my ( $even, $odd ) = part { $_ % 2 } 1 .. 10;
print sum( @$odd ) * 3 + sum ( @$even );

Theory wrote:

grep vs. map vs. part

Chris and Adrian,

Thanks for your suggestions. I should have known that List::MoreUtils would have something of interest. And Chris, your version worked, too, once I added no warnings 'unintialized';. So just out of curiosity, I benchmarked all three approaches (N = 100_000):

grep: 3.40 wallclock seconds (3.40 usr + 0.00 sys = 3.40 CPU) @ 29390.86/s
map:  3.32 wallclock seconds (3.31 usr + 0.01 sys = 3.32 CPU) @ 30110.48/s
part: 3.92 wallclock seconds (3.59 usr + 0.30 sys = 3.89 CPU) @ 25535.60/s

So it looks like map wins by a nose, although they're all really fast. The figures, since it's also the most obscure. ;-)

—Theory

Benjamin Franz wrote:

One sum to rule them....

I realize this thread is a couple of months old. But I just couldn't resist the challenge:

use List::Util qw( sum );

sub isa_gtin {
    use integer;
    my @nums =  split //, reverse shift;
    0 == sum(
        map { $nums[$_ << 1] + $nums[1 + ($_ << 1)] * 3 } 0..$#nums >> 1
    ) % 10;
}

Theory wrote:

Re: One sum to rule them…

Jerry,

I had to add no warnings 'uninitialized', but otherwise, not bad!:

 grep: 0.30 wallclock seconds (0.30 usr + 0.00 sys = 0.30 CPU) @ 33894.05/s
  map: 0.28 wallclock seconds (0.28 usr + 0.00 sys = 0.28 CPU) @ 35583.90/s
 part: 0.32 wallclock seconds (0.31 usr + 0.01 sys = 0.32 CPU) @ 31325.57/s
shift: 0.25 wallclock seconds (0.24 usr + 0.00 sys = 0.24 CPU) @ 40695.90/s

The fastest by a nose! I really should learn about the bit operators and how they actually work!

—Theory

Powered by KinoSearch