ISBN13 check digit

From Rosetta Code
Task
ISBN13 check digit
You are encouraged to solve this task according to the task description, using any language you may know.
Task

Validate the check digit of an ISBN-13 code:

  •   Multiply every other digit by  3.
  •   Add these numbers and the other digits.
  •   Take the remainder of this number after division by  10.
  •   If it is  0,   the ISBN-13 check digit is correct.


You might use the following codes for testing:

  •   978-0596528126       (good)
  •   978-0596528120         (bad)
  •   978-1788399081       (good)
  •   978-1788399083         (bad)


Show output here, on this page


See also



11l

Translation of: Python
F is_isbn13(=n)
   n = n.replace(‘-’, ‘’).replace(‘ ’, ‘’)
   I n.len != 13
      R 0B
   V product = sum(n[(0..).step(2)].map(ch -> Int(ch)))
             + sum(n[(1..).step(2)].map(ch -> Int(ch) * 3))
   R product % 10 == 0

V tests = |‘978-0596528126
            978-0596528120
            978-1788399081
            978-1788399083’.split("\n")

L(t) tests
   print(‘ISBN13 ’t‘ validates ’is_isbn13(t))
Output:
ISBN13 978-0596528126 validates 1B
ISBN13 978-0596528120 validates 0B
ISBN13 978-1788399081 validates 1B
ISBN13 978-1788399083 validates 0B

8080 Assembly

	org	100h
	jmp	demo
	;;;	---------------------------------------------------------------
	;;;	Check if the string at BC is a valid ISBN-13 code.
	;;;	Carry set if true, clear if not.
isbn13:	lxi	h,0	; HL = accumulator
	mov	d,h	; D = 0 (such that if E=A, DE=A).
	call	isbngc	; Get first character
	rnc		; Carry clear = invalid
	dad	d	; Add to running total once
	call	isbngc	; Get second character
	rnc		; Carry clear = invalid
	dad	d	; Add to running total thrice
	dad	d
	dad	d
	call	isbngc	; Get third character
	rnc		; Carry clear = invalid
	dad	d	; Add to running total once
	ldax	b	; Fourth character should be a dash '-'
	inx	b
	cpi	'-'
	stc		; Clear carry w/o touching other flags
	cmc
	rnz		; If not equal, invalid.
	push	h	; Keep loop counter on stack
	mvi	l,5	; 5 times 2 characters
isbnlp:	xthl		; Accumulator in HL 
	call	isbngc	; Get even character
	jnc	isbnex	; If invalid, stop
	dad	d	; Add to running total thrice
	dad	d
	dad 	d
	call	isbngc	; Get odd character
	jnc	isbnex	; If invalid, stop
	dad	d	; Add to running total once
	xthl		; Loop counter in (H)L
	dcr	l	; Done yet?
	jnz	isbnlp	; If not, do next two characters
	pop	h	; Get accumulator
	lxi	d,-10	; Trial division by ten
isbndv:	dad	d	; Subtract 10
	jc	isbndv 	; Until zero passed
	mov	a,l	; Move low byte to A
	adi	10 	; Add ten back (the mod loop went one step too far)
	rz		; If zero, return (carry will have been set)
	ana	a	; Otherwise, make sure carry is clear
	ret		; And then return 
isbnex:	pop	h	; Test failed - throw away accumulator and return
	ret
isbngc:	ldax	b	; Get character from [BC]
	inx	b	; Increment BC 
	sui	'0'	; Subtract ASCII '0' to get digit value
	cpi	10	; If 10 or higher (unsigned), invalid digit.
	mov	e,a	; Set (D)E = value
	ret
	;;;	---------------------------------------------------------------
	;;;	Demo: see if the CP/M command line contains a valid ISBN13
	;;; 	code.
demo:	lxi	b,82h	; Start of command line argument, skipping first space
	call	isbn13	; Is it valid?
	mvi	c,9	; CP/M print string
	lxi	d,good	; If carry is set, then yes
	jc	5
	lxi	d,bad	; Otherwise, no.
	jmp	5
good:	db	'good$'
bad:	db	'bad$'
Output:
A>isbn13 978-0596528126
good
A>isbn13 978-0596528120
bad
A>isbn13 978-1788399081
good
A>isbn13 978-1788399083
bad

8086 Assembly

	cpu	8086
	bits	16
	org	100h
section	.text
	jmp	demo
isbn13:	;;;	---------------------------------------------------------------
	;;;	Check if the string at DS:SI is a valid ISBN-13 code.
	;;;	Carry set if true, clear if false.
	xor	dx,dx		; DX = running total
	xor	ah,ah		; Set AH=0 so that AX=AL 
	call	.digit		; Get first digit and add to DX
	call	.digit		; Get second digit and add to DX
	add	dx,ax		; Add to DX twice more
	add	dx,ax
	call	.digit		; Get third digit and add to DX
	lodsb			; Fourth character should be a '-'
	cmp	al,'-'
	jne	.fail 		; If not equal, fail
	mov	cx,5 		; Then loop 5 times for the next 10 digits
.loop:	call	.digit		; Get even digit and add to DX
	add	dx,ax		; Add to running total twice more
	add	dx,ax
	call	.digit		; Get odd digit and add to DX
	loop	.loop		; Do this 5 times
	mov	ax,dx		; Divide running total by 10
	mov	dl,10
	div	dl
	test	ah,ah		; Is the remainder zero?
	jnz	.out		; If not, stop (TEST clears carry)
	stc			; Otherwise, set carry and return
	ret
.digit:	lodsb			; Get first character
	sub	al,'0'		; Subtract ASCII 0 to get digit value
	cmp	al,9		 
	ja	.dfail
	add	dx,ax		; Add to ASCII
	ret
.dfail:	pop	dx		; Remove return pointer for .digit from stack
.fail:	clc			; Failure - clear carry
.out:	ret
	;;;	---------------------------------------------------------------
	;;;	Demo: see if the MS-DOS command line contains a valid ISBN13
	;;;	code.
demo:	mov	si,82h		; Start of command line argument skipping space
	call	isbn13		; Is it valid?
	mov	ah,9		; MS-DOS print string
	mov	dx,good		; If carry is set, it is good
	jc	.print
	mov	dx,bad		; Otherwise, it is bad
.print:	int	21h
	ret
section	.data
good:	db	'good$'
bad: 	db	'bad$'
Output:
C:\>isbn13 978-0596528126
good
C:\>isbn13 978-0596528120
bad
C:\>isbn13 978-1788399081
good
C:\>isbn13 978-1788399083
bad

ABC

HOW TO REPORT valid.isbn13 str:
    PUT {} IN digits
    FOR d IN {0..9}: PUT d IN digits["`d`"]
    IF #str <> 14 OR str item 4 <> '-': FAIL
    PUT 1, 0 IN mul, sum
    FOR c IN str|3 ^ str@5:
        IF c not.in keys digits: FAIL
        PUT sum + digits[c] * mul IN sum
        PUT 4 - mul IN mul
    REPORT sum mod 10 = 0

PUT {} IN tests
PUT "978-0596528126" IN tests[1]
PUT "978-0596528120" IN tests[2]
PUT "978-1788399081" IN tests[3]
PUT "978-1788399083" IN tests[4]

FOR test IN tests:
    SELECT:
        valid.isbn13 test: WRITE test^": good"/
        ELSE: WRITE test^": bad"/
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Action!

INCLUDE "D2:CHARTEST.ACT" ;from the Action! Tool Kit

BYTE FUNC CheckISBN13(CHAR ARRAY t)
  BYTE i,index,sum,v

  sum=0 index=0
  FOR i=1 TO t(0)
  DO
    v=t(i)
    IF IsDigit(v) THEN
      v==-'0
      IF index MOD 2=1 THEN
        v==*3
      FI
      sum==+v
      index==+1
    ELSEIF v#'  AND v#'- THEN
      RETURN (0)
    FI
  OD
  IF index#13 OR sum MOD 10#0 THEN
    RETURN (0)
  FI
RETURN (1)

PROC Test(CHAR ARRAY t)
  BYTE correct

  correct=CheckISBN13(t)
  Print(t) Print(" is ")
  IF correct THEN
    PrintE("correct")
  ELSE
    PrintE("incorrect")
  FI
RETURN

PROC Main()
  Put(125) PutE() ;clear screen

  Test("978-0596528126")
  Test("978-0596528120")
  Test("978-1788399081")
  Test("978-1788399083")
RETURN
Output:

Screenshot from Atari 8-bit computer

978-0596528126 is correct
978-0596528120 is incorrect
978-1788399081 is correct
978-1788399083 is incorrect

Ada

with Ada.Text_IO;

procedure ISBN_Check is

   function Is_Valid (ISBN : String) return Boolean is
      Odd       : Boolean := True;
      Sum       : Integer := 0;
      Value     : Integer;
   begin
      for I in ISBN'Range loop
         if ISBN (I) in '0' .. '9' then
            Value := Character'Pos (ISBN (I)) - Character'Pos ('0');
            if Odd then
               Sum := Sum + Value;
            else
               Sum := Sum + 3 * Value;
            end if;
            Odd := not Odd;
         end if;
      end loop;
      return Sum mod 10 = 0;
   end Is_Valid;

   procedure Show (ISBN : String) is
      use Ada.Text_IO;
      Valid : constant Boolean := Is_Valid (ISBN);
   begin
      Put (ISBN); Put ("  ");
      Put ((if Valid then "Good" else "Bad"));
      New_Line;
   end Show;
begin
   Show ("978-0596528126");
   Show ("978-0596528120");
   Show ("978-1788399081");
   Show ("978-1788399083");
end ISBN_Check;
Output:
978-0596528126  Good
978-0596528120  Bad
978-1788399081  Good
978-1788399083  Bad

ALGOL 68

Works with: ALGOL 68G version Any - tested with release 2.8.3.win32
BEGIN # Check some IsBN13 check digits                                         #
    # returns TRUE if the alledged isbn13 has the correct check sum,           #
    #         FALSE otherwise                                                  #
    #         non-digit characters are ignored and there must be 13 digits     #
    PROC check isbn13 = ( STRING isbn13 )BOOL:
         BEGIN
            INT sum          := 0;
            INT digits       := 0;
            BOOL other digit := FALSE;
            FOR pos FROM LWB isbn13 TO UPB isbn13 DO
                IF CHAR c = isbn13[ pos ];
                   c >= "0" AND c <= "9"
                THEN
                    # have another digit                                       #
                    digits +:= 1;
                    sum    +:= ( ABS c - ABS "0" ) * IF other digit THEN 3 ELSE 1 FI;
                    other digit := NOT other digit
                FI
            OD;
            digits = 13 AND sum MOD 10 = 0
         END; # check isbn13 #
    # task test cases #
    []STRING tests    = ( "978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083" );
    []BOOL   expected = (             TRUE,            FALSE,             TRUE,            FALSE );
    FOR pos FROM LWB tests TO UPB tests DO
        BOOL result = check isbn13( tests[ pos ] );
        print( ( tests[ pos ]
               , ": "
               , IF result THEN "good" ELSE "bad" FI
               , IF result = expected[ pos ] THEN "" ELSE " NOT AS EXPECTED" FI
               , newline
               )
             )
    OD
