21
\$\begingroup\$

Introduction

In this challenge your task is to generate the ISBN-10 code for books given its ISBN-13 code, assuming that such a code exists. Such an ISBN-13 code consists of several parts separated by -:

978-GG-PPPP-TTT-C

The letters G (group), P (publisher), T (title) and C (checksum) all stand for one digit. For the purpose of this challenge the grouping and the computation of C (see this challenge) are not interesting and we'll drop all hyphens to make this task simpler.

An ISBN-10 number has a very similar layout:

GG-PPPP-TTT-c

The letters G,P and T are the same as for the 13 digits ISBN, however c is different (and is computed using a different algorithm). The digit c is chosen in a way such that the following equivalence holds (digits in order):

10*G + 9*G + 8*P + … + 3*T + 2*T + 1*c = 0 (mod 11)

Example

Let us consider the ISBN number 9780345391803: To get its corresponding ISBN-10 code we simply drop the leading 978 and the checksum 3 yielding 034539180.

Next we need to compute the new checksum:

10*0 + 9*3 + 8*4 + 7*5 + 6*3 + 5*9 + 4*1 + 3*8 + 2*0 = 185

The next number divisible by 11 is 187, so the new checksum is 2 and thus the resulting ISBN-10 code 0345391802.

Rules

  • Your input will always have a corresponding ISBN-10 number (ie. it is exactly 13 digits long and starts with 978)
  • The input doesn't necessarily have to be a valid ISBN-13 (eg. 9780000000002)
  • You're guaranteed that the resulting ISBN won't end with X
  • You may take input as an integer or string (with or without hyphens) however a precomputed list of digits are not allowed
  • Your output must be a valid ISBN-10 number (with or without hyphens)
  • Your output may be an integer or string (again no lists of digits)

Testcases

9780000000002 -> 0000000000
9780201882957 -> 0201882957
9781420951301 -> 1420951300
9780452284234 -> 0452284236
9781292101767 -> 1292101768
9780345391803 -> 0345391802

Note the leading zeroes!

\$\endgroup\$
  • 5
    \$\begingroup\$ It doesn't affect solutions at all, but just for the sake of being That Guy, your description of how the parts of an ISBN (either -10 or -13) are separated is incorrect. The registration group element is variable length, and the number of digits for subsequent parts can vary between, and within, registration groups. For example, in both 0-684-84328-5 and 99921-58-10-7, the first part (0 and 99921 respectively) is the registration group, the second part is the publisher, and so on. \$\endgroup\$ – Jordan Jan 19 '18 at 19:52
  • 5
    \$\begingroup\$ 10/10 sample ISBN choices \$\endgroup\$ – Jakob Mar 24 '18 at 16:01

22 Answers 22

10
\$\begingroup\$

Retina,  44  39 28 bytes

>,L3,-2`.+
.
$.>`**
_{11}

_

Try it online!

Explanation

Time to show off some new Retina features. :)

>,L3,-2`.+

We match the entire input with .+, return that match L, but select only the characters 3 (zero-based) to -2 (penultimate), inclusive. We also print the result without a trailing linefeed (>).

Now, subtracting things in Retina is a bit annoying. But luckily, we're working modulo 11, so we can just invert the coefficients of the linear combination (mod 11) and add everything up. In other words, if the constraint is:

10*G + 9*G + 8*P + … + 3*T + 2*T + 1*c = 0 (mod 11)

then we get:

c = 1*G + 2*G + 3*P + … + 8*T + 9*T (mod 11)

That simplifies things a lot here:

.
$.>`**

We replace each character with that thing at the bottom. * is Retina's repetition operator. It's right-associative and it has implicit operands $& on the left and _ on the right, so the substitution is actually short for $.>`*$&*_. $&*_ creates a string of d underscores, where d is the digit we're currently replacing. Then $.>` is the length of the string up to and including the match.1 Hence, the entire expression results in a unary representation of the nth term of our linear combination.

_{11}

Doing the actual modulo is trivial in unary: we just drop all complete sets of 11 underscores.

_

Finally, we count how many underscores remain and print the result, which completes the ISBN-10.


1 How does $.>` give the length of the string up to and including the match? You might be familiar with $` in regex substitutions, which gives you the string up to (but excluding) the match. By inserting a >, we can shift the context of the $` to the separator between the current match and the next (which is an empty string between the current digit and the next). That separator's $` will include the current match. So $>` is a shorter way to write $`$&. Finally, for all $x-type substitution elements, Retina lets you insert a . after the $ to get its length.

