PWC162 - ISBN-13

TL;DR

Here we are with TASK #1 from The Weekly Challenge #162. Enjoy!

The challenge

Write a script to generate the check digit of given ISBN-13 code. Please refer wikipedia for more information.

Example

ISBN-13 check digit for '978-0-306-40615-7' is 7.

The questions

I’m assuming that no validation of the inputs have to be done, like making sure that there are 12+1 digits, separators are in the right place, etc.

The solution

Let’s first reshuffle the formula for calculating the goal value:

\[x_{13} = (10 - ((x_1 + 3 x_2 + x_3 + 3 x_4 + ... + x_{11} + 3 x_{12}) \mod{10})) \mod{10} \\ x_{13} = (- (x_1 + 3 x_2 + x_3 + 3 x_4 + ... + x_{11} + 3 x_{12})) \mod{10} \\ x_{13} = (-x_1 - 3 x_2 - x_3 - 3 x_4 + ... - x_{11} - 3 x_{12}) \mod{10} \\ x_{13} = \sum_{i = 1}^{6} (-x_{2i-1} - 3 x_{2i}) \mod{10}\]

So we can take pairs of consecutive digits and address them as in the parenthesis in the summation above.

Let’s start Raku:

#!/usr/bin/env raku
use v6;
sub MAIN (Str:D $input = '978-0-306-40615-7') {
   put "ISBN-13 check digit for '$input' is {isbn_13($input)}.";
}

sub isbn_13 ($input) {
   $input.comb(/\d/)[0..11]    # focus on first 12 digits
      .map({-$^a - 3 * $^b})   # apply equivalent weights
      .sum % 10;               # sum and take remainder
}

We’re concentrating on the first 12 digits, so the comb gets the digits and the slice [0..11] takes the first 12 of them. The following map/sum tandem implements the formula seen above.

Let’s go Perl now, trying to show some Raku-ish dialect!

#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use List::Util qw< pairmap sum >;

my $input = shift // '978-0-306-40615-7';
say "ISBN-13 check digit for '$input' is @{[isbn_13($input)]}.";

sub isbn_13 ($input) {
   sum(
      pairmap { -$a - 3* $b }
      ($input =~ m{(\d)}gmxs)[0 .. 11]
   ) % 10;
}

Function pairmap only appeared in version 1.29 of List::Util, but we are OK with the constraint on perl’s version which appeared three years later (give or take a couple of months). It allows us to replicate the map taking two values at a time behaviour that comes stock in Raku, so why not?

The other rakuism is in the embedding of the call to isbn_13 directly into the string:

say "ISBN-13 check digit for '$input' is @{[isbn_13($input)]}.";
#                                        ^^^^^^^^^^^^^^^^^^^^

This is hackish in Perl, although it also funnily gets an aura of secret operator (apparently, it’s the Babycart operator).

Stay safe!


Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!