END
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

APL

Works with: Dyalog APL
check_isbn13{
    13≠⍴n(⎕D)/⍵:0
    0=10|(¨n)+.×131 3
}
Output:
      check_isbn13¨ '978-0596528126' '978-0596528120' '978-1788399081' '978-1788399083'
1 0 1 0

AppleScript

Composition of pure functions

-------------------- ISBN13 CHECK DIGIT --------------------

-- isISBN13 :: String -> Bool
on isISBN13(s)
    script digitValue
        on |λ|(c)
            if isDigit(c) then
                {c as integer}
            else
                {}
            end if
        end |λ|
    end script
    
    set digits to concatMap(digitValue, characters of s)
    
    13 = length of digits ¬
        and 0 = sum(zipWith(my mul, digits, cycle({1, 3}))) mod 10
end isISBN13


--------------------------- TEST ---------------------------
on run
    script test
        on |λ|(s)
            {s, isISBN13(s)}
        end |λ|
    end script
    
    map(test, {"978-0596528126", "978-0596528120", ¬
        "978-1788399081", "978-1788399083"})
end run


-------------------- GENERIC FUNCTIONS ---------------------

-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
    set lng to length of xs
    set acc to {}
    tell mReturn(f)
        repeat with i from 1 to lng
            set acc to acc & (|λ|(item i of xs, i, xs))
        end repeat
    end tell
    return acc
end concatMap


-- cycle :: [a] -> Generator [a]
on cycle(xs)
    script
        property lng : 1 + (length of xs)
        property i : missing value
        on |λ|()
            if missing value is i then
                set i to 1
            else
                set nxt to (1 + i) mod lng
                if 0 = ((1 + i) mod lng) then
                    set i to 1
                else
                    set i to nxt
                end if
            end if
            return item i of xs
        end |λ|
    end script
end cycle


-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl


-- isDigit :: Char -> Bool
on isDigit(c)
    set n to (id of c)
    48  n and 57  n
end isDigit


-- length :: [a] -> Int
on |length|(xs)
    set c to class of xs
    if list is c or string is c then
        length of xs
    else
        (2 ^ 29 - 1) -- (maxInt - simple proxy for non-finite)
    end if
end |length|


-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map


-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min


-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn


-- mul (*) :: Num a => a -> a -> a
on mul(a, b)
    a * b
end mul


-- sum :: [Num] -> Num
on sum(xs)
    script add
        on |λ|(a, b)
            a + b
        end |λ|
    end script
    
    foldl(add, 0, xs)
end sum


-- take :: Int -> [a] -> [a]
-- take :: Int -> String -> String
on take(n, xs)
    set c to class of xs
    if list is c then
        if 0 < n then
            items 1 thru min(n, length of xs) of xs
        else
            {}
        end if
    else if string is c then
        if 0 < n then
            text 1 thru min(n, length of xs) of xs
        else
            ""
        end if
    else if script is c then
        set ys to {}
        repeat with i from 1 to n
            set v to |λ|() of xs
            if missing value is v then
                return ys
            else
                set end of ys to v
            end if
        end repeat
        return ys
    else
        missing value
    end if
end take


-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
on zipWith(f, xs, ys)
    set lng to min(|length|(xs), |length|(ys))
    if 1 > lng then return {}
    set xs_ to take(lng, xs) -- Allow for non-finite
    set ys_ to take(lng, ys) -- generators like cycle etc
    set lst to {}
    tell mReturn(f)
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs_, item i of ys_)
        end repeat
        return lst
    end tell
end zipWith
Output:
{{"978-0596528126", true}, {"978-0596528120", false}, {"978-1788399081", true}, {"978-1788399083", false}}

Straightforward

This task can be tackled very simply by working through the numeric text two characters at a time:

on validateISBN13(ISBN13)
    if (ISBN13's class is not text) then return false
    
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to {"-", space}
    set ISBN13 to ISBN13's text items
    set AppleScript's text item delimiters to ""
    set ISBN13 to ISBN13 as text
    set AppleScript's text item delimiters to astid
    
    if (((count ISBN13) is not 13) or (ISBN13 contains ".") or (ISBN13 contains ",")) then return false
    try
        ISBN13 as number
    on error
        return false
    end try
    
    set sum to 0
    repeat with i from 1 to 12 by 2
        set sum to sum + (character i of ISBN13) + (character (i + 1) of ISBN13) * 3 -- Automatic text-to-number coercions.
    end repeat
    
    return ((sum + (character 13 of ISBN13)) mod 10 = 0)
end validateISBN13

-- Test:
set output to {}
set verdicts to {"bad", "good"}
repeat with thisISBN13 in {"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"}
    set isValid to validateISBN13(thisISBN13)
    set end of output to thisISBN13 & ": " & item ((isValid as integer) + 1) of verdicts
end repeat

set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
set output to output as text
set AppleScript's text item delimiters to astid
return output
Output:
"978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad"

Or it can be handled purely numerically. Since the "weights" alternate and are palindromic, it makes no difference whether the last digit or the first is treated as the check digit. In fact, if preferred, the repeat below can go round 7 times with the return line as simply: return (sum mod 10 = 0).

on validateISBN13(ISBN13)
    if (ISBN13's class is not text) then return false
    
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to {"-", space}
    set ISBN13 to ISBN13's text items
    set AppleScript's text item delimiters to ""
    set ISBN13 to ISBN13 as text
    set AppleScript's text item delimiters to astid
    
    if (((count ISBN13) is not 13) or (ISBN13 contains ".") or (ISBN13 contains ",")) then return false
    try
        set ISBN13 to ISBN13 as number
    on error
        return false
    end try
    
    set sum to 0
    repeat 6 times
        set sum to sum + ISBN13 mod 10 + ISBN13 mod 100 div 10 * 3
        set ISBN13 to ISBN13 div 100
    end repeat
    
    return ((sum + ISBN13) mod 10 = 0)
end validateISBN13

Arturo

validISBN?: function [isbn][
    currentCheck: to :integer to :string last isbn
    isbn: map split chop replace isbn "-" "" 'd -> to :integer d

    s: 0
    loop.with:'i isbn 'n [
        if? even? i -> s: s + n
        else -> s: s + 3*n
    ]
    checkDigit: 10 - s % 10
    return currentCheck = checkDigit
]

tests: [
    "978-0596528126" "978-0596528120"
    "978-1788399081" "978-1788399083"
]

loop tests 'test [
    print [test "=>" validISBN? test]
]
Output:
978-0596528126 => true 
978-0596528120 => false 
978-1788399081 => true 
978-1788399083 => false

AutoHotkey

ISBN13_check_digit(n){
	for i, v in StrSplit(RegExReplace(n, "[^0-9]"))
		sum += !Mod(i, 2) ? v*3 : v
	return n "`t" (Mod(sum, 10) ? "(bad)" : "(good)")
}

Examples:

output := ""
nums := ["978-0596528126","978-0596528120","978-1788399081","978-1788399083"]
for i, n in nums
	output .= ISBN13_check_digit(n) "`n"
MsgBox % output
return
Output:
978-0596528126	(good)
978-0596528120	(bad)
978-1788399081	(good)
978-1788399083	(bad)

AWK

# syntax: GAWK -f ISBN13_CHECK_DIGIT.AWK
BEGIN {
    arr[++n] = "978-0596528126"
    arr[++n] = "978-0596528120"
    arr[++n] = "978-1788399081"
    arr[++n] = "978-1788399083"
    arr[++n] = "9780820424521"
    arr[++n] = "0820424528"
    for (i=1; i<=n; i++) {
      printf("%s %s\n",arr[i],isbn13(arr[i]))
    }
    exit(0)
}
function isbn13(isbn,  check_digit,i,sum) {
    gsub(/[ -]/,"",isbn)
    if (length(isbn) != 13) { return("NG length") }
    for (i=1; i<=12; i++) {
      sum += substr(isbn,i,1) * (i % 2 == 1 ? 1 : 3)
    }
    check_digit = 10 - (sum % 10)
    return(substr(isbn,13,1) == check_digit ? "OK" : sprintf("NG check digit S/B %d",check_digit))
}
Output:
978-0596528126 OK
978-0596528120 NG check digit S/B 2
978-1788399081 OK
978-1788399083 NG check digit S/B 1
9780820424521 OK
0820424528 NG length

BASIC256

Translation of: Ring
arraybase 1
dim isbn = {"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083", "978-2-74839-908-0", "978-2-74839-908-5", "978 1 86197 876 9"}

for n = 1 to isbn[?]
	sum = 0
	isbnStr = isbn[n]
	isbnStr = replace(isbnStr, "-", "")
	isbnStr = replace(isbnStr, " ", "")
	for m = 1 to length(isbnStr)
		if m mod 2 = 0 then
			num = 3 * int(mid(isbnStr, m, 1))
		else
			num = int(mid(isbnStr, m, 1))
		end if
		sum += num
	next m
	if sum mod 10 = 0 then
		print isbn[n]; ": good"
	else
		print isbn[n]; ": bad"
	end if
next n

BCPL

get "libhdr"

let checkISBN(s) = valof
$(  let tally = 0
    unless s%0 = 14 resultis false
    unless s%4 = '-' resultis false 
    
    for i=1 to 3
    $(  let digit = s%i-'0'
        test i rem 2 = 0 
            do tally := tally + 3 * digit
            or tally := tally + digit
    $)
    
    for i=5 to 14
    $(  let digit = s%i-'0'
        test i rem 2 = 0
            do tally := tally + digit
            or tally := tally + 3 * digit
    $)
    
    resultis tally rem 10 = 0
$)

let show(s) be
    writef("%S: %S*N", s, checkISBN(s) -> "good", "bad")

let start() be
$(  show("978-0596528126")
    show("978-0596528120")
    show("978-1788399081")
    show("978-1788399083")
$)
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

C

#include <stdio.h>

int check_isbn13(const char *isbn) {
    int ch = *isbn, count = 0, sum = 0;
    /* check isbn contains 13 digits and calculate weighted sum */
    for ( ; ch != 0; ch = *++isbn, ++count) {
        /* skip hyphens or spaces */
        if (ch == ' ' || ch == '-') {
            --count;
            continue;
        }
        if (ch < '0' || ch > '9') {
            return 0;
        }
        if (count & 1) {
            sum += 3 * (ch - '0');
        } else {
            sum += ch - '0';
        }
    }
    if (count != 13) return 0;
    return !(sum%10);
}

int main() {
    int i;
    const char* isbns[] = {"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"};
    for (i = 0; i < 4; ++i) {
        printf("%s: %s\n", isbns[i], check_isbn13(isbns[i]) ? "good" : "bad");
    }
    return 0;
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

C++

Translation of: C
#include <iostream>

bool check_isbn13(std::string isbn) {
    int count = 0;
    int sum = 0;

    for (auto ch : isbn) {
        /* skip hyphens or spaces */
        if (ch == ' ' || ch == '-') {
            continue;
        }
        if (ch < '0' || ch > '9') {
            return false;
        }
        if (count & 1) {
            sum += 3 * (ch - '0');
        } else {
            sum += ch - '0';
        }
        count++;
    }

    if (count != 13) {
        return false;
    }
    return sum % 10 == 0;
}

int main() {
    auto isbns = { "978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083" };
    for (auto isbn : isbns) {
        std::cout << isbn << ": " << (check_isbn13(isbn) ? "good" : "bad") << '\n';
    }

    return 0;
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

C#

using System;
using System.Linq;

public class Program
{
    public static void Main() {
        Console.WriteLine(CheckISBN13("978-0596528126"));
        Console.WriteLine(CheckISBN13("978-0596528120"));
        Console.WriteLine(CheckISBN13("978-1788399081"));
        Console.WriteLine(CheckISBN13("978-1788399083"));

        static bool CheckISBN13(string code) {
            code = code.Replace("-", "").Replace(" ", "");
            if (code.Length != 13) return false;
            int sum = 0;
            foreach (var (index, digit) in code.Select((digit, index) => (index, digit))) {
                if (char.IsDigit(digit)) sum += (digit - '0') * (index % 2 == 0 ? 1 : 3);
                else return false;
            }
            return sum % 10 == 0;
        }
    }
}
Output:
True
False
True
False

CLU

isbn13_check = proc (s: string) returns (bool)
    if string$size(s) ~= 14 then return(false) end
    if s[4] ~= '-' then return(false) end
    begin
        check: int := 0
        for i: int in int$from_to(1, 14) do
            if i=4 then continue end
            d: int := int$parse(string$c2s(s[i]))
            if i=2 cor (i>4 cand i//2=1) then d := d*3 end
            check := check + d
        end
        return(check//10 = 0)
    end except when bad_format: 
        return(false)
    end
end isbn13_check

start_up = proc ()
    po: stream := stream$primary_output()
    tests: array[string] := array[string]$
       ["978-0596528126",
        "978-0596528120",
        "978-1788399081",
        "978-1788399083"]
        
    for test: string in array[string]$elements(tests) do
        stream$puts(po, test)
        stream$puts(po, ": ")
        if isbn13_check(test)
            then stream$putl(po, "good")
            else stream$putl(po, "bad")
        end
    end
end start_up
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

COBOL

Works with: GnuCOBOL
      ******************************************************************
      * Author: Jay Moseley
      * Date: November 10, 2019
      * Purpose: Testing various subprograms/ functions.
      * Tectonics: cobc -xj testSubs.cbl
      ******************************************************************
       IDENTIFICATION DIVISION.

       PROGRAM-ID. testSubs.
       ENVIRONMENT DIVISION.

       CONFIGURATION SECTION.
       REPOSITORY.
           FUNCTION ALL INTRINSIC
           FUNCTION validISBN13.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

       DATA DIVISION.

       FILE SECTION.

       WORKING-STORAGE SECTION.

       01  IX                          PIC S9(4) COMP.
       01  TEST-ISBNS.
           02  FILLER                  PIC X(14) VALUE '978-0596528126'.
           02  FILLER                  PIC X(14) VALUE '978-0596528120'.
           02  FILLER                  PIC X(14) VALUE '978-1788399081'.
           02  FILLER                  PIC X(14) VALUE '978-1788399083'.
       01  TEST-ISBN                   REDEFINES TEST-ISBNS
                                       OCCURS 4 TIMES
                                       PIC X(14).

       PROCEDURE DIVISION.

       MAIN-PROCEDURE.

           PERFORM 
             VARYING IX 
             FROM 1
             BY 1
             UNTIL IX > 4

             DISPLAY TEST-ISBN (IX) '   ' WITH NO ADVANCING
             END-DISPLAY
             IF validISBN13(TEST-ISBN (IX)) = -1
               DISPLAY '(bad)'
             ELSE
               DISPLAY '(good)'
             END-IF

           END-PERFORM.

           GOBACK.

       END PROGRAM testSubs.

      ******************************************************************
      * Author: Jay Moseley
      * Date: May 19, 2016
      * Purpose: validate ISBN-13 (International Standard
      *          Book Number).
      ******************************************************************
       IDENTIFICATION DIVISION.

       FUNCTION-ID. validISBN13.
       ENVIRONMENT DIVISION.

       CONFIGURATION SECTION.
       REPOSITORY.
           FUNCTION ALL INTRINSIC.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.

       DATA DIVISION.

       FILE SECTION.

       WORKING-STORAGE SECTION.

       01  PASSED-SIZE                 PIC S9(6) COMP-5.
       01  IX                          PIC S9(4) COMP.

       01  WORK-FIELDS.
           02  WF-DIGIT                PIC X.
           02  WF-COUNT                PIC 9(2).
               88  WEIGHT-1  VALUE 1, 3, 5, 7, 9, 11, 13.
               88  WEIGHT-3  VALUE 2, 4, 6, 8, 10, 12.
           02  WF-SUM                  PIC S9(8) COMP.

       LINKAGE SECTION.

       01  PASSED-ISBN                 PIC X ANY LENGTH.
       01  RETURN-VALUE                PIC S9.

       PROCEDURE DIVISION USING PASSED-ISBN
                          RETURNING RETURN-VALUE.

           CALL 'C$PARAMSIZE'
             USING 1
             GIVING PASSED-SIZE
           END-CALL.
       
       COMPUTE-CKDIGIT.

           INITIALIZE WORK-FIELDS.
           PERFORM 
             VARYING IX 
             FROM 1 
             BY 1
             UNTIL IX GREATER THAN PASSED-SIZE

               MOVE PASSED-ISBN (IX:1) TO WF-DIGIT
               IF WF-DIGIT IS NUMERIC
                 ADD 1 TO WF-COUNT
                 IF WEIGHT-1
                   ADD NUMVAL(WF-DIGIT) TO WF-SUM
                 ELSE
                   COMPUTE WF-SUM = WF-SUM + 
                     (NUMVAL(WF-DIGIT) * 3)
                   END-COMPUTE
                 END-IF
               END-IF

           END-PERFORM.

           IF MOD(WF-SUM, 10) = 0
             MOVE +0 TO RETURN-VALUE
           ELSE
             MOVE -1 TO RETURN-VALUE
           END-IF.

           GOBACK.
      * - - - - - - - - - - - - - - - - - - - - - - PROGRAM EXIT POINT

       END FUNCTION validISBN13.
Output:
978-0596528126   (good)
978-0596528120   (bad)
978-1788399081   (good)
978-1788399083   (bad)

Cowgol

include "cowgol.coh";
 
sub check_isbn13(isbn: [uint8]): (r: uint8) is  
    var n: uint8 := 0;
    r := 0;
    loop    
        var c := [isbn];
        isbn := @next isbn;
        if c == 0 then break; end if;
        c := c - '0';
        if c <= 9 then
            r := r + c;
            n := n + 1;
            if (n & 1) == 0 then
                r := r + c * 2;
            end if;
        end if;
    end loop;
    if n == 13 and r%10 == 0 then
        r := 1;
    else
        r := 0;
    end if;
end sub;
 
var isbns: [uint8][] := {
    "978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"
};
 
var result: [uint8][] := {": bad\n", ": good\n"};
 
var n: uint8 := 0;
while n < @sizeof isbns loop
    print(isbns[n]);
    print(result[check_isbn13(isbns[n])]);
    n := n + 1;
end loop;
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

D

Translation of: Kotlin
import std.stdio;

bool isValidISBN13(string text) {
    int sum, i;
    foreach (c; text) {
        if ('0' <= c && c <= '9') {
            int value = c - '0';
            if (i % 2 == 0) {
                sum += value;
            } else {
                sum += 3 * value;
            }

            i++;
        }
    }
    return i == 13 && 0 == sum % 10;
}

unittest {
    assert(isValidISBN13("978-0596528126"));
    assert(!isValidISBN13("978-0596528120"));
    assert(isValidISBN13("978-1788399081"));
    assert(!isValidISBN13("978-1788399083"));
}

Delphi

Works with: Delphi version 6.0


function ValidateISBN(ISBN: string): boolean;
{Validate an ISBN number}
var I,N,Sum: integer;
begin
Sum:=0;
{Go througha chars, ignoring non-digits}
for I:=1 to Length(ISBN) do
 if ISBN[I] in ['0'..'9'] then
	begin
	N:=StrToInt(ISBN[I]);
	{Every other digit multiplied by 3}
	if (I and 1)=1 then N:=N*3;
	{Sum digits}
	Sum:=Sum+N;
	end;
{The sum must be an even multiple of 10}
Result:=(Sum mod 10)=0;
end;

procedure ValidateAndShow(Memo: TMemo; ISBN: string);
{Validate ISBN number and show the result}
var S: string;
begin
S:=ISBN;
if ValidateISBN(ISBN) then S:=S+' (Good)'
else S:=S+' (Bad)';
Memo.Lines.Add(S);
end;

procedure TestISBNSet(Memo: TMemo);
{Test supplied set of ISBN numbers}
begin
ValidateAndShow(Memo,'978-0596528126');		//(good)
ValidateAndShow(Memo,'978-0596528120');		//(bad)
ValidateAndShow(Memo,'978-1788399081');		//(good)
ValidateAndShow(Memo,'978-1788399083');		//(bad)
end;
Output:
978-0596528126 (Good)
978-0596528120 (Bad)
978-1788399081 (Good)
978-1788399083 (Bad)


Draco

proc nonrec isbn13_check(*char isbn) bool:
    byte n, check, d;
    char cur;
    bool ok;
    n := 0;
    check := 0;
    ok := true;
    while
        cur := isbn*;
        isbn := isbn + 1;
        ok and cur ~= '\e'
    do
        if n=3 then
            if cur ~= '-' then ok := false fi
        elif cur<'0' or cur>'9' then
            ok := false
        else
            d := cur - '0';
            if n=1 or (n>3 and n&1 = 0) then
                d := d * 3;
            fi;
            check := check + d
        fi;
        n := n + 1
    od;
    ok and n = 14 and check%10 = 0
corp

proc nonrec test(*char isbn) void:
    writeln(isbn, ": ", 
            if isbn13_check(isbn) then "good" else "bad" fi)
corp

proc nonrec main() void:
    test("978-0596528126");
    test("978-0596528120");
    test("978-1788399081");
    test("978-1788399083")
corp
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

EasyLang

func ISBN13check isbn$ .
   for c$ in strchars isbn$
      if c$ <> "-"
         ndigs += 1
      .
      dig = number c$
      if ndigs mod 2 = 0
         dig *= 3
      .
      sum += dig
   .
   if sum mod 10 <> 0
      return 0
   .
   return 1
.
codes$[] = [ "978-0596528126" "978-0596528120" "978-1788399081" "978-1788399083" ]
for code$ in codes$[]
   if ISBN13check code$ = 1
      print code$ & " is a valid ISBN"
   else
      print code$ & " is not a valid ISBN"
   .
.
Output:
978-0596528126 is a valid ISBN
978-0596528120 is not a valid ISBN
978-1788399081 is a valid ISBN
978-1788399083 is not a valid ISBN

Excel

LAMBDA

Binding the name ISBN13Check to the following lambda expression in the Name Manager of the Excel WorkBook:

(See LAMBDA: The ultimate Excel worksheet function)

ISBN13Check
=LAMBDA(s,
    LET(
        ns, FILTERP(
            LAMBDA(v,
                NOT(ISERROR(v))
            )
        )(
            VALUE(CHARSROW(s))
        ),
        ixs, SEQUENCE(
            1, COLUMNS(ns),
            1, 1
        ),

        0 = MOD(
            SUM(
                IF(0 <> MOD(ixs, 2),
                    INDEX(ns, ixs),
                    3 * INDEX(ns, ixs)
                )
            ),
            10
        )
    )
)

and also assuming the following generic bindings in the Name Manager for the WorkBook:

CHARSROW
=LAMBDA(s,
    MID(s,
        SEQUENCE(1, LEN(s), 1, 1),
        1
    )
)


FILTERP
=LAMBDA(p,
    LAMBDA(xs,
        FILTER(xs, p(xs))
    )
)


ISDIGIT
=LAMBDA(c,
    LET(
        ic, CODE(c),

        AND(47 < ic, 58 > ic)
    )
)
Output:
fx =ISBN13Check(A2)
A B
1 Candidate string ISBN13 checked
2 978-0596528126 TRUE
3 978-0596528120 FALSE
4 978-1788399081 TRUE
5 978-1788399083 FALSE

Factor

USING: combinators.short-circuit formatting kernel math
math.functions math.parser math.vectors qw sequences
sequences.extras sets unicode ;

: (isbn13?) ( str -- ? )
    string>digits
    [ <evens> sum ] [ <odds> 3 v*n sum + ] bi 10 divisor? ;

: isbn13? ( str -- ? )
    "- " without
    { [ length 13 = ] [ [ digit? ] all? ] [ (isbn13?) ] } 1&& ;

qw{ 978-0596528126 978-0596528120 978-1788399081 978-1788399083 }
[ dup isbn13? "good" "bad" ? "%s: %s\n" printf ] each
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Forth

The following word not only identifies correct 13 digit ISBN (and EAN) codes, but also 8 digit EAN and GTIN codes.

: is-digit [char] 0 - 10 u< ;

: isbn?                                ( a n -- f)
  1- chars over + 2>r 0 1 2r> ?do
    dup i c@ dup is-digit              \ get length and factor, setup loop
    if [char] 0 - * rot + swap 3 * 8 mod else drop drop then
  -1 chars +loop drop 10 mod 0=        \ now calculate checksum
;
Output:

In Forth, a "true" value is indicated by "-1".

s" 978-0596528126" isbn? .  -1  ok
s" 978-0596528120" isbn? .  0  ok
s" 978-1788399081" isbn? .  -1  ok
s" 978-1788399083" isbn? .  0  ok

Fortran

program isbn13
    implicit none

    character(len=14), dimension(4), parameter  :: isbns=["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"]
    integer                                     :: i

    do i = 1, ubound(isbns, 1)
        if (check_isbn13(isbns(i))) then
            print*, isbns(i), " : ", "good"
        else
            print*, isbns(i), " : ", "bad"
        end if
    end do
contains
    pure function check_isbn13(isbn)
        character(len=*), intent(in)    :: isbn
        logical                         :: check_isbn13
        integer                         :: summ, counter, i, digit

        check_isbn13 = .false.
        counter = 0
        summ = 0

        do i = 1, len(isbn)
            if (isbn(i:i) == ' ' .or. isbn(i:i) == '-') cycle
            counter = counter + 1
            read(isbn(i:i), '(I1)') digit
            if (modulo(counter, 2) == 0) then
                summ = summ + 3*digit
            else
                summ = summ + digit
            end if
        end do
        if (counter == 13 .and. modulo(summ, 10) == 0) check_isbn13 = .true.
    end function check_isbn13
end program isbn13
Output:
 978-0596528126 : good
 978-0596528120 : bad
 978-1788399081 : good
 978-1788399083 : bad

FreeBASIC

#define ZEROC asc("0")

function is_num( byval c as string ) as boolean
    if asc(c) >= ZEROC andalso asc(c)<ZEROC+10 then return true
    return false
end function

function is_good_isbn( isbn as string ) as boolean
    dim as uinteger charno = 0, digitno = 0, sum = 0
    dim as string*1 currchar
    while charno <= len(isbn)
        currchar = mid(isbn,charno,1)
        if is_num(currchar) then
            if digitno mod 2 = 1 then
                sum += 2*(asc(currchar)-ZEROC)
            end if
            sum += asc(currchar)-ZEROC
            digitno += 1
        end if
        charno += 1
    wend
    if sum mod 10 = 0 then 
        return true
    else 
        return false
    end if
end function

dim as string isbns(0 to 3) = { "978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083" }
dim as uinteger i
for i = 0 to 3
    if is_good_isbn( isbns(i) ) then
        print isbns(i)+": good"
    else
        print isbns(i)+": bad"
    end if
next i
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

F#

// ISBN13 Check Digit
open System

let parseInput (input: string) =
    Seq.map(fun c -> c |> string) input |> Seq.toList |> List.map(fun x -> Int32.Parse x)

[<EntryPoint>]
let main argv =
    let isbnnum = parseInput (String.filter (fun x -> x <> '-') argv.[0])
    // Multiply every other digit by 3
    let everyOther = List.mapi (fun i x -> if i % 2 = 0 then x * 3 else x) isbnnum
    // Sum the *3 list with the original list
    let sum = List.sum everyOther + List.sum isbnnum
    // If the remainder of sum / 10 is 0 then it's a valid ISBN13
    if sum % 10 = 0 then
        printfn "%s Valid ISBN13" argv.[0]
    else
        printfn "%s Invalid ISBN13" argv.[0]
    0
Output:
978-0596528126 Valid ISBN13
978-0596528120 Inalid ISBN13
978-1788399081 Valid ISBN13
978-1788399083 Inalid ISBN13

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

Test cases

Gambas

Translation of: BASIC256
Public Sub Main() 
  
  Dim isbn As String[] = ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083", "978-2-74839-908-0", "978-2-74839-908-5", "978 1 86197 876 9"]
  
  For n As Integer = 1 To isbn.Count
    Dim sum As Integer = 0, num As Integer 
    Dim isbnStr As String = isbn[n]
    isbnStr = Replace(isbnStr, "-", "") 
    isbnStr = Replace(isbnStr, " ", "") 
    For m As Integer = 1 To Len(isbnStr) 
      If m Mod 2 = 0 Then 
        num = 3 * CInt(Mid(isbnStr, m, 1)) 
      Else 
        num = CInt(Mid(isbnStr, m, 1)) 
      End If 
      sum += num 
    Next
    If sum Mod 10 = 0 Then 
      Print isbn[n]; ": good" 
    Else 
      Print isbn[n]; ": bad" 
    End If 
  Next
  
End

Go

package main

import (
    "fmt"
    "strings"
    "unicode/utf8"
)

func checkIsbn13(isbn string) bool {
    // remove any hyphens or spaces
    isbn = strings.ReplaceAll(strings.ReplaceAll(isbn, "-", ""), " ", "")
    // check length == 13
    le := utf8.RuneCountInString(isbn)
    if le != 13 {
        return false
    }
    // check only contains digits and calculate weighted sum
    sum := int32(0)
    for i, c := range isbn {
        if c < '0' || c > '9' {
            return false
        }
        if i%2 == 0 {
            sum += c - '0'
        } else {
            sum += 3 * (c - '0')
        }
    }
    return sum%10 == 0
}

func main() {
    isbns := []string{"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"}
    for _, isbn := range isbns {
        res := "bad"
        if checkIsbn13(isbn) {
            res = "good"
        }
        fmt.Printf("%s: %s\n", isbn, res)
    }
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Haskell

import Data.Char (digitToInt, isDigit)
import Text.Printf (printf)

pair :: Num a => [a] -> [(a, a)]
pair [] = []
pair xs = p (take 2 xs) : pair (drop 2 xs)
  where
    p ps = case ps of
      (x : y : zs) -> (x, y)
      (x : zs) -> (x, 0)

validIsbn13 :: String -> Bool
validIsbn13 isbn
  | length (digits isbn) /= 13 = False
  | otherwise = calc isbn `rem` 10 == 0
  where
    digits = map digitToInt . filter isDigit
    calc = sum . map (\(x, y) -> x + y * 3) . pair . digits

main :: IO ()
main =
  mapM_
    (printf "%s: Valid: %s\n" <*> (show . validIsbn13))
    [ "978-0596528126",
      "978-0596528120",
      "978-1788399081",
      "978-1788399083"
    ]
Output:
978-0596528126: Valid: True
978-0596528120: Valid: False
978-1788399081: Valid: True
978-1788399083: Valid: False

Or, expressed in terms of cycle:

import Data.Char (digitToInt, isDigit)

isISBN13 :: String -> Bool
isISBN13 =
  (0 ==)
    . flip rem 10
    . sum
    . flip
      (zipWith ((*) . digitToInt) . filter isDigit)
      (cycle [1, 3])

main :: IO ()
main =
  mapM_
    (print . ((,) <*> isISBN13))
    [ "978-0596528126",
      "978-0596528120",
      "978-1788399081",
      "978-1788399083"
    ]
Output:
("978-0596528126",True)
("978-0596528120",False)
("978-1788399081",True)
("978-1788399083",False)

IS-BASIC

100 PROGRAM "ISBN13.bas"
110 DO
120   PRINT :INPUT PROMPT "ISBN-13 code: ":IS$
130   IF IS$="" THEN EXIT DO
140   IF ISBN(IS$) THEN
150     PRINT "Ok."
160   ELSE
170     PRINT "CRC error."
180   END IF
190 LOOP
200 DEF TRIM$(S$)
210   LET T$=""
220   FOR I=1 TO LEN(S$)
230     IF S$(I)>CHR$(47) AND S$(I)<CHR$(58) THEN LET T$=T$&S$(I)
240   NEXT
250   LET TRIM$=T$
260 END DEF
270 DEF ISBN(S$)
280   LET SUM,ISBN=0:LET ISBN$=TRIM$(IS$)
290   IF LEN(ISBN$)<>13 THEN PRINT "Invalid length.":EXIT DEF
300   FOR I=1 TO 11 STEP 2
310     LET SUM=SUM+VAL(ISBN$(I))+VAL(ISBN$(I+1))*3
320   NEXT
330   LET SUM=SUM+VAL(ISBN$(13))
340   IF MOD(SUM,10)=0 THEN LET ISBN=-1
350 END DEF

J

   D            =:  '0123456789'
   
   isbn13c      =:  D&([ check@:i. clean)
     check      =:  0 = 10 | lc
       lc       =:  [ +/@:* weight
         weight =:  1 3 $~ #
     clean      =:  ] -. a. -. [
Output:
   isbn13c;._1 ' 978-0596528126 978-0596528120 978-1788399081 978-1788399083'
1 0 1 0

Java

public static void main(String[] args) {
    String[] isbn13s = {
        "978-0596528126",
        "978-0596528120",
        "978-1788399081",
        "978-1788399083"
    };
    for (String isbn13 : isbn13s)
        System.out.printf("%s %b%n", isbn13, validateISBN13(isbn13));
}

static boolean validateISBN13(String string) {
    int[] digits = digits(string.strip().replace("-", ""));
    return digits[12] == checksum(digits);
}

static int[] digits(String string) {
    int[] digits = new int[13];
    int index = 0;
    for (char character : string.toCharArray()) {
        if (character < '0' || character > '9')
            throw new IllegalArgumentException("Invalid ISBN-13");
        /* convert ascii to integer */
        digits[index++] = Character.digit(character, 10);
    }
    return digits;
}

static int checksum(int[] digits) {
    int total = 0;
    int index = 0;
    for (int digit : digits) {
        if (index == 12) break;
        if (index++ % 2 == 1) digit *= 3;
        total += digit;
    }
    return 10 - (total % 10);
}
978-0596528126 true
978-0596528120 false
978-1788399081 true
978-1788399083 false


An alternate demonstration

public static void main(){
        System.out.println(isISBN13("978-0596528126"));
        System.out.println(isISBN13("978-0596528120"));
        System.out.println(isISBN13("978-1788399081"));
        System.out.println(isISBN13("978-1788399083"));
    }
public static boolean isISBN13(String in){
        int pre = Integer.parseInt(in.substring(0,3));
        if (pre!=978)return false;
        String postStr = in.substring(4);
        if (postStr.length()!=10)return false;
        int post = Integer.parseInt(postStr);
        int sum = 38;
        for(int x = 0; x<10;x+=2)
        sum += (postStr.charAt(x)-48)*3 + ((postStr.charAt(x+1)-48));
        if(sum%10==0) return true;
        return false;
    }
Output:
true
false
true
false

jq

Works with: jq

Works with gojq, the Go implementation of jq

def isbn_check:
  def digits: tostring | explode | map( select(. >= 48 and . <= 57) | [.] | implode | tonumber);
  def sum(s): reduce s as $x (null; . + $x);
  digits
  | . as $digits
  |      sum(range(0;length;2) | $digits[.]) as $one
  | (3 * sum(range(1;length;2) | $digits[.])) as $two
  | (($one+$two) % 10) == 0;

def testingcodes:
 ["978-0596528126", "978-0596528120",
  "978-1788399081", "978-1788399083"];
 
testingcodes[]
| "\(.): \(if isbn_check then "good" else "bad" end)"
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Julia

function isbncheck(str)
    return sum(iseven(i) ? 3 * parse(Int, ch) : parse(Int, ch) 
        for (i, ch) in enumerate(replace(str, r"\D" => ""))) % 10 == 0
end

const testingcodes = ["978-0596528126", "978-0596528120",
                      "978-1788399081", "978-1788399083"]

for code in testingcodes
    println(code, ": ", isbncheck(code) ? "good" : "bad")
end
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

K

Works with: ngn/k
digits: {x[&(x>"/")&x<":"]-"0"}
isbn13c: 0=10!+/{x*(#x)#1 3} digits@

isbn13c "978-0596528126"
1
isbn13c "978-0596528120"
0
isbn13c " 978-1788399081"
1
isbn13c "978-1788399083"
0

Kotlin

fun isValidISBN13(text: String): Boolean {
    val isbn = text.replace(Regex("[- ]"), "")
    return isbn.length == 13 && isbn.map { it - '0' }
        .mapIndexed { index, value -> when (index % 2) { 0 -> value else -> 3 * value } }
        .sum() % 10 == 0
}

Tested using Spek

describe("ISBN Utilities") {
    mapOf(
        "978-0596528126" to true,
        "978-0596528120" to false,
        "978-1788399081" to true,
        "978-1788399083" to false
    ).forEach { (input, expected) ->
        it("returns $expected for $input") {
            println("$input: ${when(isValidISBN13(input)) { 
                true -> "good"
                else -> "bad"
            }}")
            assert(isValidISBN13(input) == expected)
        }
    }
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

langur

In this example, we map to multiple functions (actually 1 no-op).

val isbn13checkdigit = fn(var s) {
    s = replace(s, RE/[\- ]/)
    s -> re/^[0-9]{13}$/ and
        fold(fn{+}, map([_, fn{*3}], s2n(s))) div 10
}

val tests = {
    "978-0596528126": true,
    "978-0596528120": false,
    "978-1788399081": true,
    "978-1788399083": false,
}

for key of tests {
    val pass = isbn13checkdigit(key)
    write key, ": ", if(pass: "good"; "bad")
    writeln if(pass == tests[key]: ""; " (ISBN-13 CHECK DIGIT TEST FAILED)")
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Lua

Translation of: C
function checkIsbn13(isbn)
    local count = 0
    local sum = 0
    for c in isbn:gmatch"." do
        if c == ' ' or c == '-' then
            -- skip
        elseif c < '0' or '9' < c then
            return false
        else
            local digit = c - '0'
            if (count % 2) > 0 then
                sum = sum + 3 * digit
            else
                sum = sum + digit
            end
            count = count + 1
        end
    end

    if count ~= 13 then
        return false
    end
    return (sum % 10) == 0
end

function test(isbn)
    if checkIsbn13(isbn) then
        print(isbn .. ": good")
    else
        print(isbn .. ": bad")
    end
end

function main()
    test("978-0596528126")
    test("978-0596528120")
    test("978-1788399081")
    test("978-1788399083")
end

main()
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad


Mathematica / Wolfram Language

ClearAll[ValidISBNQ]
ValidISBNQ[iban_String] := Module[{i},
  i = StringReplace[iban, {" " -> "", "-" -> ""}];
  If[StringMatchQ[i, Repeated[DigitCharacter]],
   i = ToExpression /@ Characters[i];
   i[[2 ;; ;; 2]] *= 3;
   Mod[Total[i], 10] == 0
   ,
   False
   ]
  ]
ValidISBNQ["978-0596528126"]
ValidISBNQ["978-0596528120"]
ValidISBNQ["978-1788399081"]
ValidISBNQ["978-1788399083"]
Output:
True
False
True
False

MiniScript

This GUI implementation is for use with Mini Micro.

isISBN13 = function(n)
	n = n.replace("-","").replace(" ","")
	s = 0 
	for i in range(0, n.len-1,2)
		s += n[i].val
	end for
	for i in range(1, n.len-1,2)
		s += n[i].val * 3
	end for
	return not (s % 10)
end function

testValues = "978-0596528126 978-0596528120 978-1788399081 978-1788399083".split(" ")
for val in testValues
	print val + " " + ["bad", "good"][isISBN13(val)]
end for
Output:
978-0596528126 good
978-0596528120 bad
978-1788399081 good
978-1788399083 bad

Miranda

main :: [sys_message]
main = [Stdout (lay (map test tests))]

test :: [char]->[char]
test isbn = isbn ++ ": good", if isbn13 isbn
          = isbn ++ ": bad", otherwise

tests :: [[char]]
tests = ["978-0596528126",
         "978-0596528120",
         "978-1788399081",
         "978-1788399083"]

isbn13 :: [char]->bool
isbn13 str = False, if #isbn ~= 13 \/ ~and (map digit isbn)
           = check mod 10 = 0, otherwise
             where isbn = filter (~= '-') str
                   digits = map (numval.(:[])) isbn
                   check = sum (zipwith (*) digits (concat (repeat [1,3])))

uncurry :: (*->**->***)->(*,**)->***
uncurry f (a,b) = f a b

zipwith :: (*->**->***)->[*]->[**]->[***]
zipwith f a b = map (uncurry f) (zip2 a b)
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Modula-2

MODULE ISBN;
FROM InOut IMPORT WriteString, WriteLn;
FROM Strings IMPORT Length;

PROCEDURE validISBN(s: ARRAY OF CHAR): BOOLEAN;
    VAR total, i, length: CARDINAL;
BEGIN
    total := 0;
    length := Length(s);
    IF (length # 14) OR (s[3] # '-') THEN
        RETURN FALSE;
    END;
    FOR i := 0 TO length-1 DO
        IF i # 3 THEN
            IF (s[i] < '0') OR (s[i] > '9') THEN
                RETURN FALSE;
            ELSIF (i < 3) AND (i MOD 2 = 1) OR (i > 3) AND (i MOD 2 = 0) THEN
                total := total + 3 * (ORD(s[i]) - ORD('0'));
            ELSE
                total := total + ORD(s[i]) - ORD('0');
            END;
        END;
    END;
    RETURN total MOD 10 = 0;
END validISBN;

PROCEDURE check(s: ARRAY OF CHAR);
BEGIN
    WriteString(s);
    WriteString(': ');
    IF validISBN(s) THEN
        WriteString('good');
    ELSE
        WriteString('bad');
    END;
    WriteLn;
END check;

BEGIN
    check('978-0596528126');
    check('978-0596528120');
    check('978-1788399081');
    check('978-1788399083');
END ISBN.
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Nanoquery

Translation of: Go
def checkIsbn13(isbn)
        // remove any hyphens or spaces
        isbn = str(isbn).replace("-","").replace(" ","")

        // check length = 13
        if len(isbn) != 13
                return false
        end

        // check only contains digits and calculate weighted sum
        sum = 0
        for i in range(0, len(isbn) - 1)
                c = isbn[i]
                if (ord(c) < ord("0")) or (ord(c) > ord("9"))
                        return false
                end

                if (i % 2) = 0
                        sum += ord(c) - ord("0")
                else
                        sum += 3 * (ord(c) - ord("0"))
                end
        end

        return (sum % 10) = 0
end

isbns = {"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"}
for isbn in isbns
        res = "bad"
        if checkIsbn13(isbn)
                res = "good"
        end

        print format("%s: %s\n", isbn, res)
end
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Nim

import strutils, strformat

func is_isbn*(s: string): bool =
  var sum, len: int
  for c in s:
    if is_digit(c):
      sum += (ord(c) - ord('0')) * (if len mod 2 == 0: 1 else: 3)
      len += 1
    elif c != ' ' and c != '-':
      return false
  return (len == 13) and (sum mod 10 == 0)

when is_main_module:
  let isbns = [ "978-0596528126", "978-0596528120",
                "978-1788399081", "978-1788399083" ]
  for isbn in isbns:
    var quality: string = if is_isbn(isbn): "good" else: "bad"
    echo &"{isbn}: {quality}"
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

OmniMark

define switch function isbn (value stream vs-s) as
  local integer li-total initial {0}
  local integer li-check
  repeat scan vs-s
    ; Any digits in an ISBN-13 may be separated by a hyphen or space
    match (digit => lpv-1 ('-' | space)? digit => lpv-3 ('-' | space)? ) => p
      ; Add the first digit to the total and add the second multiplied by 3
      increment li-total by (lpv-1 + (lpv-3 * 3))
    match any => lpv-c
      ; This is always the thirteenth digit, the 'check' digit
      set li-check to lpv-c
  again
  do when 10 - li-total modulo 10 = li-check
    return true
  else when li-total modulo 10 = 0 and li-check = 0
    return true
  else
    return false
  done

process
  ; These are the first four test ISBN-13s from the problem description, with
  ; the first and third being valid and the second and fourth invalid.
  submit "978-0596528126"
  submit "978-0596528120"
  submit "978-1788399081"
  submit "978-1788399083"

  ; This ISBN-13 is from https://isbn-information.com/the-13-digit-isbn.html
  submit "978-1-86197-876-9"
  submit "978 1 86197 876 9"

  ; These ISBNs are from https://en.wikipedia.org/wiki/ISBN
  submit "978-3-16-148410-0"
  submit "978 3 16 148410 9" ; But this one has had the 'check' digit changed
  submit "This is valid: 978-0-306-40615-7^" ; Adding some extra complexity :)
  submit "978-03-0640-615 7." ; Formatted as "EAN-Group-Publisher-Title Check"

  ; These strings show how non-ISBN-13 numbers must be (and are) ignored.
  submit "This is a 14-digit number - 555-30640610555 - so it is ignored.%n"
  submit "This is an ISBN-10, so it is ignored too: 0-306-40615-2.%n"

; This find rule will only find a string that is an ISBN-13 (digit/-/space)
; combination.  It has been split into three lines to make it easier to see
; what is being done.
;   1. The string must either be preceded by any character other than a digit
;      or start the stream, and
;   2. It must then have 12 digits (with optional hyphens or spaces
;      intermingled) and must conclude with a digit, and
;   3. It either must not be followed by a another digit (making it more
;      than 13 digits, so not an ISBN-13) or the stream ends.
find ([any \ digit] | value-start) => lpv-s
     ((digit ('-' | space)?){12} digit) => lpv-n
     ([any \ digit] | value-end) => lpv-e
  output lpv-s || lpv-n || lpv-e
  do when isbn(lpv-n) = true
    output ' (good)%n'
  else 
    output ' (bad)%n'
  done

; output all other characters verbatim
find any => lpv-char
  output lpv-char
Output:
978-0596528126 (good)
978-0596528120 (bad)
978-1788399081 (good)
978-1788399083 (bad)
978-1-86197-876-9 (good)
978 1 86197 876 9 (good)
978-3-16-148410-0 (good)
978 3 16 148410 9 (bad)
This is valid: 978-0-306-40615-7^ (good)
978-03-0640-615 7. (good)
This is a 14-digit number - 555-30640610555 - so it is ignored.
This is an ISBN-10, so it is ignored too: 0-306-40615-2.

Pascal

Works with: Extended Pascal
program ISBNChecksum(output);

const
	codeIndexMaximum = 17;
	ISBNIndexMinimum = 1;
	ISBNIndexMaximum = 13;
	ISBNIndexRange = ISBNIndexMaximum - ISBNIndexMinimum + 1;

type
	code = string(codeIndexMaximum);
	codeIndex = 1..codeIndexMaximum value 1;
	decimalDigit = '0'..'9';
	decimalValue = 0..9;
	ISBNIndex = ISBNIndexMinimum..ISBNIndexMaximum;
	ISBN = array[ISBNIndex] of decimalDigit;

{ returns the integer value represented by a character }
function numericValue(protected c: decimalDigit): decimalValue;
begin
	{ in Pascal result variable bears the same name as the function }
	numericValue := ord(c) - ord('0')
end;

{ determines whether an ISBN is technically valid (checksum correct) }
function isValidISBN(protected n: ISBN): Boolean;
var
	sum: 0..225 value 0;
	i: ISBNIndex;
begin
	{ NB: in Pascal for-loop-limits are _inclusive_ }
	for i := ISBNIndexMinimum to ISBNIndexMaximum do
	begin
		{ alternating scale factor 3^0, 3^1 based on Boolean }
		sum := sum + numericValue(n[i]) * 3 pow ord(not odd(i))
	end;
	
	isValidISBN := sum mod 10 = 0
end;

{ transform '978-0-387-97649-5' into '9780387976495' }
function digits(n: code): code;
var
	sourceIndex, destinationIndex: codeIndex;
begin
	for sourceIndex := 1 to length(n) do
	begin
		if n[sourceIndex] in ['0'..'9'] then
		begin
			n[destinationIndex] := n[sourceIndex];
			destinationIndex := destinationIndex + 1
		end
	end;
	
	{ to alter a string’s length you need overwrite it completely }
	digits := subStr(n, 1, destinationIndex - 1)
end;

{ data type coercion }
function asISBN(protected n: code): ISBN;
var
	result: ISBN;
begin
	unpack(n[1..length(n)], result, 1);
	asISBN := result
end;

{ tells whether a string value contains a proper ISBN representation }
function isValidISBNString(protected hyphenatedForm: code): Boolean;
var
	digitOnlyForm: code;
begin
	digitOnlyForm := digits(hyphenatedForm);
	{ The Extended Pascal `and_then` Boolean operator allows us }
	{ to first check the length before invoking `isValidISBN`. }
	isValidISBNString := (length(digitOnlyForm) = ISBNIndexRange)
		and_then isValidISBN(asISBN(digitOnlyForm))
end;

{ === MAIN ============================================================= }
begin
	writeLn(isValidISBNString('978-0596528126'));
	writeLn(isValidISBNString('978-0596528120'));
	writeLn(isValidISBNString('978-1788399081'));
	writeLn(isValidISBNString('978-1788399083'))
end.
Output:
True
False
True
False

Perl

use strict;
use warnings;
use feature 'say';

sub check_digit {
    my($isbn) = @_; my($sum);
    $sum += (1,3)[$_%2] * (split '', join '', split /\D/, $isbn)[$_] for 0..11;
    (10 - $sum % 10) % 10;
}

for (<978-0596528126 978-0596528120 978-1788399081 978-1788399083 978-2-74839-908-0 978-2-74839-908-5>) {
    my($isbn,$check) = /(.*)(.)/;
    my $check_d = check_digit($isbn);
    say "$_ : " . ($check == $check_d ? 'Good' : "Bad check-digit $check; should be $check_d")
}
Output:
978-0596528126 : Good
978-0596528120 : Bad check-digit 9; should be 2
978-1788399081 : Good
978-1788399083 : Bad check-digit 3; should be 1
978-2-74839-908-0 : Good
978-2-74839-908-5 : Bad check-digit 5; should be 0

Phix

with javascript_semantics
procedure check_isbn13(string isbn)
    integer digits = 0, checksum = 0, w = 1
    for i=1 to length(isbn) do
        integer ch = isbn[i]
        if ch!=' ' and ch!='-' then
            ch -= '0'
            if ch<0 or ch>9 then checksum = 9 exit end if
            checksum += ch*w
            digits += 1
            w = 4-w
        end if
    end for
    checksum = remainder(checksum,10)
    string gb = iff(digits=13 and checksum=0 ? "good" : "bad")
    printf(1,"%s: %s\n",{isbn,gb})
end procedure
 
constant isbns = {"978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083",
                  "978-2-74839-908-0","978-2-74839-908-5","978 1 86197 876 9"}
for i=1 to length(isbns) do check_isbn13(isbns[i]) end for
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad
978-2-74839-908-0: good
978-2-74839-908-5: bad
978 1 86197 876 9: good

PicoLisp

(de isbn13? (S)
   (let L
      (make
         (for N (chop S)
            (and (format N) (link @)) ) )
      (and
         (= 13 (length L))
         (=0 (% (sum * L (circ 1 3)) 10)) ) ) )
(mapc
   '((A)
      (tab
         (-19 1)
         A
         (if (isbn13? A) 'ok 'fail) ) )
   (quote
      "978-0596528126"
      "978-0596528120"
      "978-1-86197-876-9"
      "978-2-74839-908-5"
      "978 1 86197 876 9" ) )
Output:
978-0596528126     ok
978-0596528120     fail
978-1-86197-876-9  ok
978-2-74839-908-5  fail
978 1 86197 876 9  ok

PL/M

100H:

CHECK$ISBN13: PROCEDURE (PTR) BYTE;
    DECLARE PTR ADDRESS, ISBN BASED PTR BYTE;
    DECLARE (I, F, T) BYTE;
    F = 1;
    T = 0;
    DO I = 0 TO 13;
        IF I = 3 THEN DO;
            /* THIRD CHAR SHOULD BE '-' */
            IF ISBN(I) <> '-' THEN RETURN 0;
        END;
        ELSE DO;
            /* DIGITS MUST BE VALID */
            IF ISBN(I) < '0' OR ISBN(I) > '9' THEN RETURN 0;
            T = T + (ISBN(I) - '0') * F;
            F = 4 - F; /* MULTIPLY BY 1 AND 3 ALTERNATELY */
        END;
    END;
    RETURN (T MOD 10) = 0;
END CHECK$ISBN13;

/* CP/M BDOS CALL */
BDOS: PROCEDURE (FUNC, ARG);
    DECLARE FUNC BYTE, ARG ADDRESS;
    GO TO 5;
END BDOS;

PRINT: PROCEDURE (STR);
    DECLARE STR ADDRESS;
    CALL BDOS(9, STR);
END PRINT;

/* TESTS */
DECLARE TEST (4) ADDRESS;
TEST(0) = .'978-0596528126$';
TEST(1) = .'978-0596528120$';
TEST(2) = .'978-1788399081$';
TEST(3) = .'978-1788399083$';

DECLARE I BYTE;
DO I = 0 TO LAST(TEST);
    CALL PRINT(TEST(I));
    CALL PRINT(.': $');
    IF CHECK$ISBN13(TEST(I)) THEN
        CALL PRINT(.'GOOD$');
    ELSE
        CALL PRINT(.'BAD$');
    CALL PRINT(.(13,10,'$'));
END;

CALL BDOS(0,0);
EOF
Output:
978-0596528126: GOOD
978-0596528120: BAD
978-1788399081: GOOD
978-1788399083: BAD

PowerShell

function Get-ISBN13 {
    $codes = (
        "978-0596528126",
        "978-0596528120",
        "978-1788399081",
        "978-1788399083"
    )

    foreach ($line in $codes) {

        $sum = $null
        $codeNoDash = $line.Replace("-","")

        for ($i = 0; $i -lt $codeNoDash.length; $i++) {

            if (($i % 2) -eq 1) {

                $sum += [decimal]$codeNoDash[$i] * 3
               
            }else {

                $sum += [decimal]$codeNoDash[$i]

            }
        }
        
        if (($sum % 10) -eq 0) {

            Write-Host "$line Good"
            
        }else {

            Write-Host "$line Bad"

        }
    }
}
Get-ISBN13
Output:
978-0596528126 Good
978-0596528120 Bad
978-1788399081 Good
978-1788399083 Bad

PureBasic

Macro TestISBN13(X)  
  Print(X) 
  If IsISBN13(X) : PrintN(" good") : Else : PrintN(" bad") : EndIf  
EndMacro

Procedure.b IsISBN13(ISBN13.s)  
  ISBN13=Left(ISBN13,3)+Mid(ISBN13,5)  
  If IsNAN(Val(ISBN13)) Or Len(ISBN13)<>13 : ProcedureReturn #False : EndIf
  Dim ISBN.s{1}(12) : PokeS(@ISBN(),ISBN13)  
  For i=0 To 11 Step 2 : sum+Val(ISBN(i))+Val(ISBN(i+1))*3 : Next
  sum+Val(ISBN(12))
  ProcedureReturn Bool(sum%10=0)
EndProcedure

If OpenConsole()
  TestISBN13("978-0596528126")
  TestISBN13("978-0596528120")
  TestISBN13("978-1788399081")
  TestISBN13("978-1788399083")
  Input()
EndIf
Output:
978-0596528126 good
978-0596528120 bad
978-1788399081 good
978-1788399083 bad

Python

def is_isbn13(n):
    n = n.replace('-','').replace(' ', '')
    if len(n) != 13:
        return False
    product = (sum(int(ch) for ch in n[::2]) 
               + sum(int(ch) * 3 for ch in n[1::2]))
    return product % 10 == 0

if __name__ == '__main__':
    tests = '''
978-0596528126
978-0596528120
978-1788399081
978-1788399083'''.strip().split()
    for t in tests:
        print(f"ISBN13 {t} validates {is_isbn13(t)}")
Output:
ISBN13 978-0596528126 validates True
ISBN13 978-0596528120 validates False
ISBN13 978-1788399081 validates True
ISBN13 978-1788399083 validates False


Or, expressed in terms of itertools.cycle

'''ISBN13 check digit'''


from itertools import cycle


# isISBN13 :: String -> Bool
def isISBN13(s):
    '''True if s is a valid ISBN13 string
    '''
    digits = [int(c) for c in s if c.isdigit()]
    return 13 == len(digits) and (
        0 == sum(map(
            lambda f, x: f(x),
            cycle([
                lambda x: x,
                lambda x: 3 * x
            ]),
            digits
        )) % 10
    )


# ------------------------- TEST -------------------------
# main :: IO ()
def main():
    '''Test strings for ISBN-13 validity.'''

    print('\n'.join(
        repr((s, isISBN13(s))) for s
        in ["978-0596528126",
            "978-0596528120",
            "978-1788399081",
            "978-1788399083"
            ]
    ))


# MAIN ---
if __name__ == '__main__':
    main()
Output:
('978-0596528126', True)
('978-0596528120', False)
('978-1788399081', True)
('978-1788399083', False)

Quackery

[ char 0 char 9 1+ within ]  is digit?    ( c --> b )

[ 1 & ]                      is odd?      ( n --> b )

[ [] swap ]'[ swap
  witheach [ 
    dup nested 
    unrot over do
    iff [ dip join ]
    else nip
  ] drop ]                   is filter    ( [ --> [ )

[ 0 swap
  witheach [
    char->n i^ odd?
    iff [ 3 * + ]
    else +
  ] ]                        is checksum  ( $ --> n )

[ filter digit?
  dup size 13 = not
  iff [ drop false ] done
  checksum 10 mod 0 = ]      is isbn      ( $ --> b )

[ dup echo$ say ": " isbn
  iff [ say "Good" ]
  else [ say "Bad" ] cr ]    is isbn-test ( $ -->   )

$ '978-0596528126' isbn-test
$ '978-0596528120' isbn-test
$ '978-1788399081' isbn-test
$ '978-1788399083' isbn-test
Output:
978-0596528126: Good
978-0596528120: Bad
978-1788399081: Good
978-1788399083: Bad

Racket

#lang racket/base

(define (isbn13-check-digit-valid? s)
  (zero? (modulo (for/sum ((i (in-naturals))
                           (d (regexp-replace* #px"[^[:digit:]]" s "")))
                   (* (- (char->integer d) (char->integer #\0))
                      (if (even? i) 1 3)))
                 10)))

(module+ test
  (require rackunit)
  (check-true (isbn13-check-digit-valid? "978-0596528126"))
  (check-false (isbn13-check-digit-valid? "978-0596528120"))
  (check-true (isbn13-check-digit-valid? "978-1788399081"))
  (check-false (isbn13-check-digit-valid? "978-1788399083")))
Output:

no output indicates tests pass

Raku

(formerly Perl 6)

Works with: Rakudo version 2019.11

Also test a value that has a zero check digit.

sub check-digit ($isbn) {
     (10 - (sum (|$isbn.comb(/<[0..9]>/)) »*» (1,3)) % 10).substr: *-1
}

{
    my $check = .substr(*-1);
    my $check-digit = check-digit .chop;
    say "$_ : ", $check == $check-digit ??
        'Good' !!
        "Bad check-digit $check; should be $check-digit"
} for words <
    978-0596528126
    978-0596528120
    978-1788399081
    978-1788399083
    978-2-74839-908-0
    978-2-74839-908-5
>;
Output:
978-0596528126 : Good
978-0596528120 : Bad check-digit 9; should be 2
978-1788399081 : Good
978-1788399083 : Bad check-digit 3; should be 1
978-2-74839-908-0 : Good
978-2-74839-908-5 : Bad check-digit 5; should be 0

Red

check_valid_isbn13: function [str] [
  is_digit: charset [#"0" - #"9"]
  remove-each i str [not pick is_digit i] ; remove non-digits

  either 13 = length? str [               ; reject strings of incorrect length
    sum: 0
    repeat i 13 [
      mul: either even? i [3] [1]         ; multiplier for odd/even digits
      sum: sum + (mul * to integer! to string! pick str i)
    ]
    
    zero? (sum % 10)                      ; check if remainder mod 10 is zero
  ] [
    false
  ]
]

; check given examples
foreach [str] ["978-0596528126" "978-0596528120" "978-1788399081" "978-1788399083"] [
  prin str
  prin " - "
  print check_valid_isbn13 str
]
Output:
978-0596528126 - true
978-0596528120 - false
978-1788399081 - true
978-1788399083 - false

Refal

$ENTRY Go {
    = <Test '978-0596528126'>
      <Test '978-0596528120'>
      <Test '978-1788399081'>
      <Test '978-1788399083'>
};

Test {
    e.X = <Prout e.X ': ' <ISBN e.X>>;
};

ISBN {
    e.X, <Remove '-' e.X>: e.DS,
         <CheckDigits e.DS>: {
            False = Bad;
            True = <ISBN1 e.DS>;
         };
};

ISBN1 {
    (13 s.Sum), <Mod s.Sum 10>: 0 = Good;
    (13 s.Sum) e.X = Bad;
    (s.N s.Sum) = Bad;

    (s.N s.Sum) s.D e.X,
        <+ s.N 1>: s.N1,
        <Numb s.D>: s.V,
        <Mod s.N 2>: {
            0 = <ISBN1 (s.N1 <+ s.Sum s.V>) e.X>;
            1 = <ISBN1 (s.N1 <+ s.Sum <* 3 s.V>>) e.X>;
        };

    e.X = <ISBN1 (0 0) e.X>;
};

Remove {
    s.1 = ;
    s.1 s.1 e.X = <Remove s.1 e.X>;
    s.1 s.2 e.X = s.2 <Remove s.1 e.X>;
};

CheckDigits {
    = True;
    s.D e.X, '0123456789': e.A s.D e.B = <CheckDigits e.X>;
    e.X = False;
};
Output:
978-0596528126: Good
978-0596528120: Bad
978-1788399081: Good
978-1788399083: Bad

REXX

A couple of additional checks were made to verify a correct length,   and also that the ISBN-13 code is all numerics   (with optional minus signs).

/*REXX pgm validates the check digit of an ISBN─13 code  (it may have embedded minuses).*/
parse arg $                                      /*obtain optional arguments from the CL*/
if $='' | if $=","  then $= '978-0596528126 978-0596528120 978-1788399081 978-1788399083'
@ISBN= "ISBN─13 code isn't"                      /*a literal used when displaying msgs. */
                                                 /* [↓]  remove all minuses from X code.*/
  do j=1  for words($);  y= word($,j)            /*obtain an ISBN─13 code from  $  list.*/
  x= space( translate(y, , '-'),  0)             /*remove all minus signs from the code.*/
  L= length(x)                                   /*obtain the length of the ISBN-13 code*/
  if L \== 13                   then do;  say @ISBN  '13 characters: '  x;  exit 13;   end
  if verify(x, 9876543210)\==0  then do;  say @ISBN  'numeric: '        x;  exit 10;   end
  sum= 0
          do k=1  for L;   #= substr(x, k, 1)    /*get a decimal digit from the X code. */
          if \(k//2)  then #= # * 3              /*multiply every other digit by three. */
          sum= sum + #                           /*add the digit (or product) to the SUM*/
          end   /*k*/

  if right(sum, 1)==0  then say '     ISBN-13 code '      x      "    is valid."
                       else say '     ISBN-13 code '      x      " isn't valid."
  end   /*j*/                                    /*stick a fork in it,  we're all done. */
output   when using the four default inputs:
     ISBN-13 code  9781734314502     is valid.
     ISBN-13 code  9781734314509  isn't valid.
     ISBN-13 code  9781788399081     is valid.
     ISBN-13 code  9781788399083  isn't valid.

Ring

load "stdlib.ring"

isbn = ["978-0596528126","978-0596528120", "978-1788399081", "978-1788399083","978-2-74839-908-0","978-2-74839-908-5","978 1 86197 876 9"]

for n = 1 to len(isbn)
    sum = 0
    isbnStr = isbn[n]
    isbnStr = substr(isbnStr,"-","")
    isbnStr = substr(isbnStr," ","")
    for m = 1 to len(isbnStr)
        if m%2 = 0
           num = 3*number(substr(isbnStr,m,1))
           sum = sum + num
        else
           num = number(substr(isbnStr,m,1))
           sum = sum + num
        ok
    next
    if sum%10 = 0
       see "" + isbn[n] + ": true" + nl
    else
       see "" + isbn[n] + ": bad" + nl
    ok
next

Output:

978-0596528126: true
978-0596528120: bad
978-1788399081: true
978-1788399083: bad
978-2-74839-908-0: true
978-2-74839-908-5: bad
978 1 86197 876 9: true

RPL

Works with: Halcyon Calc version 4.2.7
≪ → isbn
  ≪ 0 1 CF
     1 isbn SIZE FOR j
        IF "0123456789" isbn j DUP SUB POS THEN
           LAST 1 - 
           IF 1 FS?C THEN 3 * ELSE 1 SF END
           +
        END
     NEXT
   ≫
   10 MOD NOT   
≫
'ISBN?' STO
≪ { "978-0596528126" "978-0596528120" "978-1788399081" "978-1788399083" } → tests 
  ≪ 1 tests SIZE FOR j tests  GET ISBN? NEXT ≫
≫ EVAL
Output:
4: 1
3: 0
2: 1
1: 0

Ruby

def validISBN13?(str)
  cleaned = str.delete("^0-9").chars
  return false unless cleaned.size == 13
  cleaned.each_slice(2).sum{|d1, d2| d1.to_i + 3*d2.to_i }.remainder(10) == 0
end

isbns = ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"]
isbns.each{|isbn| puts "#{isbn}: #{validISBN13?(isbn)}" }
Output:
978-0596528126: true
978-0596528120: false
978-1788399081: true
978-1788399083: false

Rust

fn main() {
    let isbns = ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"];
    isbns.iter().for_each(|isbn| println!("{}: {}", isbn, check_isbn(isbn)));
}

fn check_isbn(isbn: &str) -> bool {
    if isbn.chars().filter(|c| c.is_digit(10)).count() != 13 {
            return false;
    } 
    let checksum = isbn.chars().filter_map(|c| c.to_digit(10))
        .zip([1, 3].iter().cycle())
        .fold(0, |acc, (val, fac)| acc + val * fac);
    checksum % 10 == 0
}
Output:
978-0596528126: true
978-0596528120: false
978-1788399081: true
978-1788399083: false

Seed7

$ include "seed7_05.s7i";

const func boolean: isISBN13 (in var string: input) is func
  result
    var boolean: isbn is FALSE;
  local
    var char: c is ' ';
    var integer: digit is 0;
    var integer: i is 1;
    var integer: sum is 0;
  begin
    input := replace(input, " ", "");
    input := replace(input, "-", "");
    if length(input) = 13 then
      for c range input do
        digit := ord(c) - 48;
        if not odd(i) then
          digit *:= 3;
        end if;
        sum +:= digit;
        incr(i);
      end for;
      isbn := sum rem 10 = 0;
    end if;
  end func;

const proc: main is func
  local
    var string: str is "";
  begin
    for str range [] ("978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083") do
      writeln(str <& ": " <& isISBN13(str));
    end for;
  end func;
Output:
978-0596528126: TRUE
978-0596528120: FALSE
978-1788399081: TRUE
978-1788399083: FALSE

SETL

program isbn13;
    loop for test in [
        "978-0596528126", "978-0596528120",
        "978-1788399081", "978-1788399083"
    ] do
        print(test + if valid_isbn13 test then ": good" else ": bad" end);
    end loop;

    op valid_isbn13(isbn);
        if #isbn /= 14
           or isbn(4) /= '-'
           or exists c in isbn | not c in "0123456789-" then
            return false;
        end if;

        m := 3;
        loop for ch in isbn | ch in "0123456789" do
            s +:= val ch * (m := 4 - m);
        end loop;
        return s mod 10 = 0;
    end op;
end program;
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Standard ML

Easy to read version

(* these type decorations are optional, you could just as well put:
  fun isValidISBN s =
*)
fun isValidISBN (s : string) : bool =
  let
    val digits = List.filter Char.isDigit (explode s)
    val nums = map (fn x => ord x - ord #"0") digits
    val len = length nums
    fun sumISBN [] = raise Domain
      | sumISBN [x] = x
      | sumISBN (x1::x2::xs) = x1 + 3*x2 + sumISBN xs
  in
    len = 13 andalso sumISBN nums mod 10 = 0
  end

val test = ["978-1734314502", "978-1734314509",
            "978-1788399081", "978-1788399083"]

fun printTest (s : string) : unit =
  (print s; print (if isValidISBN s then ": good\n" else ": bad\n"))

fun main () = app printTest test

Original version

let
  fun check (c, p as (m, n)) =
    if Char.isDigit c then
      (not m, (if m then 3 * (ord c - 48) else ord c - 48) + n)
    else
      p
in
  fun checkISBN s =
    Int.rem (#2 (CharVector.foldl check (false, 0) s), 10) = 0
end

val test = ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"]
val () = (print
          o concat
          o map (fn s => s ^ (if checkISBN s then ": good\n" else ": bad\n"))) test
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Swift

func checkISBN(isbn: String) -> Bool {
  guard !isbn.isEmpty else {
    return false
  }

  let sum = isbn
    .compactMap({ $0.wholeNumberValue })
    .enumerated()
    .map({ $0.offset & 1 == 1 ? 3 * $0.element : $0.element })
    .reduce(0, +)

  return sum % 10 == 0
}

let cases = [
  "978-0596528126",
  "978-0596528120",
  "978-1788399081",
  "978-1788399083"
]

for isbn in cases {
  print("\(isbn) => \(checkISBN(isbn: isbn) ? "good" : "bad")")
}


Output:
978-0596528126 => good
978-0596528120 => bad
978-1788399081 => good
978-1788399083 => bad

Tcl

proc validISBN13 code {
   regsub -all {\D} $code "" code ;# remove non-digits
   if {[string length $code] == 13} {
      set sum 0
      set fac 1
      foreach digit [split $code ""] {
         set sum [expr {$sum + $digit * $fac}]
         set fac [expr {$fac == 1? 3: 1}]
      }
      if {$sum % 10 == 0} {return true}
   }
   return false
}
foreach test {
   978-0596528126
   978-0596528120
   978-1788399081
   978-1788399083
} {puts $test:[validISBN13 $test]}
Output:
978-0596528126:true
978-0596528120:false
978-1788399081:true
978-1788399083:false

Simpler variant, using two loop vars and constant factors; same output:

proc validISBN13 code {
   regsub -all {\D} $code "" code ;# remove non-digits
   if {[string length $code] == 13} {
      set sum 0
      foreach {d1 d2} [split $code ""] {
         if {$d2 eq ""} {set d2 0} ;# last round
         set sum [expr {$sum + $d1 + $d2 * 3}]
      }
      if {$sum % 10 == 0} {return true}
   }
   return false
}

uBasic/4tH

Works with: version 3.64.0
a := "978-0596528126" 
Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-0596528120"
Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-1788399081"
Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-1788399083"
Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

End

_IsISBN
  Param (1)
  Local (4)

  c@ = 0 : e@ = 1                      ' set sum and multiplier

  For b@ = Len (a@) - 1 To 0 Step -1   ' scan string in reverse
    d@ = Peek(a@, b@) - Ord ("0")      ' get character
    If ((d@ < 0) + (d@ > 9)) = 0 Then c@ = c@ + (d@ * e@)
    e@ = (e@ * 3) % 8                  ' evaluate character, change multiplier
  Next

Return ((c@ % 10) = 0)                 ' modulus 10 must be zero
Output:
978-0596528126  good
978-0596528120  bad
978-1788399081  good
978-1788399083  bad

0 OK, 0:339

UNIX Shell

Works with: Bourne Again Shell
check_isbn13 () {
    local i n t
    n=${1//[^0-9]/}
    t=0
    for ((i=0; i<${#n}; ++i )); do
      (( t += ${n:i:1}*(1 + ((i&1)<<1)) ))
    done
    (( 0 == t % 10 ))
}

for isbn in 978-0596528126 978-0596528120 978-1788399081 978-1788399083; do
  printf '%s: ' "$isbn"
  if check_isbn13 "$isbn"; then
    printf '%s\n' OK
  else
    printf '%s\n' 'NOT OK'
  fi
done
Output:
978-0596528126: OK
978-0596528120: NOT OK
978-1788399081: OK
978-1788399083: NOT OK

Visual Basic .NET

Translation of: C#
Module Module1

    Function CheckISBN13(code As String) As Boolean
        code = code.Replace("-", "").Replace(" ", "")
        If code.Length <> 13 Then
            Return False
        End If
        Dim sum = 0
        For Each i_d In code.Select(Function(digit, index) (index, digit))
            Dim index = i_d.index
            Dim digit = i_d.digit
            If Char.IsDigit(digit) Then
                sum += (Asc(digit) - Asc("0")) * If(index Mod 2 = 0, 1, 3)
            Else
                Return False
            End If
        Next
        Return sum Mod 10 = 0
    End Function

    Sub Main()
        Console.WriteLine(CheckISBN13("978-0596528126"))
        Console.WriteLine(CheckISBN13("978-0596528120"))
        Console.WriteLine(CheckISBN13("978-1788399081"))
        Console.WriteLine(CheckISBN13("978-1788399083"))
    End Sub

End Module
Output:
True
False
True
False

V (Vlang)

Translation of: Go
fn check_isbn13(isbn13 string) bool {
    // remove any hyphens or spaces
    isbn := isbn13.replace('-','').replace(' ','')
    // check length == 13
    le := isbn.len
    if le != 13 {
        return false
    }
    // check only contains digits and calculate weighted sum
    mut sum := 0
    for i, c in isbn.split('') {
        if c.int() < '0'.int() || c.int() > '9'.int() {
            return false
        }
        if i%2 == 0 {
            sum += c.int() - '0'.int()
        } else {
            sum += 3 * (c.int() - '0'.int())
        }
    }
    return sum%10 == 0
}
 
fn main() {
    isbns := ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"]
    for isbn in isbns {
        mut res := "bad"
        if check_isbn13(isbn) {
            res = "good"
        }
        println("$isbn: $res")
    }
}
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad

Wren

var isbn13 = Fn.new { |s|
    var cps = s.codePoints
    var digits = []
    // extract digits
    for (i in 0...cps.count) {
        var c = cps[i]
        if (c >= 48 && c <= 57) digits.add(c)
    }
    // do calcs
    var sum = 0
    for (i in 0...digits.count) {
        var d = digits[i] - 48
        sum = sum + ((i%2 == 0) ? d : 3 * d)
    }
    return sum % 10 == 0
}

var tests = ["978-0596528126", "978-0596528120", "978-1788399081", "978-1788399083"]
for (test in tests) {
    System.print("%(test) -> %(isbn13.call(test) ? "good" : "bad")")
}
Output:
978-0596528126 -> good
978-0596528120 -> bad
978-1788399081 -> good
978-1788399083 -> bad

XPL0

include xpllib;         \contains StrLen function

proc ISBN13(Str);       \Show if International Standard Book Number is good
char Str;
int Sum, Cnt, Dig, I;
[Sum:= 0;  Cnt:= 0;
for I:= 0 to StrLen(Str)-1 do
    [Dig:= Str(I) - ^0;
    if Dig>=0 & Dig<=9 then
        [Sum:= Sum + Dig;
         Cnt:= Cnt + 1;
         if (Cnt&1) = 0 then
                Sum:= Sum + Dig + Dig;
        ];
    ];
Text(0, Str);
Text(0, if rem(Sum/10)=0 & Cnt=13 then ": good" else ": bad");
CrLf(0);
];

[ISBN13("978-0596528126");
 ISBN13("978-0596528120");
 ISBN13("978-1788399081");
 ISBN13("978-1788399083");
 ISBN13("978-1-59327-220-3");
 ISBN13("978-178839918");
]
Output:
978-0596528126: good
978-0596528120: bad
978-1788399081: good
978-1788399083: bad
978-1-59327-220-3: good
978-178839918: bad

zkl

fcn ISBN13_check(isbn){  // "978-0596528126", throws on invalid digits
   var [const] one3=("13"*6 + 1).split("").apply("toInt"); // examine 13 digits
   // one3=("13"*6) if you want to calculate what the check digit should be
   one3.zipWith('*,isbn - " -").sum(0) % 10 == 0
}
isbns:=
#<<<"
    978-0596528126
    978-0596528120
    978-1788399081
    978-1788399083
    978-2-74839-908-0
    978-2-74839-908-5".split("\n");
#<<<
foreach isbn in (isbns)
   { println(isbn.strip(),"  ",ISBN13_check(isbn) and " Good" or " Bad") }
Output:
978-0596528126   Good
978-0596528120   Bad
978-1788399081   Good
978-1788399083   Bad
978-2-74839-908-0   Good
978-2-74839-908-5   Bad