\$\endgroup\$
  • \$\begingroup\$ What is this modulo 11 magic?! That'll save me 4 bytes... but I don't gettit! \$\endgroup\$ – streetster Jan 19 '18 at 21:32
  • 1
    \$\begingroup\$ @streetster Basically, -2 ≡ 9 (mod 11) (because adding or subtracting 11 from a number doesn't change its "value" in the congruence class mod 11). And addition and multiplication respects congruence classes, so you can replace any value in a linear combination with an equivalent value under the current modulo. The reason I'm talking about negative numbers is really just that I've rearranged the equation to have c on one side and all the other terms (as negatives) on the other. \$\endgroup\$ – Martin Ender Jan 19 '18 at 21:36
  • \$\begingroup\$ I think I get it now. So you move c over to become -c = ... and rather than multiplying by 10 9 8... you subtract 11 from each to get -1 -2 -3... and then multiply everything by -1 to get c. \$\endgroup\$ – streetster Jan 19 '18 at 21:50
  • \$\begingroup\$ Would you mind explaining why the final stage is only replacing the trailing underscores? I've spent a while trying to figure out what is causing that but I can't seem to reproduce it. This update looks amazing by the way, nice job! \$\endgroup\$ – FryAmTheEggman Jan 20 '18 at 3:18
  • 1
    \$\begingroup\$ @FryAmTheEggman Thanks :) At that point, the string contains only underscores. We've already printed the first nine digits in the first stage. \$\endgroup\$ – Martin Ender Jan 20 '18 at 7:33
6
\$\begingroup\$

05AB1E, 17 15 13 12 bytes

¦¦¦¨DSƶO11%«

Try it online!

Explanation

¦¦¦            # remove the first 3 characters
   ¨           # remove the last character
    D          # duplicate
     S         # split to list of digits
      ƶ        # multiply each by its 1-based index
       O       # sum
        11%    # mod by 11
           «   # concatenate
\$\endgroup\$
5
\$\begingroup\$

PowerShell, 96 84 bytes

$y=-(([char[]]($x="$args"-replace'^978|.$')|%{--$a*[int]"$_"})-join'+'|iex)%11;$x+$y

Try it online!

Takes input "$args", does a regex -replace to get only the pertinent part, stores that into $x as a string. Then we cast that as a char-array and loop through each letter. Inside the loop, we pre-decrement $a (which defaults to 0) and multiply out according to the checksum calculation. Note the cast to int, else this would use ASCII values.

We then -join those numbers together with + and pipe that to iex (Invoke-Expression and similar to eval). We take that %11 and store that checksum into $y. Finally, we string concatenate $x + $y, and leave that on the pipeline. Output is implicit.

Saved 12 bytes thanks to Emigna.

\$\endgroup\$
  • \$\begingroup\$ I don't really know powershell, but I think something like this can work for 84 \$\endgroup\$ – Emigna Jan 19 '18 at 19:24
  • \$\begingroup\$ @Emigna Yes, of course. Modulus arithmetic and my brain don't play nicely. \$\endgroup\$ – AdmBorkBork Jan 19 '18 at 19:32
5
\$\begingroup\$

Octave, 46 41 39 37 bytes

