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 );
},
} );
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With