ISBN13 check digit: Difference between revisions
add →Pascal |
Added solution for Action! |
||
Line 198:
C:\>isbn13 978-1788399083
bad</pre>
=={{header|Action!}}==
{{libheader|Action! Tool Kit}}
<lang 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-1734314502")
Test("978-1734314509")
Test("978-1788399081")
Test("978-1788399083")
RETURN</lang>
{{out}}
[https://gitlab.com/amarok8bit/action-rosetta-code/-/raw/master/images/ISBN13_check_digit.png Screenshot from Atari 8-bit computer]
<pre>
978-1734314502 is correct
978-1734314509 is incorrect
978-1788399081 is correct
978-1788399083 is incorrect
</pre>
=={{header|Ada}}==
|
Revision as of 18:38, 25 November 2021
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.
Use the following codes for testing:
- 978-1734314502 (good)
- 978-1734314509 (bad)
- 978-1788399081 (good)
- 978-1788399083 (bad)
Show output here, on this page
- See also
-
- for details: 13-digit ISBN method of validation. (installs cookies.)
11l
<lang 11l>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-1734314502
978-1734314509 978-1788399081 978-1788399083’.split("\n")
L(t) tests
print(‘ISBN13 ’t‘ validates ’is_isbn13(t))</lang>
- Output:
ISBN13 978-1734314502 validates 1B ISBN13 978-1734314509 validates 0B ISBN13 978-1788399081 validates 1B ISBN13 978-1788399083 validates 0B
8080 Assembly
<lang 8080asm> 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$'</lang>
- Output:
A>isbn13 978-1734314502 good A>isbn13 978-1734314509 bad A>isbn13 978-1788399081 good A>isbn13 978-1788399083 bad
8086 Assembly
<lang asm> 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$'</lang>
- Output:
C:\>isbn13 978-1734314502 good C:\>isbn13 978-1734314509 bad C:\>isbn13 978-1788399081 good C:\>isbn13 978-1788399083 bad
Action!
<lang 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-1734314502") Test("978-1734314509") Test("978-1788399081") Test("978-1788399083")
RETURN</lang>
- Output:
Screenshot from Atari 8-bit computer
978-1734314502 is correct 978-1734314509 is incorrect 978-1788399081 is correct 978-1788399083 is incorrect
Ada
<lang 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-1734314502"); Show ("978-1734314509"); Show ("978-1788399081"); Show ("978-1788399083");
end ISBN_Check;</lang>
- Output:
978-1734314502 Good 978-1734314509 Bad 978-1788399081 Good 978-1788399083 Bad
ALGOL 68
<lang algol68>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-1734314502", "978-1734314509", "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</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
APL
<lang apl>check_isbn13←{
13≠⍴n←(⍵∊⎕D)/⍵:0 0=10|(⍎¨n)+.×13⍴1 3
}</lang>
- Output:
check_isbn13¨ '978-1734314502' '978-1734314509' '978-1788399081' '978-1788399083' 1 0 1 0
AppleScript
Composition of pure functions
<lang applescript>-------------------- 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-1734314502", "978-1734314509", ¬ "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</lang>
- Output:
{{"978-1734314502", true}, {"978-1734314509", 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:
<lang applescript>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-1734314502", "978-1734314509", "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</lang>
- Output:
"978-1734314502: good 978-1734314509: 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).
<lang applescript>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</lang>
Arturo
<lang rebol>validISBN?: function [isbn][
currentCheck: to :integer to :string last isbn isbn: map split chop replace isbn "-" "" => [to :integer]
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-1734314502" "978-1734314509" "978-1788399081" "978-1788399083"
]
loop tests 'test [
print [test "=>" validISBN? test]
]</lang>
- Output:
978-1734314502 => true 978-1734314509 => false 978-1788399081 => true 978-1788399083 => false
AutoHotkey
<lang 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)") }</lang> Examples:<lang AutoHotkey>output := "" nums := ["978-1734314502","978-1734314509","978-1788399081","978-1788399083"] for i, n in nums output .= ISBN13_check_digit(n) "`n" MsgBox % output return</lang>
- Output:
978-1734314502 (good) 978-1734314509 (bad) 978-1788399081 (good) 978-1788399083 (bad)
AWK
<lang AWK>
- syntax: GAWK -f ISBN13_CHECK_DIGIT.AWK
BEGIN {
arr[++n] = "978-1734314502" arr[++n] = "978-1734314509" 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))
} </lang>
- Output:
978-1734314502 OK 978-1734314509 NG check digit S/B 2 978-1788399081 OK 978-1788399083 NG check digit S/B 1 9780820424521 OK 0820424528 NG length
BCPL
<lang 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-1734314502")
show("978-1734314509") show("978-1788399081") show("978-1788399083")
$)</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
C
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"}; for (i = 0; i < 4; ++i) { printf("%s: %s\n", isbns[i], check_isbn13(isbns[i]) ? "good" : "bad"); } return 0;
}</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
C++
<lang cpp>#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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" }; for (auto isbn : isbns) { std::cout << isbn << ": " << (check_isbn13(isbn) ? "good" : "bad") << '\n'; }
return 0;
}</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
C#
<lang csharp>using System; using System.Linq;
public class Program {
public static void Main() { Console.WriteLine(CheckISBN13("978-1734314502")); Console.WriteLine(CheckISBN13("978-1734314509")); 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; } }
}</lang>
- Output:
True False True False
COBOL
<lang COBOL> ******************************************************************
* 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-1734314502'. 02 FILLER PIC X(14) VALUE '978-1734314509'. 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.
</lang>
- Output:
978-1734314502 (good) 978-1734314509 (bad) 978-1788399081 (good) 978-1788399083 (bad)
Cowgol
<lang 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-1734314502", "978-1734314509", "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;</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
D
<lang d>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-1734314502")); assert(!isValidISBN13("978-1734314509")); assert(isValidISBN13("978-1788399081")); assert(!isValidISBN13("978-1788399083"));
}</lang>
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)
<lang lisp>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 ) )
)</lang>
and also assuming the following generic bindings in the Name Manager for the WorkBook:
<lang lisp>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) )
)</lang>
- Output:
fx | =ISBN13Check(A2) | ||
---|---|---|---|
A | B | ||
1 | Candidate string | ISBN13 checked | |
2 | 978-1734314502 | TRUE | |
3 | 978-1734314509 | FALSE | |
4 | 978-1788399081 | TRUE | |
5 | 978-1788399083 | FALSE |
Factor
<lang 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-1734314502 978-1734314509 978-1788399081 978-1788399083 } [ dup isbn13? "good" "bad" ? "%s: %s\n" printf ] each</lang>
- Output:
978-1734314502: good 978-1734314509: 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. <lang>: 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
- </lang>
- Output:
In Forth, a "true" value is indicated by "-1".
s" 978-1734314502" isbn? . -1 ok s" 978-1734314509" isbn? . 0 ok s" 978-1788399081" isbn? . -1 ok s" 978-1788399083" isbn? . 0 ok
Fortran
<lang fortran> program isbn13
implicit none
character(len=14), dimension(4), parameter :: isbns=["978-1734314502", "978-1734314509", "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 </lang>
- Output:
978-1734314502 : good 978-1734314509 : bad 978-1788399081 : good 978-1788399083 : bad
FreeBASIC
<lang 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-1734314502", "978-1734314509", "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</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
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, However they run on execution servers. By default remote servers are used, but they are limited in memory and processing power, since they are intended for demonstration and casual use. A local server can be downloaded and installed, it has no limitations (it runs in your own computer). Because of that, example programs can be fully visualized and edited, but some of them will not run if they require a moderate or heavy computation/memory resources, and no local server is being used.
In this page you can see the program(s) related to this task and their results.
Go
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"} for _, isbn := range isbns { res := "bad" if checkIsbn13(isbn) { res = "good" } fmt.Printf("%s: %s\n", isbn, res) }
}</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Haskell
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" ]</lang>
- Output:
978-1734314502: Valid: True 978-1734314509: Valid: False 978-1788399081: Valid: True 978-1788399083: Valid: False
Or, expressed in terms of cycle:
<lang haskell>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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" ]</lang>
- Output:
("978-1734314502",True) ("978-1734314509",False) ("978-1788399081",True) ("978-1788399083",False)
IS-BASIC
<lang 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</lang>
J
<lang j> D =: '0123456789'
isbn13c =: D&([ check@:i. clean) check =: 0 = 10 | lc lc =: [ +/@:* weight weight =: 1 3 $~ # clean =: ] -. a. -. [</lang>
- Output:
<lang j> isbn13c;._1 ' 978-1734314502 978-1734314509 978-1788399081 978-1788399083' 1 0 1 0</lang>
Java
<lang java>public static void main(){
System.out.println(isISBN13("978-1734314502")); System.out.println(isISBN13("978-1734314509")); 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; }
</lang>
- Output:
true false true false
jq
Works with gojq, the Go implementation of jq <lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"];
testingcodes[] | "\(.): \(if isbn_check then "good" else "bad" end)" </lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Julia
<lang 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-1734314502", "978-1734314509",
"978-1788399081", "978-1788399083"]
for code in testingcodes
println(code, ": ", isbncheck(code) ? "good" : "bad")
end
</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Kotlin
<lang 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
} </lang>
Tested using Spek <lang Kotlin> describe("ISBN Utilities") {
mapOf( "978-1734314502" to true, "978-1734314509" 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) } }
} </lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
langur
In this example, we map to multiple functions (actually 1 no-op). <lang langur>val .isbn13checkdigit = f(var .s) {
.s = replace(.s, RE/[\- ]/) matching(re/^[0-9]{13}$/, .s) and fold(f{+}, map [_, f{x 3}], s2n .s) div 10
}
val .tests = h{
"978-1734314502": true, "978-1734314509": 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)")
}</lang>
In this example, we set a for loop value as it progresses. <lang langur>val .isbn13checkdigit = f(var .s) {
.s = replace(.s, RE/[\- ]/) var .alt = true matching(re/^[0-9]{13}$/, .s) and for[=0] .d in s2n(.s) { _for += if(not= .alt: .d x 3; .d) } div 10
}
val .tests = h{
"978-1734314502": true, "978-1734314509": 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)")
}</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Lua
<lang lua>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-1734314502") test("978-1734314509") test("978-1788399081") test("978-1788399083")
end
main()</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Mathematica / Wolfram Language
<lang Mathematica>ClearAll[ValidISBNQ] ValidISBNQ[iban_String] := Module[{i},
i = StringReplace[iban, {" " -> "", "-" -> ""}]; If[StringMatchQ[i, Repeated[DigitCharacter]], i = ToExpression /@ Characters[i]; i2 ;; ;; 2 *= 3; Mod[Total[i], 10] == 0 , False ] ]
ValidISBNQ["978-1734314502"] ValidISBNQ["978-1734314509"] ValidISBNQ["978-1788399081"] ValidISBNQ["978-1788399083"]</lang>
- Output:
True False True False
Nanoquery
<lang nanoquery>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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"} for isbn in isbns
res = "bad" if checkIsbn13(isbn) res = "good" end
print format("%s: %s\n", isbn, res)
end</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Nim
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" ] for isbn in isbns: var quality: string = if is_isbn(isbn): "good" else: "bad" echo &"{isbn}: {quality}"</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Pascal
<lang 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-1734314502')); writeLn(isValidISBNString('978-1734314509')); writeLn(isValidISBNString('978-1788399081')); writeLn(isValidISBNString('978-1788399083')) end.</lang>
- Output:
True False True False
Perl
<lang 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-1734314502 978-1734314509 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")
}</lang>
- Output:
978-1734314502 : Good 978-1734314509 : 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
<lang Phix>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-1734314502", "978-1734314509", "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</lang>
- Output:
978-1734314502: good 978-1734314509: 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
<lang 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-1734314502" "978-1734314509" "978-1-86197-876-9" "978-2-74839-908-5" "978 1 86197 876 9" ) )</lang>
- Output:
978-1734314502 ok 978-1734314509 fail 978-1-86197-876-9 ok 978-2-74839-908-5 fail 978 1 86197 876 9 ok
PL/M
<lang plm>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-1734314502$'; TEST(1) = .'978-1734314509$'; 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</lang>
- Output:
978-1734314502: GOOD 978-1734314509: BAD 978-1788399081: GOOD 978-1788399083: BAD
PureBasic
<lang 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-1734314502") TestISBN13("978-1734314509") TestISBN13("978-1788399081") TestISBN13("978-1788399083") Input()
EndIf</lang>
- Output:
978-1734314502 good 978-1734314509 bad 978-1788399081 good 978-1788399083 bad
Python
<lang 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-1734314502 978-1734314509 978-1788399081 978-1788399083.strip().split()
for t in tests: print(f"ISBN13 {t} validates {is_isbn13(t)}")</lang>
- Output:
ISBN13 978-1734314502 validates True ISBN13 978-1734314509 validates False ISBN13 978-1788399081 validates True ISBN13 978-1788399083 validates False
Or, expressed in terms of itertools.cycle
<lang python>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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" ] ))
- MAIN ---
if __name__ == '__main__':
main()
</lang>
- Output:
('978-1734314502', True) ('978-1734314509', False) ('978-1788399081', True) ('978-1788399083', False)
Quackery
<lang 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-1734314502' isbn-test $ '978-1734314509' isbn-test $ '978-1788399081' isbn-test $ '978-1788399083' isbn-test</lang>
- Output:
978-1734314502: Good 978-1734314509: Bad 978-1788399081: Good 978-1788399083: Bad
Racket
<lang 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-1734314502")) (check-false (isbn13-check-digit-valid? "978-1734314509")) (check-true (isbn13-check-digit-valid? "978-1788399081")) (check-false (isbn13-check-digit-valid? "978-1788399083")))</lang>
- Output:
no output indicates tests pass
Raku
(formerly Perl 6)
Also test a value that has a zero check digit.
<lang perl6>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-1734314502 978-1734314509 978-1788399081 978-1788399083 978-2-74839-908-0 978-2-74839-908-5
>;</lang>
- Output:
978-1734314502 : Good 978-1734314509 : 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
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). <lang rexx>/*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-1734314502 978-1734314509 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. */</lang>
- 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
<lang ring> load "stdlib.ring"
isbn = ["978-1734314502","978-1734314509", "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 </lang> Output:
978-1734314502: true 978-1734314509: 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
Ruby
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] isbns.each{|isbn| puts "#{isbn}: #{validISBN13?(isbn)}" }
</lang>
- Output:
978-1734314502: true 978-1734314509: false 978-1788399081: true 978-1788399083: false
Rust
<lang rust>fn main() {
let isbns = ["978-1734314502", "978-1734314509", "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
} </lang>
- Output:
978-1734314502: true 978-1734314509: false 978-1788399081: true 978-1788399083: false
Seed7
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083") do writeln(str <& ": " <& isISBN13(str)); end for; end func;</lang>
- Output:
978-1734314502: TRUE 978-1734314509: FALSE 978-1788399081: TRUE 978-1788399083: FALSE
Standard ML
<lang sml>local
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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] val () = (print
o concat o map (fn s => s ^ (if checkISBN s then ": good\n" else ": bad\n"))) test</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad
Swift
<lang 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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"
]
for isbn in cases {
print("\(isbn) => \(checkISBN(isbn: isbn) ? "good" : "bad")")
}</lang>
- Output:
978-1734314502 => good 978-1734314509 => bad 978-1788399081 => good 978-1788399083 => bad
Tcl
<lang 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-1734314502 978-1734314509 978-1788399081 978-1788399083
} {puts $test:[validISBN13 $test]} </lang>
- Output:
978-1734314502:true 978-1734314509:false 978-1788399081:true 978-1788399083:false
Simpler variant, using two loop vars and constant factors; same output: <lang tcl> 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
} </lang>
uBasic/4tH
<lang>a := "978-1734314502" Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))
a := "978-1734314509" 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</lang>
- Output:
978-1734314502 good 978-1734314509 bad 978-1788399081 good 978-1788399083 bad 0 OK, 0:339
UNIX Shell
<lang sh>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-1734314502 978-1734314509 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</lang>
- Output:
978-1734314502: OK 978-1734314509: NOT OK 978-1788399081: OK 978-1788399083: NOT OK
Visual Basic .NET
<lang vbnet>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-1734314502")) Console.WriteLine(CheckISBN13("978-1734314509")) Console.WriteLine(CheckISBN13("978-1788399081")) Console.WriteLine(CheckISBN13("978-1788399083")) End Sub
End Module</lang>
- Output:
True False True False
Wren
<lang ecmascript>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-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] for (test in tests) {
System.print("%(test) -> %(isbn13.call(test) ? "good" : "bad")")
}</lang>
- Output:
978-1734314502 -> good 978-1734314509 -> bad 978-1788399081 -> good 978-1788399083 -> bad
XPL0
<lang 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-1734314502");
ISBN13("978-1734314509"); ISBN13("978-1788399081"); ISBN13("978-1788399083"); ISBN13("978-1-59327-220-3"); ISBN13("978-178839918");
]</lang>
- Output:
978-1734314502: good 978-1734314509: bad 978-1788399081: good 978-1788399083: bad 978-1-59327-220-3: good 978-178839918: bad
zkl
<lang zkl>fcn ISBN13_check(isbn){ // "978-1734314502", 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
}</lang> <lang zkl>isbns:=
- <<<"
978-1734314502 978-1734314509 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") }</lang>
- Output:
978-1734314502 Good 978-1734314509 Bad 978-1788399081 Good 978-1788399083 Bad 978-2-74839-908-0 Good 978-2-74839-908-5 Bad
- Programming Tasks
- Solutions by Programming Task
- 11l
- 8080 Assembly
- 8086 Assembly
- Action!
- Action! Tool Kit
- Ada
- ALGOL 68
- APL
- AppleScript
- Arturo
- AutoHotkey
- AWK
- BCPL
- C
- C++
- C sharp
- COBOL
- Cowgol
- D
- Excel
- Factor
- Forth
- Fortran
- FreeBASIC
- Fōrmulæ
- Go
- Haskell
- IS-BASIC
- J
- Java
- Jq
- Julia
- Kotlin
- Langur
- Lua
- Mathematica
- Wolfram Language
- Nanoquery
- Nim
- Pascal
- Perl
- Phix
- PicoLisp
- PL/M
- PureBasic
- Python
- Quackery
- Racket
- Raku
- REXX
- Ring
- Ruby
- Rust
- Seed7
- Standard ML
- Swift
- Tcl
- UBasic/4tH
- UNIX Shell
- Visual Basic .NET
- Wren
- XPL0
- Zkl