@(a)[c=a(4:12) 48+mod(7+c*(1:9)',11)]

Try it online!

The code takes input as a string, and returns a string.

The code breaks down as follows:

@(a) creates an anonymous function.

With [c=a(4:12) ... ] we extract the characters that form the main code, saving a copy to c for later use, and adding another copy to the final output string.

Based on @MartinEnter's clever way of swapping 10:-1:2 into 1:10, we can easily generate that range and transpose it to get a column vector. c*(1:10)' does array multiplication of the row vector c and range column vector. This is equivalent to doing an element-wise multiplication then summing.

The checksum would normally be mod(11-sum,11) to calculate the number required for the sum to be a multiple of 11. However, because c was a character string, the sum will actually be larger than it should be by 2592 (48*54) because we multiplied by numbers that were 48 larger than the actual value.

When we perform the modulo, it will automatically get rid of all but 7 of that 2592. As such, and accounting for the negation of the range, the actual calculation becomes 48+mod(7+sum,11). We add on 48 to the result to convert back to an ASCII character.

The checksum character is appended to the end of the result, and the value returned.

\$\endgroup\$
5
\$\begingroup\$

Jelly, 12 bytes

ṫ4ṖȮV€xJS%11

This is a full program that uses strings for I/O.

Try it online!

How it works

ṫ4ṖȮV€xJS%11  Main link. Argument: s (string of length 13)

ṫ4            Tail 4; discard the first three characters.
  Ṗ           Pop; discard the last characters.
   Ȯ          Output; print the result to STDOUT and return it.
    V€        Eval each; turn digit characters into digits.
       J      Indices; yield [1, ..., 13].
      x       Repeat the first digit once, the second digit twice, etc.
        S%11  Take the sum, modulo 11.
              (implicit) Print the checksum to STDOUT.
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 59 56 bytes

s=>(s=s.slice(3,-1))+[...s].reduce(n=>n+s[i++]*i,i=0)%11

f=
s=>(s=s.slice(3,-1))+[...s].reduce(n=>n+s[i++]*i,i=0)%11
//s=>(s=s.slice(3,-1))+(n=0,[...s].map((c,i)=>n+=c*-~i),n)%11
//s=>(s=s.slice(3,-1))+(n=0,[...s].map(c=>n+=c*i++,i=1),n)%11
//s=>(n=0,[...(s=s.slice(3,-1))].map((c,i)=>n+=c*-~i),s+n%11)
//s=>(s=s.slice(3,-1,n=i=0))+eval(`for(c of s)n+=c*-~i++`)%11

console.log(...[
  '9780000000002',
  '9780201882957',
  '9781420951301',
  '9780452284234',
  '9781292101767',
  '9780345391803'
].map(f))

-3 bytes thanks to @Shaggy's suggestion.

\$\endgroup\$
  • \$\begingroup\$ 57 bytes? \$\endgroup\$ – Shaggy Jan 21 '18 at 21:02
  • 1
    \$\begingroup\$ Or maybe even 56 bytes. \$\endgroup\$ – Shaggy Jan 21 '18 at 21:05
  • \$\begingroup\$ So why not input as array of digits? 54 bytes \$\endgroup\$ – tsh Sep 12 '18 at 4:04
3
\$\begingroup\$

Perl 5, 49 + 1 (-p) = 50 bytes

s/^...|.$//g;$\=(eval s/./"-$&0+".$i++*$&/ger)%11

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Pyth, 16 bytes

%s.e*ksbpP>Q3hT

Try it here!

Pyth, 17 bytes

%s*VsMKpP>Q3SlK11

Try it here!

Explanation

%s.e*hksbpP>Q3hT || Full program. Uses string for input and output.

            Q    || The input.
           > 3   || With elements at indexes smaller than 3 trimmed.
          P      || Pop (remove the last item).
         p       || Print the result without a linefeed, but also return it.
  .e             || Enumerated map. For each (index, value), assign two variables (k, b).
       sb        || b converted to an integer.
    *hk          || And multiplied by k + 1.
 s               || Summation.
%                || Modulo by:
               T || The literal 10.
              h  || Incremented by 1.
\$\endgroup\$
3
\$\begingroup\$

Japt, 16 15 bytes

Came up with this down the pub the other night and forgot all about it.

s3J
U+¬x_*°TÃuB

Try it

\$\endgroup\$
  • \$\begingroup\$ Think you can save a byte with s3J and U+¬x_*°TÃuB \$\endgroup\$ – ETHproductions Jan 22 '18 at 15:54
  • \$\begingroup\$ Weird; could've sworn I'd tried that. Thanks, @ETHproductions. \$\endgroup\$ – Shaggy Jan 22 '18 at 15:57
  • \$\begingroup\$ Wait, no, I'd forgotten the U - D'oh! \$\endgroup\$ – Shaggy Jan 22 '18 at 16:35
3
+50
\$\begingroup\$

Hexagony, 77 61 bytes

,,,,'~'11=\.A&.=\./';"-'"{4.8}}\'.A.>.,<\'+'%!@}/=+'+{./&{{&/

Try it online!


Colored:


Here is a larger version. There are some path crossings, but because all of those cells are . (no-op in Hexagony), you don't need to worry about them:

(I also tried to keep the old mirrors, but sometimes I need to change something)

The linear command executed is:

,,,,'48}}
,
while memory > 0:
    ';"-'"{+'+{=A&=''A
    if memory < 0:
        undefined behavior
    &{{&}
    ,
'"''+~'11='%!@

Explanation: Instead of keeping a counter and do multiplication at each digit, this program:

  • keep a "partial sum" variable and "total sum" variable (p and t)
  • for each digit read: add it to partial sum, and add the partial sum to total sum.
  • print (-p-t)%11, where % always return positive results.
\$\endgroup\$
3
\$\begingroup\$

K (oK), 29 25 24 23 bytes

Solution:

x,$11!7+/(1+!9)*x:-1_3_

Try it online!

Examples:

x,$11!7+/(1+!9)*x:-1_3_"9780000000002"
"0000000000"
x,$11!7+/(1+!9)*x:-1_3_"9780345391803"
"0345391802"
x,$11!7+/(1+!9)*x:-1_3_"9781292101767"
"1292101768"
x,$11!7+/(1+!9)*x:-1_3_"9780452284234"
"0452284236"

Explanation:

Evaluation is performed right-to-left.

Two tricks taken from other solutions:

  • multiply by 1 2 3... instead of 10 9 8...
  • multiply ASCII values and then add 7 to the sum to balance out

Breakdown:

x,$11!7+/(1+!9)*x:-1_3_ / the solution
                     3_ / drop three items from the start
                  -1_   / drop one item from the end
                x:      / save this as variable x
               *        / multiply by
         (    )         / all this together
            !9          / til, !9 => 0 1 2 3 4 5 6 7 8
          1+            / add 1 => 1 2 3 4 5 6 7 8 9
      7+/               / sum (+) over (/), start from 7
   11!                  / mod by 11
  $                     / convert back to a string
x,                      / join with x

Notes:

  • -4 bytes thanks to Martin Enders's "just invert the coefficients" magic.
  • -1 byte thanks to Tom Carpenter for removing the need to convert to integers (by adding 7 to the sum)
  • -1 byte start the accumulator at 7
\$\endgroup\$
3
\$\begingroup\$

C (gcc), 96 95 87 86 85 bytes

(-1 thanks to ceilingcat)

*f(s,r,c,d)char*s,*r;{for(d=13;--d;s+=*++s<48)r=d>8?c=3,s:r,c-=~d**s;*s=58-c%11;s=r;}

Try it online!

To be called as f(s), where s is a pointer to the first element of a modifiable character array. Modifies the input array, returns a pointer into the input array.

\$\endgroup\$
2
\$\begingroup\$

Python 2, 62 bytes

lambda n:n[3:12]+`-sum(i*int(n[13-i])for i in range(2,11))%11`

Try it online!

Takes a string as input; and outputs a string.

\$\endgroup\$
2
\$\begingroup\$

Jelly, 14 bytes

ṫ4ṖµV€×JS%11;@

Test suite!

Jelly, 17 bytes

Ṛḣ⁵µVD×JḊSN%11;ḊṚ

Try it online! or Test suite!

Takes input and outputs as a string.

\$\endgroup\$
2
\$\begingroup\$

ECMAScript 6, 86 67 bytes

a=>(c=a.substr(3,9))+([...c].map(v=>g+=--i*v,e=i=g=11)?(e-g%e)%e:0)

Try it online!


Thanks for Arnauld's comment, switched from reduce to map and got rid of return keyword.

\$\endgroup\$
  • 3
    \$\begingroup\$ Welcome to PPCG! Answers must be either full programs or callable functions (although they may be unnamed functions), not just snippets. I believe the shortest option in JavaScript is usually an unnamed lambda. \$\endgroup\$ – Martin Ender Jan 19 '18 at 22:46
  • \$\begingroup\$ @MartinEnder thanks, I have edited my answer \$\endgroup\$ – Kos Jan 19 '18 at 22:57
  • 3
    \$\begingroup\$ Welcome on board! Some tips: Variable initialization can usually be put as extra parameters of map(), reduce() etc. With some additional rewriting, it's often possible to get rid of {} and return. Also, in this particular case, map() is probably shorter than reduce(). (Here is a 65-byte version.) \$\endgroup\$ – Arnauld Jan 19 '18 at 23:09
  • \$\begingroup\$ I'm pretty sure f= isn't necessary. Also, you can initialize c at the spread for something like this: a=>{i=10;s=[...c=a.substr(3,9)].reduce((g,v)=>+g+(i--)*v,0)%11;return c+=s?11-s:0} (-4 bytes) \$\endgroup\$ – Asone Tuhid Jan 20 '18 at 12:57
2
\$\begingroup\$

Retina 0.8.2, 72 51 bytes

...(.*).
$1¶$1
r`.\G
$&$'
r`.\G
$*
1{11}

¶(1*)
$.1

Try it online! Because I haven't learned Retina 1.0 yet. Explanation:

...(.*).
$1¶$1

Delete the unwanted characters and make a second copy of the appropriate digits.

r`.\G
$&$'

Suffix each digit in the second copy with its suffix. This effectively repeats each digit in the suffix by its position.

r`.\G
$*

Convert the digits in the second copy to unary thus adding them together.

1{11}

Reduce modulo 11. (There are only 9 digits in the first copy, so this can never affect it.)

¶(1*)
$.1

Convert the result back to decimal and remove the newline again.

\$\endgroup\$
2
\$\begingroup\$

APL (Dyalog Unicode), 26 24 bytes

∊⍕¨(⊢,11|⊢+.×⍳∘≢)3↓¯1↓⍎¨

Try it online!

Tacit prefix function. Takes input as string.

2 bytes saved thanks to @ngn.

How?

∊⍕¨(⊢,11|⊢+.×⍳∘≢)3↓¯1↓⍎¨    ⍝ Main function.
                       ⍎¨    ⍝ Execute each; turns the string into a vector of digits.
                 3↓¯1↓       ⍝ Drop (↓) the last (¯1) and the first 3 digits.
   (           ≢)            ⍝ Tally; returns the number of digits in the vector.
             ⍳∘               ⍝ Then (∘) index (⍳) from 1
            ×                ⍝ Multiply the resulting vector [1..9]
         ⊢+.                 ⍝ Dot product with sum with the original vector;
                             ⍝ This will multiply both vectors, and sum the resulting vector.
      11|                    ⍝ Mod 11
     ,                       ⍝ Concatenate
    ⊢                        ⍝ With the original vector
 ⍕¨                          ⍝ Format each; returns a vector of digits as strings.
∊                            ⍝ Flatten to get rid of the spaces.
\$\endgroup\$
1
\$\begingroup\$

Clean, 104 102 98 bytes

import StdEnv
$s#s=s%(3,11)
#i=sum[digitToInt d*p\\d<-s&p<-[10,9..]]+10
=s++[toChar(i/11*11-i+58)]

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Kotlin, 83 bytes

i.drop(3).dropLast(1).let{it+(11-(it.mapIndexed{i,c->(10-i)*(c-'0')}.sum()%11))%11}

Beautified

i.drop(3).dropLast(1).let {
    it + (11 - (it.mapIndexed { i, c -> (10 - i) * (c - '0') }.sum() % 11)) % 11
}

Test

data class Test(val input: String, val output: String)

fun f(i: String) =

i.drop(3).dropLast(1).let{it+(11-(it.mapIndexed{i,c->(10-i)*(c-'0')}.sum()%11))%11}

val tests = listOf(
        Test("9780000000002", "0000000000"),
        Test("9780201882957", "0201882957"),
        Test("9781420951301", "1420951300"),
        Test("9780452284234", "0452284236"),
        Test("9781292101767", "1292101768"),
        Test("9780345391803", "0345391802")
)

fun main(args: Array<String>) {
    for (c in tests) {
        val answer = f(c.input)
        val good = answer == c.output
        println("$good ${c.input} -> ${c.output} | $answer")
    }
}

TIO

TryItOnline

\$\endgroup\$
1
\$\begingroup\$

Ruby, 69 68 64 bytes

->n{v=n[3,9];c=11;v+"#{(c-v.chars.map{|i|i.to_i*c-=1}.sum)%11}"}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

PHP, 64 bytes

Unfortunately, in PHP (-$c)%11 is the same as -($c%11); so I have to get the difference to at least the largest sum possible (55*9 = 495 = 45*11) instead of just using -$c%11.

for($f=11;--$f>1;print$d)$c+=$f*$d=$argn[13-$f];echo(495-$c)%11;

or

for($c=45*$f=11;--$f>1;print$d)$c-=$f*$d=$argn[13-$f];echo$c%11;

Run as pipe with -nR or try them online.

\$\endgroup\$
0
\$\begingroup\$

Java 10, 110 bytes

l->{var s=l+"";int c=0,i=3;for(;i<12;)c+=(13-i)*(s.charAt(i++)-48);return(l-(long)978e10)/10*10+(11-c%11)%11;}

Takes input and outputs as a long integer. Try it online here.

Ungolfed version:

l -> { // lambda taking a long as argument
    var s = l + ""; // convert the input to a String
    int c = 0, // the check digit
    i = 3; // variable for iterating over the digits
    for(; i < 12 ;) // go from the first digit past 978 to the one before the check digit
        c += (13 - i) * (s.charAt(i++) - 48); // calculate the check sum
    return (l - (long) 978e10) // remove the leading 978
           /10 *10 // remove the original check digit
           + (11 - c % 11) % 11; // add the new check digit
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.