Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the fastest check digit routine for a string in Perl?

Tags:

perl

pack

unpack

Given a string of digits, I have to sum all digits as fast as possible using Perl.

My first implementation unpacks digits with unpack(), then sums the list of digits with List::Utils' sum(). It's pretty fast but is there a faster pack/unpack recipe for this task?

I tried with a pack/unpack combination, and benchmarked the two implementations. Used CPU time is almost the same; maybe is there some fast trick I'm not aware of?

Here is how I did my benchmark:

#!/usr/bin/env perl

use 5.012;
use strict;
use List::Util qw/sum/;
use Benchmark qw/timethese/;

timethese ( 1000000, {
    list_util => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = sum( unpack( 'AAAAAAAAA', $CheckDigit ) );
        } while ( $CheckDigit > 9 );
    },
    perl_only => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = unpack( '%16S*', pack( 'S9', unpack( 'AAAAAAAAA', $CheckDigit ) ) );
        } while ( $CheckDigit > 9 );
    },
} );
like image 400
Marco De Lellis Avatar asked Dec 29 '25 02:12

Marco De Lellis


1 Answers

unpack isn't the fastest way to split a string:

#!/usr/bin/env perl

use strict;
use List::Util qw/sum/;
use Benchmark qw/cmpthese/;

cmpthese ( -3, {
    list_util => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = sum( unpack( 'AAAAAAAAA', $CheckDigit ) );
        } while ( $CheckDigit > 9 );
    },
    unpack_star => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = sum( unpack( '(A)*', $CheckDigit ) );
        } while ( $CheckDigit > 9 );
    },
    re => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = sum( $CheckDigit =~ /(.)/g );
        } while ( $CheckDigit > 9 );
    },
    split => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = sum( split //, $CheckDigit );
        } while ( $CheckDigit > 9 );
    },
    perl_only => sub {
        my $CheckDigit = "999989989";
        do {
            $CheckDigit = unpack( '%16S*', pack( 'S9', unpack( 'AAAAAAAAA', $CheckDigit ) ) );
        } while ( $CheckDigit > 9 );
    },
    modulo => sub {
        my $CheckDigit = "999989989";
        $CheckDigit = ($CheckDigit+0) && ($CheckDigit % 9 || 9);
    },
} );

Produces:

                 Rate perl_only list_util       re unpack_star    split   modulo
perl_only     89882/s        --      -15%     -30%        -45%     -54%     -97%
list_util    105601/s       17%        --     -17%        -35%     -45%     -97%
re           127656/s       42%       21%       --        -21%     -34%     -96%
unpack_star  162308/s       81%       54%      27%          --     -16%     -95%
split        193405/s      115%       83%      52%         19%       --     -94%
modulo      3055254/s     3299%     2793%    2293%       1782%    1480%       --

So split looks like your best bet if you have to split the string into characters.

But repeatedly summing the digits is almost the same as taking the number mod 9 (as mirod pointed out). The difference is that $Digits % 9 produces 0 instead of 9. One formula that fixes that is ($Digits-1) % 9 + 1, but (in Perl at least) that doesn't work for the all-zeros case (it produces 9 instead of 0). An expression that works in Perl is ($Digits+0) && ($Digits % 9 || 9). The first term handles the all-zero case, the second the normal case, and the third changes 0 to 9.

like image 179
cjm Avatar answered Dec 31 '25 18:12

cjm