CUSIP: Difference between revisions
Drkameleon (talk | contribs) |
No edit summary |
||
Line 4,208: | Line 4,208: | ||
68389X106 -> incorrect |
68389X106 -> incorrect |
||
68389X105 -> correct</pre> |
68389X105 -> correct</pre> |
||
=={{header|Vlang}}== |
|||
{{trans|Go}} |
|||
<lang vlang>fn is_cusip(s string) bool { |
|||
if s.len != 9 { return false } |
|||
mut sum := 0 |
|||
for i in 0..8 { |
|||
c := s[i] |
|||
mut v :=0 |
|||
match true { |
|||
c >= '0'[0] && c <= '9'[0] { |
|||
v = c - 48 |
|||
} |
|||
c >= 'A'[0] && c <= 'Z'[0] { |
|||
v = c - 55 |
|||
} |
|||
c == '*'[0] { |
|||
v = 36 |
|||
} |
|||
c == '@'[0] { |
|||
v = 37 |
|||
} |
|||
c == '#'[0] { |
|||
v = 38 |
|||
} |
|||
else { |
|||
return false |
|||
} |
|||
} |
|||
if i % 2 == 1 { v *= 2 } // check if odd as using 0-based indexing |
|||
sum += v/10 + v%10 |
|||
} |
|||
return int(s[8]) - 48 == (10 - (sum%10)) % 10 |
|||
} |
|||
fn main() { |
|||
candidates := [ |
|||
"037833100", |
|||
"17275R102", |
|||
"38259P508", |
|||
"594918104", |
|||
"68389X106", |
|||
"68389X105", |
|||
] |
|||
for candidate in candidates { |
|||
mut b :=' ' |
|||
if is_cusip(candidate) { |
|||
b = "correct" |
|||
} else { |
|||
b = "incorrect" |
|||
} |
|||
println("$candidate -> $b") |
|||
} |
|||
}</lang> |
|||
{{out}} |
|||
<pre> |
|||
037833100 -> correct |
|||
17275R102 -> correct |
|||
38259P508 -> correct |
|||
594918104 -> correct |
|||
68389X106 -> incorrect |
|||
68389X105 -> correct |
|||
</pre> |
|||
=={{header|Wren}}== |
=={{header|Wren}}== |
Revision as of 15:53, 31 May 2022
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at CUSIP. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
A CUSIP is a nine-character alphanumeric code that identifies a North American financial security for the purposes of facilitating clearing and settlement of trades. The CUSIP was adopted as an American National Standard under Accredited Standards X9.6.
- Task
Ensure the last digit (i.e., the check digit) of the CUSIP code (the 1st column) is correct, against the following:
- 037833100 Apple Incorporated
- 17275R102 Cisco Systems
- 38259P508 Google Incorporated
- 594918104 Microsoft Corporation
- 68389X106 Oracle Corporation (incorrect)
- 68389X105 Oracle Corporation
- Example pseudo-code below.
<lang>algorithm Cusip-Check-Digit(cusip) is
Input: an 8-character CUSIP
sum := 0 for 1 ≤ i ≤ 8 do c := the ith character of cusip if c is a digit then v := numeric value of the digit c else if c is a letter then p := ordinal position of c in the alphabet (A=1, B=2...) v := p + 9 else if c = "*" then v := 36 else if c = "@" then v := 37 else if' c = "#" then v := 38 end if if i is even then v := v × 2 end if
sum := sum + int ( v div 10 ) + v mod 10 repeat return (10 - (sum mod 10)) mod 10
end function</lang>
- See related tasks
11l
<lang 11l>F cusip_check(=cusip)
I cusip.len != 9 X ValueError(‘CUSIP must be 9 characters’)
cusip = cusip.uppercase() V total = 0 L(i) 8 V v = 0 V c = cusip[i] I c.is_digit() v = Int(c) E I c.is_alpha() V p = c.code - ‘A’.code + 1 v = p + 9 E I c == ‘*’ v = 36 E I c == ‘@’ v = 37 E I c == ‘#’ v = 38
I i % 2 != 0 v *= 2
total += v I/ 10 + v % 10 V check = (10 - (total % 10)) % 10 R String(check) == cusip.last
V codes = [‘037833100’,
‘17275R102’, ‘38259P508’, ‘594918104’, ‘68389X106’, ‘68389X105’]
L(code) codes
print(code‘: ’(I cusip_check(code) {‘valid’} E ‘invalid’))</lang>
- Output:
037833100: valid 17275R102: valid 38259P508: valid 594918104: valid 68389X106: invalid 68389X105: valid
360 Assembly
<lang 360asm>* CUSIP 07/06/2018 CUSIP CSECT
USING CUSIP,R13 base register B 72(R15) skip savearea DC 17F'0' savearea SAVE (14,12) save previous context ST R13,4(R15) link backward ST R15,8(R13) link forward LR R13,R15 set addressability LA R6,1 i=1 DO WHILE=(C,R6,LE,=F'6') do i=1 to 6 LR R1,R6 i MH R1,=H'9' *9 LA R4,T-9(R1) @t(i) MVC X,0(R4) x=t(i) SR R10,R10 w=0 LA R7,1 j=1 DO WHILE=(C,R7,LE,=F'8') do j=1 to 8 LA R14,X-1 x AR R14,R7 j MVC Y(1),0(R14) y=substr(x,j,1) LA R9,L'XX z=length(xx) LA R8,1 k=1 DO WHILE=(C,R8,LE,=A(L'XX)) do k=1 to length(xx) LA R4,XX-1 xx AR R4,R8 k MVC C(1),0(R4) c=substr(xx,k,1) IF CLC,Y(1),EQ,C THEN if y=c then LR R9,R8 k BCTR R9,0 z=k-1 ENDIF , endif LA R8,1(R8) k++ ENDDO , enddo k LR R4,R7 j LA R1,2 2 SRDA R4,32 ~ DR R4,R1 j/2=0 IF LTR,R4,Z,R4 THEN if j//2=0 then AR R9,R9 z=z+z ENDIF , endif LR R4,R9 z LA R1,10 10 SRDA R4,32 ~ DR R4,R1 r4=z//10 ; r5=z/10 AR R10,R5 w+z/10 AR R10,R4 w=w+z/10+z//10 LA R7,1(R7) j++ ENDDO , enddo j LR R4,R10 w LA R1,10 10 SRDA R4,32 ~ DR R4,R1 w/10 LA R2,10 10 SR R2,R4 10-w//10 SRDA R2,32 ~ DR R2,R1 /10 STC R2,U u=(10-w//10)//10 OI U,X'F0' bin to char IF CLC,U,EQ,X+8 THEN if u=substr(x,9,1) then MVC OK,=CL3' ' ok=' ' ELSE , else MVC OK,=C'nt' ok='nt' ENDIF , endif MVC PG+6(9),X output x MVC PG+18(3),OK output ok XPRNT PG,L'PG print LA R6,1(R6) i++ ENDDO , enddo i L R13,4(0,R13) restore previous savearea pointer RETURN (14,12),RC=0 restore registers from calling sav
XX DC CL39'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' U DS CL1 Y DS CL1 C DS CL1 T DC CL9'037833100',CL9'17275R102',CL9'38259P508'
DC CL9'594918104',CL9'68389X106',CL9'68389X105'
X DS CL9 OK DS CL3 PG DC CL80'CUSIP ......... is... valid'
YREGS END CUSIP</lang>
- Output:
CUSIP 037833100 is valid CUSIP 17275R102 is valid CUSIP 38259P508 is valid CUSIP 594918104 is valid CUSIP 68389X106 isn't valid CUSIP 68389X105 is valid
Action!
<lang Action!>INCLUDE "D2:CHARTEST.ACT" ;from the Action! Tool Kit
BYTE FUNC Verify(CHAR ARRAY code)
BYTE i,c,v CARD sum
IF code(0)#9 THEN RETURN (0) ELSEIF IsDigit(code(1))=0 THEN RETURN (0) FI
sum=0 FOR i=2 TO code(0) DO c=code(i) IF IsDigit(c) THEN v=c-'0 ELSEIF IsAlpha(c) THEN v=ToUpper(c)-'A+10 ELSEIF c='* THEN v=36 ELSEIF c='@ THEN v=37 ELSEIF c='# THEN v=38 ELSE RETURN (0) FI
IF (i&1)=0 THEN v==*2 FI
sum==+v/10+v MOD 10 OD
v=(10-(sum MOD 10)) MOD 10 IF v#code(1)-'0 THEN RETURN (0) FI
RETURN (1)
PROC Test(CHAR ARRAY code)
Print(code) IF Verify(code) THEN PrintE(" is valid") ELSE PrintE(" is invalid") FI
RETURN
PROC Main()
Put(125) PutE() ;clear the screen Test("037833100") Test("17275R102") Test("38259P508") Test("594918104") Test("68389X106") Test("68389X105")
RETURN</lang>
- Output:
Screenshot from Atari 8-bit computer
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 is invalid 68389X105 is valid
Ada
<lang Ada>with Ada.Text_IO;
procedure Cusip_Test is
use Ada.Text_IO;
subtype Cusip is String (1 .. 9);
function Check_Cusip (Code : Cusip) return Boolean is Sum : Integer := 0; V : Integer;
begin for I in Code'First .. Code'Last - 1 loop case Code (I) is when '0' .. '9' => V := Character'Pos (Code (I)) - Character'Pos ('0'); when 'A' .. 'Z' => V := Character'Pos (Code (I)) - Character'Pos ('A') + 10; when '*' => V := 36; when '@' => V := 37; when '#' => V := 38; when others => return False; end case;
if I mod 2 = 0 then V := V * 2; end if;
Sum := Sum + V / 10 + (V mod 10); end loop;
return (10 - (Sum mod 10)) mod 10 = Character'Pos (Code (Code'Last)) - Character'Pos ('0'); end Check_Cusip;
type Cusip_Array is array (Natural range <>) of Cusip;
Test_Array : Cusip_Array := ("037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105");
begin
for I in Test_Array'Range loop Put (Test_Array (I) & ": "); if Check_Cusip (Test_Array (I)) then Put_Line ("valid"); else Put_Line ("not valid"); end if; end loop;
end Cusip_Test;</lang>
- Output:
037833100: valid 17275R102: valid 38259P508: valid 594918104: valid 68389X106: not valid 68389X105: valid
ALGOL 68
<lang algol68>BEGIN
# returns TRUE if cusip is a valid CUSIP code # OP ISCUSIP = ( STRING cusip )BOOL: IF ( UPB cusip - LWB cusip ) /= 8 THEN # code is wrong length # FALSE ELSE # string is 9 characters long - check it is valid # STRING cusip digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#"[ AT 0 ]; INT check digit := 0; IF NOT char in string( cusip[ UPB cusip ], check digit, cusip digits ) THEN # invalid check digit # FALSE ELSE # OK so far compare the calculated check sum to the supplied one # INT sum := 0; INT c pos := LWB cusip - 1; FOR i TO 8 DO INT digit := 0; IF NOT char in string( cusip[ i + c pos ], digit, cusip digits ) THEN # invalid digit # digit := -999 FI; IF NOT ODD i THEN # even digit # digit *:= 2 FI; sum +:= ( digit OVER 10 ) + ( digit MOD 10 ) OD; ( 10 - ( sum MOD 10 ) ) MOD 10 = check digit FI FI ; # ISCUSIP #
# task test cases #
PROC test cusip = ( STRING cusip )VOID: print( ( cusip, IF ISCUSIP cusip THEN " valid" ELSE " invalid" FI, newline ) );
test cusip( "037833100" ); test cusip( "17275R102" ); test cusip( "38259P508" ); test cusip( "594918104" ); test cusip( "68389X106" ); test cusip( "68389X105" )
END</lang>
- Output:
037833100 valid 17275R102 valid 38259P508 valid 594918104 valid 68389X106 invalid 68389X105 valid
ALGOL W
Based on Algol 68 <lang algolw>begin % returns true if cusip is a valid CUSIP code %
logical procedure isCusip ( string(9) value cusip ) ; begin % returns the base 39 digit corresponding to a character of a CUSIP code % integer procedure cusipDigit( string(1) value cChar ) ; if cChar >= "0" and cChar <= "9" then ( decode( cChar ) - decode( "0" ) ) else if cChar >= "A" and cChar <= "Z" then ( decode( cChar ) - decode( "A" ) ) + 10 else if cChar = "*" then 36 else if cChar = "@" then 37 else if cChar = "#" then 38 else % invalid digit % -999 ;
integer checkDigit, sum; checkDigit := cusipDigit( cusip( 8 // 1 ) ); for cPos := 1 until 8 do begin integer digit; digit := cusipDigit( cusip( ( cPos - 1 ) // 1 ) ); if not odd( cPos ) then digit := digit * 2; sum := sum + ( digit div 10 ) + ( digit rem 10 ) end for_cPos ; ( ( 10 - ( sum rem 10 ) ) rem 10 ) = checkDigit end isCusip ;
begin % task test cases % procedure testCusip ( string(9) value cusip ) ; write( s_w := 0, cusip, if isCusip( cusip ) then " valid" else " invalid" );
testCusip( "037833100" ); testCusip( "17275R102" ); testCusip( "38259P508" ); testCusip( "594918104" ); testCusip( "68389X106" ); testCusip( "68389X105" ) end testCases
end.</lang>
- Output:
037833100 valid 17275R102 valid 38259P508 valid 594918104 valid 68389X106 invalid 68389X105 valid
AppleScript
<lang applescript>use AppleScript version "2.4" use framework "Foundation" use scripting additions
-- isCusip :: String -> Bool
on isCusip(s)
set cs to "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*&#" set ns to mapMaybe(elemIndex(cs), s) script go on |λ|(f, x) set fx to apply(f, x) (fx div 10) + (fx mod 10) end |λ| end script 9 = length of ns and item -1 of ns = (10 - (sum(zipWith(go, ¬ cycle({my identity, my double}), ¬ take(8, ns))) mod 10)) mod 10
end isCusip
TEST ---------------------------
on run
script test on |λ|(s) s & " -> " & isCusip(s) end |λ| end script unlines(map(test, ¬ {"037833100", "17275R102", "38259P508", ¬ "594918104", "68389X106", "68389X105"}))
end run
-- 037833100 -> true -- 17275R102 -> true -- 38259P508 -> true -- 594918104 -> true -- 68389X106 -> false -- 68389X105 -> true
GENERIC FUNCTIONS --------------------
-- Just :: a -> Maybe a on Just(x)
-- Constructor for an inhabited Maybe (option type) value. -- Wrapper containing the result of a computation. {type:"Maybe", Nothing:false, Just:x}
end Just
-- Nothing :: Maybe a
on Nothing()
-- Constructor for an empty Maybe (option type) value. -- Empty wrapper returned where a computation is not possible. {type:"Maybe", Nothing:true}
end Nothing
-- Tuple (,) :: a -> b -> (a, b)
on Tuple(a, b)
-- Constructor for a pair of values, possibly of two different types. {type:"Tuple", |1|:a, |2|:b, length:2}
end Tuple
-- apply ($) :: (a -> b) -> a -> b
on apply(f, x)
-- The value of f(x) mReturn(f)'s |λ|(x)
end apply
-- 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
-- double :: Num -> Num
on double(x)
2 * x
end double
-- elemIndex :: Eq a => [a] -> a -> Maybe Int
on elemIndex(xs)
script on |λ|(x) set lng to length of xs repeat with i from 1 to lng if x = (item i of xs) then return Just(i - 1) end repeat return Nothing() end |λ| end script
end elemIndex
-- identity :: a -> a
on identity(x)
-- The argument unchanged. x
end identity
-- 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
-- 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
-- The mapMaybe function is a version of map which can throw out
-- elements. In particular, the functional argument returns
-- something of type Maybe b. If this is Nothing, no element is
-- added on to the result list. If it just Just b, then b is
-- included in the result list.
-- mapMaybe :: (a -> Maybe b) -> [a] -> [b]
on mapMaybe(mf, xs)
script property g : mReturn(mf) on |λ|(a, x) set mb to g's |λ|(x) if Nothing of mb then a else a & (Just of mb) end if end |λ| end script foldl(result, {}, xs)
end mapMaybe
-- 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
-- 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
-- unlines :: [String] -> String
on unlines(xs)
-- A single string formed by the intercalation -- of a list of strings with the newline character. set {dlm, my text item delimiters} to ¬ {my text item delimiters, linefeed} set str to xs as text set my text item delimiters to dlm str
end unlines
-- 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:
037833100 -> true 17275R102 -> true 38259P508 -> true 594918104 -> true 68389X106 -> false 68389X105 -> true
Arturo
<lang rebol>validCUSIP?: function [cusip][
s: 0 alpha: `A`..`Z`
loop.with:'i chop cusip 'c [ v: 0
case ø when? [numeric? c] -> v: to :integer to :string c when? [in? c alpha] -> v: (index alpha c) + 1 + 9 when? [c = `*`] -> v: 36 when? [c = `@`] -> v: 37 when? [c = `#`] -> v: 38 else []
if odd? i -> v: 2 * v
s: s + (v / 10) + (v % 10) ] check: (10 - (s % 10)) % 10
return check = to :integer to :string last cusip
]
loop ["037833100" "17275R102" "38259P508" "594918104" "68389X106" "68389X105"] 'cusip [
print [cusip "=>" (validCUSIP? cusip)? -> "VALID" -> "INVALID"]
]</lang>
- Output:
037833100 => VALID 17275R102 => VALID 38259P508 => VALID 594918104 => VALID 68389X106 => INVALID 68389X105 => VALID
AutoHotkey
<lang AutoHotkey>Cusip_Check_Digit(cusip){
sum := 0, i := 1, x := StrSplit(cusip) while (i <= 8) { c := x[i] if c is digit v := c else if c is alpha v := Asc(c) - 64 + 9 else if (c = "*") v := 36 else if (c = "@") v := 37 else if (c = "#") v := 38 if (i/2 = Floor(i/2)) v *= 2 sum += Floor(v/10) + Mod(v, 10) i++ } return (Mod(10 - Mod(sum, 10), 10) = x[9])
}</lang> Examples:<lang AutoHotkey>data = ( 037833100 17275R102 38259P508 594918104 68389X106 68389X105 )
output := "Cusip`t`tValid`n" loop, Parse, data, `n, `r
output .= A_LoopField "`t" Cusip_Check_Digit(A_LoopField) "`n"
MsgBox % output</lang>
- Output:
Cusip Valid 037833100 1 17275R102 1 38259P508 1 594918104 1 68389X106 0 68389X105 1
AWK
<lang AWK>
- syntax: GAWK -f CUSIP.AWK
BEGIN {
n = split("037833100,17275R102,38259P508,594918104,68389X106,68389X105",arr,",") for (i=1; i<=n; i++) { printf("%9s %s\n",arr[i],cusip(arr[i])) } exit(0)
} function cusip(n, c,i,sum,v,x) {
- returns: 1=OK, 0=NG, -1=bad data
if (length(n) != 9) { return(-1) } for (i=1; i<=8; i++) { c = substr(n,i,1) if (c ~ /[0-9]/) { v = c } else if (c ~ /[A-Z]/) { v = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",c) + 9 } else if (c == "*") { v = 36 } else if (c == "@") { v = 37 } else if (c == "#") { v = 38 } else { return(-1) } if (i ~ /[02468]/) { v *= 2 } sum += int(v / 10) + (v % 10) } x = (10 - (sum % 10)) % 10 return(substr(n,9,1) == x ? 1 : 0)
} </lang>
- Output:
037833100 1 17275R102 1 38259P508 1 594918104 1 68389X106 0 68389X105 1
BCPL
<lang bcpl>get "libhdr"
let validcusip(c) = valof $( let sum = 0
unless c%0 = 9 resultis false for i = 1 to 8 do $( let v = ( 2 - (i & 1) ) * valof $( test '0' <= c%i <= '9' then resultis c%i - '0' or test 'A' <= c%i <= 'Z' then resultis 10 + c%i - 'A' or test c%i = '**' then resultis 36 or test c%i = '@' then resultis 37 or test c%i = '#' then resultis 38 else resultis -1 $) sum := sum + v/10 + v rem 10 $) resultis (10 - (sum rem 10)) rem 10 = c%9 - '0'
$)
let show(c) be
writef("%S: %Svalid*N", c, validcusip(c) -> "", "in")
let start() be $( show("037833100")
show("17275R102") show("38259P508") show("594918104") show("68389X106") show("68389X105")
$)</lang>
- Output:
037833100: valid 17275R102: valid 38259P508: valid 594918104: valid 68389X106: invalid 68389X105: valid
C
Reads CUSIP strings from a file and prints results to console, usage printed on incorrect invocation. <lang C>
- include<stdlib.h>
- include<stdio.h>
int cusipCheck(char str[10]){ int sum=0,i,v;
for(i=0;i<8;i++){ if(str[i]>='0'&&str[i]<='9') v = str[i]-'0'; else if(str[i]>='A'&&str[i]<='Z') v = (str[i] - 'A' + 10); else if(str[i]=='*') v = 36; else if(str[i]=='@') v = 37; else if(str[i]=='#') v = 38; if(i%2!=0) v*=2;
sum += ((int)(v/10) + v%10); } return ((10 - (sum%10))%10); }
int main(int argC,char* argV[]) { char cusipStr[10];
int i,numLines;
if(argC==1) printf("Usage : %s <full path of CUSIP Data file>",argV[0]);
else{ FILE* fp = fopen(argV[1],"r");
fscanf(fp,"%d",&numLines);
printf("CUSIP Verdict\n"); printf("-------------------");
for(i=0;i<numLines;i++){
fscanf(fp,"%s",cusipStr);
printf("\n%s : %s",cusipStr,(cusipCheck(cusipStr)==(cusipStr[8]-'0'))?"Valid":"Invalid"); }
fclose(fp); } return 0; } </lang> Input file :
6 037833100 17275R102 38259P508 594918104 68389X106 68389X105
Invocation and output :
C:\rosettaCode>cusipCheck.exe cusipData.txt CUSIP Verdict ------------------- 037833100 : Valid 17275R102 : Valid 38259P508 : Valid 594918104 : Valid 68389X106 : Invalid 68389X105 : Valid
C#
<lang csharp>using System; using System.Collections.Generic;
namespace CUSIP {
class Program { static bool IsCusip(string s) { if (s.Length != 9) return false; int sum = 0; for (int i = 0; i <= 7; i++) { char c = s[i];
int v; if (c >= '0' && c <= '9') { v = c - 48; } else if (c >= 'A' && c <= 'Z') { v = c - 55; // lower case letters apparently invalid } else if (c == '*') { v = 36; } else if (c == '#') { v = 38; } else { return false; } if (i % 2 == 1) v *= 2; // check if odd as using 0-based indexing sum += v / 10 + v % 10; } return s[8] - 48 == (10 - (sum % 10)) % 10; }
static void Main(string[] args) { List<string> candidates = new List<string>() { "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" }; foreach (var candidate in candidates) { Console.WriteLine("{0} -> {1}", candidate, IsCusip(candidate) ? "correct" : "incorrect"); } } }
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
C++
<lang cpp>#include <iostream>
- include <vector>
bool isCusip(const std::string& s) {
if (s.size() != 9) return false;
int sum = 0; for (int i = 0; i <= 7; ++i) { char c = s[i];
int v; if ('0' <= c && c <= '9') { v = c - '0'; } else if ('A' <= c && c <= 'Z') { v = c - '@'; } else if (c = '*') { v = 36; } else if (c = '#') { v = 38; } else { return false; } if (i % 2 == 1) { v *= 2; } sum += v / 10 + v % 10; } return s[8] - '0' == (10 - (sum % 10)) % 10;
}
int main() {
using namespace std;
vector<string> candidates{ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" };
for (auto str : candidates) { auto res = isCusip(str) ? "correct" : "incorrect"; cout << str.c_str() << " -> " << res << "\n"; }
return 0;
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Caché ObjectScript
<lang cos>Class Utils.Check [ Abstract ] {
ClassMethod CUSIP(x As %String) As %Boolean { SET x=$TRANSLATE(x," ") // https://leiq.bus.umich.edu/res_codes_cusip.htm IF x'?8UNP1N QUIT 0 SET cd=$EXTRACT(x,*), x=$EXTRACT(x,1,*-1), t=0 FOR i=1:1:$LENGTH(x) { SET n=$EXTRACT(x,i) IF n'=+n SET n=$CASE(n,"*":36,"@":37,"#":38,:$ASCII(n)-55) IF i#2=0 SET n=n*2 SET t=t+(n\10)+(n#10) } QUIT cd=((10-(t#10))#10) }
}</lang>
- Examples:
USER>For { Read s Quit:s="" Write ": "_##class(Utils.Check).CUSIP(s), ! } 037833100: 1 17275R102: 1 38259P508: 1 594918104: 1 68389X106: 0 68389X105: 1 USER>
Clojure
<lang Clojure> (defn- char->value
"convert the given char c to a value used to calculate the cusip check sum" [c] (let [int-char (int c)] (cond (and (>= int-char (int \0)) (<= int-char (int \9))) (- int-char 48) (and (>= int-char (int \A)) (<= int-char (int \Z))) (- int-char 55) (= c \*) 36 (= c \@) 37 (= c \#) 38 :else nil)))
(defn- calc-sum
"Calculate cusip sum. nil is returned for an invalid cusip." [cusip] (reduce (fn [sum [i c]] (if-let [v (char->value c)] (let [v (if (= (mod i 2) 1) (* v 2) v)] (+ sum (int (+ (/ v 10) (mod v 10))))) (reduced nil))) 0 (map-indexed vector (subs cusip 0 8))))
(defn calc-cusip-checksum
"Given a valid 8 or 9 digit cusip, return the 9th checksum digit" [cusip] (when (>= (count cusip) 8) (let [sum (calc-sum cusip)] (when sum (mod (- 10 (mod sum 10)) 10)))))
(defn is-valid-cusip9?
"predicate validating a 9 digit cusip." [cusip9] (when-let [checksum (and (= (count cusip9) 9) (calc-cusip-checksum cusip9))] (= (- (int (nth cusip9 8)) 48) checksum)))
(defn rosetta-output
"show some nice output for the Rosetta Wiki" [] (doseq [cusip ["037833100" "17275R102" "38259P508" "594918104" "68389X106" "68389X105" "EXTRACRD8" "EXTRACRD9" "BADCUSIP!" "683&9X106" "68389x105" "683$9X106" "68389}105" "87264ABE4"]] (println cusip (if (is-valid-cusip9? cusip) "valid" "invalid"))))
</lang>
- Output:
(rosetta-output) 037833100 valid 17275R102 valid 38259P508 valid 594918104 valid 68389X106 invalid 68389X105 valid EXTRACRD8 invalid EXTRACRD9 valid BADCUSIP! invalid 683&9X106 invalid 68389x105 invalid 683$9X106 invalid 68389}105 invalid 87264ABE4 valid
CLU
<lang clu>valid_cusip = proc (s: string) returns (bool)
own chars: string := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#" if string$size(s) ~= 9 then return(false) end sum: int := 0 for i: int in int$from_to(1,8) do v: int := string$indexc(s[i], chars)-1 if v<0 then return(false) end if i//2=0 then v := v*2 end sum := sum + v/10 + v//10 end check: int := (10 - sum // 10) // 10 return(check = string$indexc(s[9], chars)-1)
end valid_cusip
start_up = proc ()
po: stream := stream$primary_output() cusips: array[string] := array[string]$[ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" ] for cusip: string in array[string]$elements(cusips) do stream$puts(po, cusip || ": ") if valid_cusip(cusip) then stream$putl(po, "valid") else stream$putl(po, "invalid") end end
end start_up</lang>
- Output:
037833100: valid 17275R102: valid 38259P508: valid 594918104: valid 68389X106: invalid 68389X105: valid
Common Lisp
<lang lisp>(defun char->value (c)
(cond ((digit-char-p c 36)) ((char= c #\*) 36) ((char= c #\@) 37) ((char= c #\#) 38) (t (error "Invalid character: ~A" c))))
(defun cusip-p (cusip)
(and (= 9 (length cusip)) (loop for i from 1 to 8 for c across cusip for v = (char->value c) when (evenp i) do (setf v (* 2 v)) sum (multiple-value-bind (quot rem) (floor v 10) (+ quot rem)) into sum finally (return (eql (digit-char-p (char cusip 8)) (mod (- 10 (mod sum 10)) 10))))))
(defun main ()
(dolist (cusip '("037833100" "17275R102" "38259P508" "594918104" "68389X106" "68389X105")) (format t "~A: ~A~%" cusip (cusip-p cusip))))</lang>
- Output:
037833100: T 17275R102: T 38259P508: T 594918104: T 68389X106: NIL 68389X105: T
D
<lang D>import std.stdio;
void main(string[] args) {
writeln("CUSIP Verdict"); foreach(arg; args[1..$]) { writefln("%9s : %s", arg, isValidCusip(arg) ? "Valid" : "Invalid"); }
}
class IllegalCharacterException : Exception {
this(string msg) { super(msg); }
}
bool isValidCusip(string cusip) in {
assert(cusip.length == 9, "Incorrect cusip length");
} body {
try { auto check = cusipCheckDigit(cusip); return cusip[8] == ('0' + check); } catch (IllegalCharacterException e) { return false; }
}
unittest {
// Oracle Corporation assertEquals(isValidCusip("68389X105"), true);
// Oracle Corporation (invalid) assertEquals(isValidCusip("68389X106"), false);
}
int cusipCheckDigit(string cusip) in {
assert(cusip.length == 9, "Incorrect cusip length");
} body {
int sum; for (int i=0; i<8; ++i) { char c = cusip[i]; int v;
switch(c) { case '0': .. case '9': v = c - '0'; break; case 'A': .. case 'Z': v = c - 'A' + 10; break; case '*': v = 36; break; case '@': v = 37; break; case '#': v = 38; break; default: throw new IllegalCharacterException("Saw character: " ~ c); } if (i%2 == 1) { v = 2 * v; }
sum = sum + (v / 10) + (v % 10); }
return (10 - (sum % 10)) % 10;
}
unittest {
// Apple Incorporated assertEquals(cusipCheckDigit("037833100"), 0);
// Cisco Systems assertEquals(cusipCheckDigit("17275R102"), 2);
// Google Incorporated assertEquals(cusipCheckDigit("38259P508"), 8);
// Microsoft Corporation assertEquals(cusipCheckDigit("594918104"), 4);
// Oracle Corporation assertEquals(cusipCheckDigit("68389X105"), 5);
}
version(unittest) {
void assertEquals(T)(T actual, T expected) { import core.exception; import std.conv; if (actual != expected) { throw new AssertError("Actual [" ~ to!string(actual) ~ "]; Expected [" ~ to!string(expected) ~ "]"); } }
}
/// Invoke with `cusip 037833100 17275R102 38259P508 594918104 68389X106 68389X105`</lang>
- Output:
CUSIP Verdict 037833100 : Valid 17275R102 : Valid 38259P508 : Valid 594918104 : Valid 68389X106 : Invalid 68389X105 : Valid
Dyalect
<lang dyalect>func isCusip(s) {
if s.Length() != 9 { return false } var sum = 0 for i in 0..7 { var c = s[i] var v = match c { '0'..'9' => c.Order() - 48, 'A'..'Z' => c.Order() - 55, '*' => 36, '@' => 37, '#' => 38, _ => false } if i % 2 == 1 { v *= 2 } sum += v / 10 + v % 10 } s[8].Order() - 48 == (10 - (sum % 10)) % 10
}
var candidates = [
"037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"
]
for candidate in candidates {
var b = if isCusip(candidate) { "correct" } else { "incorrect" } print("\(candidate) -> \(b)")
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Excel
LAMBDA
Binding the names ISCUSIP and CUSIPMAP to the following lambda expressions in the Name Manager of the Excel WorkBook:
(See LAMBDA: The ultimate Excel worksheet function)
<lang lisp>=LAMBDA(s,
LET( ns, VLOOKUP( CHARS(s), CUSIPMAP, 2, FALSE ), AND( 9 = COLUMNS(ns), LET( firstEight, INITCOLS(ns), ixs, SEQUENCE(1, 8), evensDoubled, IF(ISEVEN(ixs), 2 * INDEX(firstEight, 1, ixs), INDEX(firstEight, 1, ixs) ), LASTCOL(ns) = MOD( 10 - MOD( SUM( QUOTIENT(evensDoubled, 10), MOD(evensDoubled, 10) ), 10 ), 10 ) ) ) )
)
CUSIPMAP
={"0",0;"1",1;"2",2;"3",3;"4",4;"5",5;"6",6;"7",7;"8",8;"9",9;"A",
10;"B",11;"C",12;"D",13;"E",14;"F",15;"G",16;"H",17;"I",18;"J",19;"K",
20;"L",21;"M",22;"N",23;"O",24;"P",25;"Q",26;"R",27;"S",28;"T",29;"U",
30;"V",31;"W",32;"X",33;"Y",34;"Z",35;"*",36;"@",37;"#",38}</lang>
and also assuming the following generic bindings in the Name Manager for the WorkBook:
<lang lisp>CHARS =LAMBDA(s,
MID( s, SEQUENCE(1, LEN(s), 1, 1), 1 )
)
INITCOLS
=LAMBDA(xs,
INDEX( xs, SEQUENCE( 1, COLUMNS(xs) - 1, 1, ) )
)
LASTCOL
=LAMBDA(xs,
INDEX( xs, SEQUENCE(ROWS(xs), 1, 1, 1), COLUMNS(xs) )
)</lang>
- Output:
fx | =ISCUSIP(A2) | ||
---|---|---|---|
A | B | ||
1 | Strings | CUSIP verdicts | |
2 | 037833100 | TRUE | |
3 | 17275R102 | TRUE | |
4 | 38259P508 | TRUE | |
5 | 594918104 | TRUE | |
6 | 68389X106 | FALSE | |
7 | 68389X105 | TRUE |
F#
<lang fsharp> // Validate CUSIP: Nigel Galloway. June 2nd., 2021 let fN=function n when n>47 && n<58->n-48 |n when n>64 && n<91->n-55 |42->36 |64->37 |_->38 let cD(n:string)=(10-(fst((n.[0..7])|>Seq.fold(fun(z,n)g->let g=(fN(int g))*(n+1) in (z+g/10+g%10,(n+1)%2))(0,0)))%10)%10=int(n.[8])-48 ["037833100";"17275R102";"38259P508";"594918104";"68389X103";"68389X105"]|>List.iter(fun n->printfn "CUSIP %s is %s" n (if cD n then "valid" else "invalid")) </lang>
- Output:
CUSIP 037833100 is valid CUSIP 17275R102 is valid CUSIP 38259P508 is valid CUSIP 594918104 is valid CUSIP 68389X103 is invalid CUSIP 68389X105 is valid Real: 00:00:00.009
Factor
<lang factor>USING: combinators.short-circuit formatting kernel math math.parser qw regexp sequences unicode ; IN: rosetta-code.cusip
- cusip-check-digit ( seq -- n )
but-last-slice [ [ dup alpha? [ digit> ] [ "*@#" index 36 + ] if ] dip odd? [ 2 * ] when 10 /mod + ] map-index sum 10 mod 10 swap - 10 mod ;
- cusip? ( seq -- ? )
{ [ R/ [0-9A-Z*@#]+/ matches? ] [ [ last digit> ] [ cusip-check-digit ] bi = ] } 1&& ;
qw{ 037833100 17275R102 38259P508 594918104 68389X106 68389X105 } [ dup cusip? "correct" "incorrect" ? "%s -> %s\n" printf ] each</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Fortran
The key notion here is to employ a single sequence of valid characters, VALID, and for each character C of the code under test, use function INDEX(VALID,C) to find its position within that sequence, which turns out to be the desired v of the example pseudocode. The only slight difficulty is that INDEX starts its counting with one for the first character of VALID, which is zero, so one must be subtracted; similarly, to return a digit character code via indexing into VALID, one must be added. By using a list of valid characters rather than peculiar character arithmetic (such as c <= "9" & c >= "0" or similar) there is no reliance on the ASCII way of things. Recall that EBCDIC encodements have different orderings and notably, non-alphabetic characters between A and Z.
The source does not bother with the MODULE protocol of F90 and later, and so the type of function CUSIPCHECK must be declared in all routines wishing to invoke it. However, the F90 feature of having the END statement of a subroutine or function give its name is to valuable to ignore. The function returns a character code rather than an integer, since the presumption is that it is to be compared to the check character of the code being inspected, which is known as a character not an integer. This means some blather when extracting the eight characters to be presented to CUSIPCHECK and comparing the result to the ninth character, but the test can be done in one expression.
There is no checking that only valid characters are presented, nor that eight-character codes only are offered, though the compiler might complain if the function were to be invoked with a text literal of the wrong size. In the absence of such checks, there need be no added complications to support a scheme for reporting such errors. <lang Fortran> CHARACTER*1 FUNCTION CUSIPCHECK(TEXT) !Determines the check sum character. Committee on Uniform Security Identification Purposes, of the American (i.e. USA) Bankers' Association.
CHARACTER*8 TEXT !Specifically, an eight-symbol code. CHARACTER*(*) VALID !These only are valid. PARAMETER (VALID = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#") INTEGER I,V,S !Assistants. S = 0 !Start the checksum. DO I = 1,LEN(TEXT) !Step through the text. V = INDEX(VALID,TEXT(I:I)) - 1 !Since counting starts with one. IF (MOD(I,2).EQ.0) V = V*2 !V = V*(2 - MOD(I,2))? S = S + V/10 + MOD(V,10) !Specified calculation. END DO !On to the next character. I = MOD(10 - MOD(S,10),10) + 1 !Again, counting starts with one. CUSIPCHECK = VALID(I:I) !Thanks to the MOD 10, surely a digit. END FUNCTION CUSIPCHECK !No checking for invalid input...
PROGRAM POKE !Just to try it out. INTEGER I,N !Assistants. PARAMETER (N = 6) !A whole lot of blather CHARACTER*9 CUSIP(N) !Just to have an array of test codes. DATA CUSIP/ !Here they are, as specified. 1 "037833100", 2 "17275R102", 3 "38259P508", 4 "594918104", 5 "68389X106", 6 "68389X105"/ CHARACTER*1 CUSIPCHECK !Needed as no use of the MODULE protocol.
DO I = 1,N !"More than two? Use a DO..." WRITE (6,*) CUSIP(I),CUSIPCHECK(CUSIP(I)(1:8)).EQ.CUSIP(I)(9:9) END DO
END</lang>
Output: standard output is to I/O unit 6, and free-format (the *) will suffice for this. Each line output starts with a space (in case it is to go to a lineprinter, with carriage control), which is convenient for layout here.
037833100 T 17275R102 T 38259P508 T 594918104 T 68389X106 F 68389X105 T
This would have worked first time, except that a fymgre frmble caused the omission of the digit 2 from the text of VALID. The benefits of checking checksums reach to unexpected places!
FreeBASIC
<lang freebasic>' version 04-04-2017 ' compile with: fbc -s console
sub cusip(input_str As String)
Print input_str; If Len(input_str) <> 9 Then Print " length is incorrect, invalid cusip" Return End If
Dim As Long i, v , sum Dim As UByte x
For i = 1 To 8 x = input_str[i-1] Select Case x Case Asc("0") To Asc("9") v = x - Asc("0") Case Asc("A") To Asc("Z") v = x - Asc("A") + 1 + 9 Case Asc("*") v= 36 Case Asc("@") v = 37 Case Asc("#") v = 38 Case Else Print " found a invalid character, invalid cusip" return End Select
If (i And 1) = 0 Then v = v * 2 sum = sum + v \ 10 + v Mod 10 Next
sum = (10 - (sum Mod 10)) Mod 10 If sum = (input_str[8] - Asc("0")) Then Print " is valid" Else Print " is invalid" End If
End Sub
' ------=< MAIN >=------
Data "037833100", "17275R102", "38259P508" Data "594918104", "68389X106", "68389X105"
Dim As String input_str
Print For i As Integer = 1 To 6
Read input_str cusip(input_str)
Next
' empty keyboard buffer While InKey <> "" : Wend Print : Print "hit any key to end program" Sleep End</lang>
- Output:
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 is invalid 68389X105 is valid
Go
<lang go>package main
import "fmt"
func isCusip(s string) bool {
if len(s) != 9 { return false } sum := 0 for i := 0; i < 8; i++ { c := s[i] var v int switch { case c >= '0' && c <= '9': v = int(c) - 48 case c >= 'A' && c <= 'Z': v = int(c) - 55 case c == '*': v = 36 case c == '@': v = 37 case c == '#': v = 38 default: return false } if i % 2 == 1 { v *= 2 } // check if odd as using 0-based indexing sum += v/10 + v%10 } return int(s[8]) - 48 == (10 - (sum%10)) % 10
}
func main() {
candidates := []string { "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105", }
for _, candidate := range candidates { var b string if isCusip(candidate) { b = "correct" } else { b = "incorrect" } fmt.Printf("%s -> %s\n", candidate, b) }
} </lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Groovy
<lang groovy>class Cusip {
private static Boolean isCusip(String s) { if (s.length() != 9) return false int sum = 0 for (int i = 0; i <= 7; i++) { char c = s.charAt(i)
int v if (c >= ('0' as char) && c <= ('9' as char)) { v = c - 48 } else if (c >= ('A' as char) && c <= ('Z' as char)) { v = c - 55 // lower case letters apparently invalid } else if (c == '*' as char) { v = 36 } else if (c == '@' as char) { v = 37 } else if (c == '#' as char) { v = 38 } else { return false } if (i % 2 == 1) v *= 2 // check if odd as using 0-based indexing sum += v / 10 + v % 10 } return s.charAt(8) - 48 == (10 - (sum % 10)) % 10 }
static void main(String[] args) { List<String> candidates=new ArrayList<>() candidates.add("037833100") candidates.add("17275R102") candidates.add("38259P508") candidates.add("594918104") candidates.add("68389X106") candidates.add("68389X105") for (String candidate : candidates) { System.out.printf("%s -> %s%n", candidate, isCusip(candidate) ? "correct" : "incorrect") } }
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Haskell
<lang haskell>import Data.List(elemIndex)
data Result = Valid | BadCheck | TooLong | TooShort | InvalidContent deriving Show
-- convert a list of Maybe to a Maybe list. -- result is Nothing if any of values from the original list are Nothing allMaybe :: [Maybe a] -> Maybe [a] allMaybe = sequence
toValue :: Char -> Maybe Int toValue c = elemIndex c $ ['0'..'9'] ++ ['A'..'Z'] ++ "*@#"
-- check a list of ints to see if they represent a valid CUSIP valid :: [Int] -> Bool valid ns0 =
let -- multiply values with even index by 2 ns1 = zipWith (\i n -> (if odd i then n else 2*n)) [1..] $ take 8 ns0
-- apply div/mod formula from site and sum up results sm = sum $ fmap (\s -> ( s `div` 10 ) + s `mod` 10) ns1
in -- apply mod/mod formula from site and compare to last value in list ns0!!8 == (10 - (sm `mod` 10)) `mod` 10
-- check a String to see if it represents a valid CUSIP checkCUSIP :: String -> Result checkCUSIP cs
| l < 9 = TooShort | l > 9 = TooLong | otherwise = case allMaybe (fmap toValue cs) of Nothing -> InvalidContent Just ns -> if valid ns then Valid else BadCheck where l = length cs
testData =
[ "037833100" , "17275R102" , "38259P508" , "594918104" , "68389X106" , "68389X105" ]
main = mapM_ putStrLn (fmap (\s -> s ++ ": " ++ show (checkCUSIP s)) testData)</lang>
- Output:
037833100: Valid 17275R102: Valid 38259P508: Valid 594918104: Valid 68389X106: BadCheck 68389X105: Valid
Or, making some alternative selections from Haskell's rich libraries: <lang Haskell>import qualified Data.Map as M (Map, fromList, lookup) import Data.Maybe (fromMaybe)
cusipMap :: M.Map Char Int cusipMap = M.fromList $ zip (['0' .. '9'] ++ ['A' .. 'Z'] ++ "*@#") [0 ..]
cusipValid :: String -> Bool cusipValid s =
let ns = (fromMaybe [] . traverse (`M.lookup` cusipMap)) s in (9 == length ns) && let qrSum = sum $ ([quot, rem] <*> zipWith id (cycle [id, (* 2)]) (take 8 ns)) <*> [10] in last ns == rem (10 - rem qrSum 10) 10
main :: IO () main =
mapM_ (print . ((,) <*> cusipValid)) [ "037833100" , "17275R102" , "38259P508" , "594918104" , "68389X106" , "68389X105" ]</lang>
- Output:
("037833100",True) ("17275R102",True) ("38259P508",True) ("594918104",True) ("68389X106",False) ("68389X105",True)
Icon and Unicon
<lang Icon># cusip.icn -- Committee on Uniform Security Identification Procedures
procedure main()
local code, codes codes := ["037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"] while code := pop(codes) do { writes(code, " : ") if check_code(code) then write("valid.") else write("not valid.") }
end
procedure check_code(c)
local p, sum, value static codetable initial codetable := buildtable()
sum := 0 value := 0 every p := 1 to 8 do { if p % 2 = 1 then # odd position value := codetable[c[p]] else # even position value := 2 * codetable[c[p]] sum +:= (value / 10) + (value % 10) } sum := (10 - (sum % 10)) % 10 if sum = c[9] then return else fail
end
procedure buildtable()
local chars, n, t t := table() chars := &digits || &ucase || "*@#" every n := 1 to *chars do t[chars[n]] := (n - 1) return t
end</lang>
- Output:
037833100 : valid.17275R102 : valid. 38259P508 : valid. 594918104 : valid. 68389X106 : not valid. 68389X105 : valid.
J
One-liner: <lang j> ccd =. 10 | 10 - 10 | [: +/ [: , 10 (#.^:_1) (8 $ 1 2) * '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' i. ]
ccd '68389X10'
5</lang>
More verbose version that checks for correct input: <lang j> CUSIPcheckdigit =. 3 : 0 assert. 8 = $ y NB. Only accept an 8-element long list assert. */ y e. '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' NB. Only accept characters from the list of 38 values =. (8 $ 1 2) * '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' i. ] NB. Verb to translate characters and then double every second value. sumdigits =. +/@,@(10 10&#:) NB. Verb to sum the base-10 digits in a numerical array invertedmod =. 10 | 10 - 10 | ] NB. Verb to find the mod-10 of 10 minus mod-10 ": invertedmod sumdigits values y NB. Return the check digit as a character )
addCUSIPcheckdigit =: , CUSIPcheckdigit verifyCUSIPcheckdigit =: {: = CUSIPcheckdigit@}:</lang>
Examples: <lang j> addCUSIPcheckdigit '68389X10' 68389X105
verifyCUSIPcheckdigit '68389X106'
0
verifyCUSIPcheckdigit '68389X105'
1
samples =: '037833100', '17275R102', '38259P508', '594918104', '68389X106',: '68389X105' samples ; verifyCUSIPcheckdigit"1 samples
┌─────────┬─┐ │037833100│1│ │17275R102│1│ │38259P508│1│ │594918104│1│ │68389X106│0│ │68389X105│1│ └─────────┴─┘</lang>
Java
Uses Java 9 <lang Java>import java.util.List;
public class Cusip {
private static Boolean isCusip(String s) { if (s.length() != 9) return false; int sum = 0; for (int i = 0; i <= 7; i++) { char c = s.charAt(i);
int v; if (c >= '0' && c <= '9') { v = c - 48; } else if (c >= 'A' && c <= 'Z') { v = c - 55; // lower case letters apparently invalid } else if (c == '*') { v = 36; } else if (c == '@') { v = 37; } else if (c == '#') { v = 38; } else { return false; } if (i % 2 == 1) v *= 2; // check if odd as using 0-based indexing sum += v / 10 + v % 10; } return s.charAt(8) - 48 == (10 - (sum % 10)) % 10; }
public static void main(String[] args) { List<String> candidates = List.of( "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105", "EXTRACRD8", "EXTRACRD9", "BADCUSIP!", "683&9X106", "68389x105", "683$9X106", "68389}105", "87264ABE4" ); for (String candidate : candidates) { System.out.printf("%s -> %s%n", candidate, isCusip(candidate) ? "correct" : "incorrect"); } }
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct EXTRACRD8 -> incorrect EXTRACRD9 -> correct BADCUSIP! -> incorrect 683&9X106 -> incorrect 68389x105 -> incorrect 683$9X106 -> incorrect 68389}105 -> incorrect 87264ABE4 -> correct
JavaScript
<lang javascript>(() => {
'use strict';
// cusipValid = Dict Char Int -> String -> Bool const cusipValid = charMap => s => { const ns = fromMaybe([])( traverse(flip(lookupDict)(charMap))( chars(s) ) ); return 9 === ns.length && ( last(ns) === rem( 10 - rem( sum(apList( apList([quot, rem])( zipWith(identity)( cycle([identity, x => 2 * x]) )(take(8)(ns)) ) )([10])) )(10) )(10) ); };
//----------------------- TEST ------------------------ // main :: IO () const main = () => {
// cusipMap :: Dict Char Int const cusipMap = dictFromList( zip(chars( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#" ))(enumFrom(0)));
console.log(unlines(map( apFn( s => validity => s + ' -> ' + str(validity) )(cusipValid(cusipMap)) )([ '037833100', '17275R102', '38259P508', '594918104', '68389X106', '68389X105' ]))); };
//----------------- GENERIC FUNCTIONS -----------------
// Just :: a -> Maybe a const Just = x => ({ type: 'Maybe', Nothing: false, Just: x });
// Nothing :: Maybe a const Nothing = () => ({ type: 'Maybe', Nothing: true, });
// Tuple (,) :: a -> b -> (a, b) const Tuple = a => b => ({ type: 'Tuple', '0': a, '1': b, length: 2 });
// apFn :: (a -> b -> c) -> (a -> b) -> a -> c const apFn = f => // Applicative instance for functions. // f(x) applied to g(x). g => x => f(x)( g(x) );
// apList (<*>) :: [(a -> b)] -> [a] -> [b] const apList = fs => // The sequential application of each of a list // of functions to each of a list of values. xs => fs.flatMap( f => xs.map(f) );
// append (++) :: [a] -> [a] -> [a] // append (++) :: String -> String -> String const append = xs => // A list or string composed by // the concatenation of two others. ys => xs.concat(ys);
// chars :: String -> [Char] const chars = s => s.split();
// cons :: a -> [a] -> [a] const cons = x => xs => Array.isArray(xs) ? ( [x].concat(xs) ) : 'GeneratorFunction' !== xs .constructor.constructor.name ? ( x + xs ) : ( // cons(x)(Generator) function*() { yield x; let nxt = xs.next() while (!nxt.done) { yield nxt.value; nxt = xs.next(); } } )();
// cycle :: [a] -> Generator [a] function* cycle(xs) { const lng = xs.length; let i = 0; while (true) { yield(xs[i]) i = (1 + i) % lng; } }
// dictFromList :: [(k, v)] -> Dict const dictFromList = kvs => Object.fromEntries(kvs);
// enumFrom :: Enum a => a -> [a] function* enumFrom(x) { // A non-finite succession of enumerable // values, starting with the value x. let v = x; while (true) { yield v; v = succ(v); } }
// flip :: (a -> b -> c) -> b -> a -> c const flip = f => 1 < f.length ? ( (a, b) => f(b, a) ) : (x => y => f(y)(x));
// fromEnum :: Enum a => a -> Int const fromEnum = x => typeof x !== 'string' ? ( x.constructor === Object ? ( x.value ) : parseInt(Number(x)) ) : x.codePointAt(0);
// fromMaybe :: a -> Maybe a -> a const fromMaybe = def => // A default value if mb is Nothing // or the contents of mb. mb => mb.Nothing ? def : mb.Just;
// fst :: (a, b) -> a const fst = tpl => // First member of a pair. tpl[0];
// identity :: a -> a const identity = x => // The identity function. (`id`, in Haskell) x;
// last :: [a] -> a const last = xs => // The last item of a list. 0 < xs.length ? xs.slice(-1)[0] : undefined;
// length :: [a] -> Int const length = xs => // Returns Infinity over objects without finite // length. This enables zip and zipWith to choose // the shorter argument when one is non-finite, // like cycle, repeat etc (Array.isArray(xs) || 'string' === typeof xs) ? ( xs.length ) : Infinity;
// liftA2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c const liftA2 = f => a => b => a.Nothing ? a : b.Nothing ? b : Just(f(a.Just)(b.Just));
// lookupDict :: a -> Dict -> Maybe b const lookupDict = k => dct => { const v = dct[k]; return undefined !== v ? ( Just(v) ) : Nothing(); };
// map :: (a -> b) -> [a] -> [b] const map = f => // The list obtained by applying f // to each element of xs. // (The image of xs under f). xs => ( Array.isArray(xs) ? ( xs ) : xs.split() ).map(f);
// pureMay :: a -> Maybe a const pureMay = x => Just(x);
// Given a type name string, returns a // specialised 'pure', where // 'pure' lifts a value into a particular functor.
// pureT :: String -> f a -> (a -> f a) const pureT = t => x => 'List' !== t ? ( 'Either' === t ? ( pureLR(x) ) : 'Maybe' === t ? ( pureMay(x) ) : 'Node' === t ? ( pureTree(x) ) : 'Tuple' === t ? ( pureTuple(x) ) : pureList(x) ) : pureList(x);
// pureTuple :: a -> (a, a) const pureTuple = x => Tuple()(x);
// quot :: Int -> Int -> Int const quot = n => m => Math.floor(n / m);
// rem :: Int -> Int -> Int const rem = n => m => n % m;
// snd :: (a, b) -> b const snd = tpl => tpl[1];
// str :: a -> String const str = x => x.toString();
// succ :: Enum a => a -> a const succ = x => { const t = typeof x; return 'number' !== t ? (() => { const [i, mx] = [x, maxBound(x)].map(fromEnum); return i < mx ? ( toEnum(x)(1 + i) ) : Error('succ :: enum out of range.') })() : x < Number.MAX_SAFE_INTEGER ? ( 1 + x ) : Error('succ :: Num out of range.') };
// sum :: [Num] -> Num const sum = xs => // The numeric sum of all values in xs. xs.reduce((a, x) => a + x, 0);
// take :: Int -> [a] -> [a] // take :: Int -> String -> String const take = n => // The first n elements of a list, // string of characters, or stream. xs => 'GeneratorFunction' !== xs .constructor.constructor.name ? ( xs.slice(0, n) ) : [].concat.apply([], Array.from({ length: n }, () => { const x = xs.next(); return x.done ? [] : [x.value]; }));
// The first argument is a sample of the type // allowing the function to make the right mapping
// toEnum :: a -> Int -> a const toEnum = e => x => ({ 'number': Number, 'string': String.fromCodePoint, 'boolean': Boolean, 'object': v => e.min + v } [typeof e])(x);
// traverse :: (Applicative f) => (a -> f b) -> [a] -> f [b] const traverse = f => // Collected results of mapping each element // of a structure to an action, and evaluating // these actions from left to right. xs => 0 < xs.length ? (() => { const vLast = f(xs.slice(-1)[0]), t = vLast.type || 'List'; return xs.slice(0, -1).reduceRight( (ys, x) => liftA2(cons)(f(x))(ys), liftA2(cons)(vLast)(pureT(t)([])) ); })() : [ [] ];
// uncons :: [a] -> Maybe (a, [a]) const uncons = xs => { // Just a tuple of the head of xs and its tail, // Or Nothing if xs is an empty list. const lng = length(xs); return (0 < lng) ? ( Infinity > lng ? ( Just(Tuple(xs[0])(xs.slice(1))) // Finite list ) : (() => { const nxt = take(1)(xs); return 0 < nxt.length ? ( Just(Tuple(nxt[0])(xs)) ) : Nothing(); })() // Lazy generator ) : Nothing(); };
// uncurry :: (a -> b -> c) -> ((a, b) -> c) const uncurry = f => // A function over a pair, derived // from a curried function. x => ((...args) => { const xy = 1 < args.length ? ( args ) : args[0]; return f(xy[0])(xy[1]); })(x);
// unlines :: [String] -> String const unlines = xs => // A single string formed by the intercalation // of a list of strings with the newline character. xs.join('\n');
// zip :: [a] -> [b] -> [(a, b)] const zip = xs => // Use of `take` and `length` here allows for zipping with non-finite // lists - i.e. generators like cycle, repeat, iterate. ys => { const lng = Math.min(length(xs), length(ys)), vs = take(lng)(ys); return take(lng)(xs).map( (x, i) => Tuple(x)(vs[i]) ); };
// Use of `take` and `length` here allows zipping with non-finite lists // i.e. generators like cycle, repeat, iterate.
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] const zipWith = f => xs => ys => { const lng = Math.min(length(xs), length(ys)); return Infinity > lng ? (() => { const as = take(lng)(xs), bs = take(lng)(ys); return Array.from({ length: lng }, (_, i) => f(as[i])( bs[i] )); })() : zipWithGen(f)(xs)(ys); };
// zipWithGen :: (a -> b -> c) -> // Gen [a] -> Gen [b] -> Gen [c] const zipWithGen = f => ga => gb => { function* go(ma, mb) { let a = ma, b = mb; while (!a.Nothing && !b.Nothing) { let ta = a.Just, tb = b.Just yield(f(fst(ta))(fst(tb))); a = uncons(snd(ta)); b = uncons(snd(tb)); } } return go(uncons(ga), uncons(gb)); };
// MAIN --- return main();
})();</lang>
- Output:
037833100 -> true 17275R102 -> true 38259P508 -> true 594918104 -> true 68389X106 -> false 68389X105 -> true
Julia
<lang julia>module CUSIP
function _lastdigitcusip(input::AbstractString)
input = uppercase(input) s = 0
for (i, c) in enumerate(input) if isdigit(c) v = Int(c) - 48 elseif isalpha(c) v = Int(c) - 64 + 9 elseif c == '*' v = 36 elseif c == '@' v = 37 elseif c == '#' v = 38 end
if iseven(i); v *= 2 end s += div(v, 10) + rem(v, 10) end
return Char(rem(10 - rem(s, 10), 10) + 48)
end
checkdigit(input::AbstractString) = input[9] == _lastdigitcusip(input[1:8])
end # module CUSIP
for code in ("037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105")
println("$code is ", CUSIP.checkdigit(code) ? "correct." : "not correct.")
end</lang>
- Output:
037833100 is correct. 17275R102 is correct. 38259P508 is correct. 594918104 is correct. 68389X106 is not correct. 68389X105 is correct.
Kotlin
<lang scala>// version 1.1.0
fun isCusip(s: String): Boolean {
if (s.length != 9) return false var sum = 0 for (i in 0..7) { val c = s[i] var v = when (c) { in '0'..'9' -> c.toInt() - 48 in 'A'..'Z' -> c.toInt() - 55 // lower case letters apparently invalid '*' -> 36 '@' -> 37 '#' -> 38 else -> return false } if (i % 2 == 1) v *= 2 // check if odd as using 0-based indexing sum += v / 10 + v % 10 } return s[8].toInt() - 48 == (10 - (sum % 10)) % 10
}
fun main(args: Array<String>) {
val candidates = listOf( "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" ) for (candidate in candidates) println("$candidate -> ${if(isCusip(candidate)) "correct" else "incorrect"}")
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
langur
If we don't strictly follow the pseudo-code, we can do this.
<lang langur>val .isCusip = f(.s) {
if not isString(.s) or len(.s) != 9 { return false }
val .basechars = cp2s('0'..'9') ~ cp2s('A'..'Z') ~ "*@#"
val .sum = for[=0] .i of 8 { var .v = index(s2s(.s, .i), .basechars) if not .v: return false .v = .v[1]-1 if .i div 2: .v x= 2 _for += .v \ 10 + .v rem 10 }
.s[9]-'0' == (10-(.sum rem 10)) rem 10
}
val .candidates = w/037833100 17275R102 38259P508 594918104 68389X106 68389X105/
for .c in .candidates {
writeln .c, ": ", if(.isCusip(.c): "good" ; "bad")
}</lang>
Following the pseudo-code would look more like the following.
<lang langur>val .isCusip = f(.s) {
if not isString(.s) or len(.s) != 9 { return false }
val .sum = for[=0] .i of 8 { val .c = .s[.i] var .v = 0
given .c { # note: default op between conditions "and" # Use "case or" to make given act like a switch in some other languages. case >= '0', <= '9': .v = .c-'0'
case >= 'A', <= 'Z': .v = .c-55 # .c-'A'+10
case '*': .v = 36 case '@': .v = 37 case '#': .v = 38
default: return false }
if .i div 2: .v x= 2 _for += .v \ 10 + .v rem 10 }
.s[9]-'0' == (10-(.sum rem 10)) rem 10
}
val .candidates = w/037833100 17275R102 38259P508 594918104 68389X106 68389X105/
for .c in .candidates {
writeln .c, ": ", if(.isCusip(.c): "good" ; "bad")
}</lang>
- Output:
037833100: good 17275R102: good 38259P508: good 594918104: good 68389X106: bad 68389X105: good
Lua
The checkDigit function is a line-for-line translation of the pseudo-code algorithm. <lang Lua>function checkDigit (cusip)
if #cusip ~= 8 then return false end local sum, c, v, p = 0 for i = 1, 8 do c = cusip:sub(i, i) if c:match("%d") then v = tonumber(c) elseif c:match("%a") then p = string.byte(c) - 55 v = p + 9 elseif c == "*" then v = 36 elseif c == "@" then v = 37 elseif c == "#" then v = 38 end if i % 2 == 0 then v = v * 2 end sum = sum + math.floor(v / 10) + v % 10 end return tostring((10 - (sum % 10)) % 10)
end
local testCases = {
"037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"
} for _, CUSIP in pairs(testCases) do
io.write(CUSIP .. ": ") if checkDigit(CUSIP:sub(1, 8)) == CUSIP:sub(9, 9) then print("VALID") else print("INVALID") end
end</lang>
- Output:
037833100: VALID 17275R102: VALID 38259P508: VALID 594918104: VALID 68389X106: INVALID 68389X105: VALID
Mathematica / Wolfram Language
<lang Mathematica>ClearAll[Cusip] rules = Thread[(ToString /@ Range[0, 9]) -> Range[0, 9]]~Join~
Thread[CharacterRange["A", "Z"] -> Range[26] + 9]~Join~ Thread[Characters["*@#"] -> {36, 37, 38}];
Cusip[cusip_String] := Module[{s = cusip, sum = 0, c, value, check},
If[StringLength[s] != 9, Print["Cusip must be 9 characters!"]; False , s = Characters[ToUpperCase[s]]; Do[ c = si; value = c /. rules; If[EvenQ[i], value *= 2]; sum += Floor[value/10] + Mod[value, 10]; , {i, 8} ]; check = Mod[(10 - Mod[sum, 10]), 10]; s-1 === ToString[check] ] ]
Cusip /@ {"037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"}</lang>
- Output:
{True, True, True, True, False, True}
Modula-2
<lang modula2>MODULE CUSIP; FROM FormatString IMPORT FormatString; FROM Terminal IMPORT WriteString,WriteLn,ReadChar;
PROCEDURE WriteInt(n : INTEGER); VAR buf : ARRAY[0..10] OF CHAR; BEGIN
FormatString("%i", buf, n); WriteString(buf)
END WriteInt;
PROCEDURE cusipCheckDigit(cusip : ARRAY OF CHAR) : INTEGER; VAR
i,v,sum : INTEGER;
BEGIN
i := 0; sum := 0; WHILE cusip[i] # 0C DO IF ('0' <= cusip[i]) AND (cusip[i] <= '9') THEN v := ORD(cusip[i]) - 48 (* 0 *) ELSIF ('A' <= cusip[i]) AND (cusip[i] <= 'Z') THEN v := ORD(cusip[i]) - 65 (* A *) + 10 ELSIF cusip[i] = '*' THEN v := 36 ELSIF cusip[i] = '@' THEN v := 37 ELSIF cusip[i] = '#' THEN v := 38 ELSE RETURN -1 END; IF i MOD 2 = 1 THEN v := 2 * v END; IF i < 8 THEN sum := sum + (v DIV 10) + (v MOD 10); END; INC(i) END;
IF i # 9 THEN RETURN -1 END; RETURN (10 - (sum MOD 10)) MOD 10
END cusipCheckDigit;
PROCEDURE isValidCusip(cusip : ARRAY OF CHAR) : BOOLEAN; VAR
check : INTEGER;
BEGIN
check := cusipCheckDigit(cusip); IF check < 0 THEN RETURN FALSE END; RETURN cusip[8] = CHR(48 (* 0 *) + check)
END isValidCusip;
PROCEDURE Print(cusip : ARRAY OF CHAR); BEGIN
WriteString(cusip); IF isValidCusip(cusip) THEN WriteString(" : Valid") ELSE WriteString(" : Invalid") END; WriteLn
END Print;
(* main *) BEGIN
WriteString("CUSIP Verdict"); WriteLn;
Print("037833100"); Print("17275R102"); Print("38259P508"); Print("594918104"); Print("68389X106"); Print("68389X105");
ReadChar
END CUSIP.</lang>
- Output:
CUSIP Verdict 037833100 : Valid 17275R102 : Valid 38259P508 : Valid 594918104 : Valid 68389X106 : Invalid 68389X105 : Valid
Nanoquery
<lang Nanoquery>def cusip_checksum(cusip)
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" num = "0123456789" sum = 0
for i in range(1, 8) c = cusip[i - 1] v = 0 if c in num v = int(c) else if c in alpha p = alpha[c] + 1 v = p + 9 else if c in "*@#" v = "*@#"[c] + 36 end if (i % 2) = 0 v *= 2 end
sum += int(v / 10) + (v % 10) end
return (10 - (sum % 10)) % 10
end
if main
codes = {"037833100", "17275R102", "38259P508",\ "594918104", "68389X106", "68389X105"}
for code in codes if int(code[len(code) - 1]) = cusip_checksum(code) println code + " is valid" else println code + " is invalid" end end
end</lang>
- Output:
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 is invalid 68389X105 is valid
Nim
<lang Nim>import strutils
proc cusipCheck(cusip: string): bool =
if cusip.len != 9: return false var sum, v = 0 for i, c in cusip[0 .. ^2]: if c.isDigit: v = parseInt($c) elif c.isUpperAscii: v = ord(c) - ord('A') + 10 elif c == '*': v = 36 elif c == '@': v = 37 elif c == '#': v = 38 if i mod 2 == 1: v *= 2 sum += v div 10 + v mod 10 let check = (10 - (sum mod 10)) mod 10 return $check == $cusip[^1]
proc main =
let codes = [ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" ]
for code in codes: echo code, ": ", if cusipCheck(code): "Valid" else: "Invalid"
main()</lang>
- Output:
037833100: Valid 17275R102: Valid 38259P508: Valid 594918104: Valid 68389X106: Invalid 68389X105: Valid
Objeck
<lang Objeck>class Cusip {
function : native : IsCusip(s : String) ~ Bool { if(s->Size() <> 9) { return false; };
sum := 0; for(i := 0; i < 7; i+=1;) { c := s->Get(i); v : Int; if (c >= '0' & c <= '9') { v := c - 48; } else if (c >= 'A' & c <= 'Z') { v := c - 55; # lower case letters apparently invalid } else if (c = '*') { v := 36; } else if (c = '@') { v := 37; } else if (c = '#') { v := 38; } else { return false; }; # check if odd as using 0-based indexing if(i % 2 = 1) { v *= 2; };
sum += v / 10 + v % 10; };
return s->Get(8) - 48 = (10 - (sum % 10)) % 10; } function : Main(args : String[]) ~ Nil { candidates := [ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" ];
each(i : candidates) { candidate := candidates[i]; "{$candidate} => "->Print(); if(IsCusip(candidate)) { "correct"->PrintLine(); } else { "incorrect"->PrintLine(); }; }; }
}</lang>
Output:
037833100 => correct 17275R102 => correct 38259P508 => correct 594918104 => correct 68389X106 => incorrect 68389X105 => correct
Perl
<lang perl>$cv{$_} = $i++ for '0'..'9', 'A'..'Z', '*', '@', '#';
sub cusip_check_digit {
my @cusip = split m{}xms, shift; my $sum = 0;
for $i (0..7) { return 'Invalid character found' unless $cusip[$i] =~ m{\A [[:digit:][:upper:]*@#] \z}xms; $v = $cv{ $cusip[$i] }; $v *= 2 if $i%2; $sum += int($v/10) + $v%10; }
$check_digit = (10 - ($sum%10)) % 10; $check_digit == $cusip[8] ? : ' (incorrect)';
}
my %test_data = (
'037833100' => 'Apple Incorporated', '17275R102' => 'Cisco Systems', '38259P508' => 'Google Incorporated', '594918104' => 'Microsoft Corporation', '68389X106' => 'Oracle Corporation', '68389X105' => 'Oracle Corporation',
);
print "$_ $test_data{$_}" . cusip_check_digit($_) . "\n" for sort keys %test_data;</lang>
- Output:
037833100 Apple Incorporated 17275R102 Cisco Systems 38259P508 Google Incorporated 594918104 Microsoft Corporation 68389X105 Oracle Corporation 68389X106 Oracle Corporation (incorrect)
Phix
sequence cch = {} function CusipCheckDigit(string cusip) integer s = 0, c, v if length(cch)=0 then cch = repeat(-1,256) for i='0' to '9' do cch[i] = i-'0' end for for i='A' to 'Z' do cch[i] = i-55 end for cch['*'] = 36 cch['@'] = 37 cch['#'] = 38 end if if length(cusip)!=9 or find('\0',cusip) then return 0 end if for i=1 to 8 do c := cusip[i] v := cch[c] if v=-1 then return 0 end if if remainder(i,2)=0 then v *= 2 end if s += floor(v/10)+mod(v,10) end for return cusip[9]=mod(10-mod(s,10),10)+'0' end function sequence tests = {"037833100", -- Apple Incorporated "17275R102", -- Cisco Systems "38259P508", -- Google Incorporated "594918104", -- Microsoft Corporation "68389X106", -- Oracle Corporation (incorrect) "68389X105"} -- Oracle Corporation for i=1 to length(tests) do string ti = tests[i] printf(1,"%s : %s\n",{ti,{"invalid","valid"}[CusipCheckDigit(ti)+1]}) end for
- Output:
037833100 : valid 17275R102 : valid 38259P508 : valid 594918104 : valid 68389X106 : invalid 68389X105 : valid
PHP
<lang PHP>function IsCusip(string $s) {
if (strlen($s) != 9) return false; $sum = 0; for ($i = 0; $i <= 7; $i++) { $c = $s[$i]; if (ctype_digit($c)) { // if character is numeric, get character's numeric value $v = intval($c); } elseif (ctype_alpha($c)) { // if character is alphabetic, get character's ordinal position in alphabet $position = ord(strtoupper($c)) - ord('A') + 1; $v = $position + 9; } elseif ($c == "*") { $v = 36; } elseif ($c == "@") { $v = 37; } elseif ($c == "#") { $v = 38; } else { return false; } // is this character position even? if ($i % 2 == 1) { $v *= 2; } // calculate the checksum digit $sum += floor($v / 10 ) + ( $v % 10 ); } return ord($s[8]) - 48 == (10 - ($sum % 10)) % 10;
}
$cusips = array("037833100",
"17275R102", "38259P508", "594918104", "68389X106", "68389X105");
foreach ($cusips as $cusip) echo $cusip . " -> " . (IsCusip($cusip) ? "valid" : "invalid") . "\n";</lang>
- Output:
037833100 -> valid 17275R102 -> valid 38259P508 -> valid 594918104 -> valid 68389X106 -> invalid 68389X105 -> valid
PicoLisp
<lang PicoLisp>(de cusip (Str)
(let (Str (mapcar char (chop Str)) S 0) (for (I . C) (head 8 Str) (let V (cond ((<= 48 C 57) (- C 48)) ((<= 65 C 90) (+ 10 (- C 65))) ((= C 42) 36) ((= C 64) 37) ((= C 35) 38) ) (or (bit? 1 I) (setq V (>> -1 V)) ) (inc 'S (+ (/ V 10) (% V 10)) ) ) ) (= (- (last Str) 48) (% (- 10 (% S 10)) 10) ) ) )
(println
(mapcar cusip (quote "037833100" "17275R102" "38259P508" "68389X106" "68389X105" ) ) )</lang>
- Output:
(T T T NIL T)
PowerShell
<lang PowerShell> function Get-CheckDigitCUSIP {
[CmdletBinding()] [OutputType([int])] Param ( # Validate input [Parameter(Mandatory=$true, Position=0)] [ValidatePattern( '^[A-Z0-9@#*]{8}\d$' )] # @#* [ValidateScript({$_.Length -eq 9})] [string] $cusip ) $sum = 0 0..7 | ForEach { $c = $cusip[$_] ; $v = $null if ([Char]::IsDigit($c)) { $v = [char]::GetNumericValue($c) } if ([Char]::IsLetter($c)) { $v = [int][char]$c - [int][char]'A' +10 } if ($c -eq '*') { $v = 36 } if ($c -eq '@') { $v = 37 } if ($c -eq '#') { $v = 38 } if($_ % 2){ $v += $v } $sum += [int][Math]::Floor($v / 10 ) + ($v % 10) } [int]$checkDigit_calculated = ( 10 - ($sum % 10) ) % 10 return( $checkDigit_calculated )
}
function Test-IsCUSIP {
[CmdletBinding()] [OutputType([bool])] Param ( [Parameter(Mandatory=$true, Position=0)] [ValidatePattern( '^[A-Z0-9@#*]{8}\d$' )] [ValidateScript({$_.Length -eq 9})] [string] $cusip ) [int]$checkDigit_told = $cusip[-1].ToString() $checkDigit_calculated = Get-CheckDigitCUSIP $cusip ($checkDigit_calculated -eq $checkDigit_told)
}
$data = @" 037833100`tApple Incorporated 17275R102`tCisco Systems 38259P508`tGoogle Incorporated 594918104`tMicrosoft Corporation 68389X106`tOracle Corporation (incorrect) 68389X105`tOracle Corporation "@ -split "`n" $data |%{ Test-IsCUSIP $_.Split("`t")[0] }
</lang>
- Output:
True True True True False True
Python
Procedural
Requires Python 3.6 for the string template literal in the print statement.
<lang python>#!/usr/bin/env python3
import math
def cusip_check(cusip):
if len(cusip) != 9: raise ValueError('CUSIP must be 9 characters')
cusip = cusip.upper() total = 0 for i in range(8): c = cusip[i] if c.isdigit(): v = int(c) elif c.isalpha(): p = ord(c) - ord('A') + 1 v = p + 9 elif c == '*': v = 36 elif c == '@': v = 37 elif c == '#': v = 38
if i % 2 != 0: v *= 2
total += int(v / 10) + v % 10 check = (10 - (total % 10)) % 10 return str(check) == cusip[-1]
if __name__ == '__main__':
codes = [ '037833100', '17275R102', '38259P508', '594918104', '68389X106', '68389X105' ] for code in codes: print(f'{code} -> {cusip_check(code)}')
</lang> Output:
037833100 -> True 17275R102 -> True 38259P508 -> True 594918104 -> True 68389X106 -> False 68389X105 -> True
Composition of pure functions
Composing a set of pure functions, including a number of general and reusable abstractions: <lang python>CUSIP
from itertools import (cycle, islice, starmap) from functools import (reduce) from operator import (add) from enum import (Enum)
- isCusip :: Dict -> String -> Bool
def isCusip(dct):
Test for the validity of a CUSIP string in the context of a supplied dictionary of char values def go(s): ns = [dct[c] for c in list(s) if c in dct] return 9 == len(ns) and ( ns[-1] == ( 10 - ( sum(zipWith( lambda f, x: add(*divmod(f(x), 10)) )(cycle([identity, double]))( take(8)(ns) )) % 10 ) ) % 10 ) return go
- cusipCharDict :: () -> Dict Char Int
def cusipCharDict():
Dictionary of integer values for CUSIP characters def kv(a, ic): i, c = ic a[c] = i return a return reduce( kv, enumerate( enumFromTo('0')('9') + ( enumFromTo('A')('Z') + list('*&#') ) ), {} )
- TEST -------------------------------------------------
- main :: IO ()
def main():
Tests
# cusipTest :: String -> Bool cusipTest = isCusip(cusipCharDict())
print( tabulated('Valid as CUSIP string:')( cusipTest )([ '037833100', '17275R102', '38259P508', '594918104', '68389X106', '68389X105' ]) )
- GENERIC -------------------------------------------------
- double :: Num -> Num
def double(x):
Wrapped here as a function for the zipWith expression return 2 * x
- enumFromTo :: Enum a => a -> a -> [a]
def enumFromTo(m):
Enumeration of values [m..n] def go(x, y): t = type(m) i = fromEnum(x) d = 0 if t != float else (x - i) return list(map( lambda x: toEnum(t)(d + x), range(i, 1 + fromEnum(y)) ) if int != t else range(x, 1 + y)) return lambda n: go(m, n)
- fromEnum :: Enum a => a -> Int
def fromEnum(x):
Index integer for enumerable value. return ord(x) if str == type(x) else ( x.value if isinstance(x, Enum) else int(x) )
- mul :: Num -> Num -> Num
def mul(x):
Function version of (*) operator; a curried equivalent of operator.mul return lambda y: x * y
- identity :: a -> a
def identity(x):
The identity function. The usual 'id' is reserved in Python. return x
- tabulated :: String -> (a -> b) -> [a] -> String
def tabulated(s):
heading -> function -> input List -> tabulated output string def go(f, xs): def width(x): return len(str(x)) w = width(max(xs, key=width)) return s + '\n' + '\n'.join([ str(x).rjust(w, ' ') + ' -> ' + str(f(x)) for x in xs ]) return lambda f: lambda xs: go(f, xs)
- take :: Int -> [a] -> [a]
- take :: Int -> String -> String
def take(n):
The prefix of xs of length n, or xs itself if n > length xs. return lambda xs: ( xs[0:n] if isinstance(xs, list) else list(islice(xs, n)) )
- toEnum :: Type -> Int -> a
def toEnum(t):
Enumerable value from index integer dct = { int: int, float: float, str: chr, bool: bool } return lambda x: dct[t](x) if t in dct else t(x)
- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
def zipWith(f):
Zipping with a custom (rather than tuple) function return lambda xs: lambda ys: ( list(starmap(f, zip(xs, ys))) )
- MAIN ---
if __name__ == '__main__':
main()</lang>
- Output:
Test for validity as a CUSIP string: '037833100' -> True '17275R102' -> True '38259P508' -> True '594918104' -> True '68389X106' -> False '68389X105' -> True
Quackery
<lang Quackery> [ -1 split 0 peek char 0 -
swap 0 swap witheach [ [ dup char 0 char 9 1+ within iff [ char 0 - ] done dup char A char Z 1+ within iff [ char A - 10 + ] done dup char * = iff [ drop 36 ] done dup char @ = iff [ drop 37 ] done dup char # = iff [ drop 38 ] done $ "Unexpected character '" swap join $ "' in CUSIP." join fail ] i^ 1 & if [ 2 * ] 10 /mod + + ] 10 mod 10 swap - 10 mod = ] is cusip ( $ --> b ) [ dup echo$ cusip iff [ say " is correct." ] else [ say " is incorrect." ] cr ] is task ( $ --> ) $ "037833100 17275R102 38259P508 594918104 68389X106 68389X105" nest$ witheach task</lang>
- Output:
037833100 is correct. 17275R102 is correct. 38259P508 is correct. 594918104 is correct. 68389X106 is incorrect. 68389X105 is correct.
Racket
<lang racket>#lang racket (require srfi/14)
(define 0-char (char->integer #\0)) (define A-char (char->integer #\A))
(define (cusip-value c)
(cond [(char-set-contains? char-set:digit c) (- (char->integer c) 0-char)] [(char-set-contains? char-set:upper-case c) (+ 10 (- (char->integer c) A-char))] [(char=? c #\*) 36] [(char=? c #\@) 37] [(char=? c #\#) 38]))
(define (cusip-check-digit cusip)
(modulo (- 10 (modulo (for/sum ((i (sequence-map add1 (in-range 8))) (c (in-string cusip))) (let* ((v (cusip-value c)) (v′ (if (even? i) (* v 2) v))) (+ (quotient v′ 10) (modulo v′ 10)))) 10)) 10))
(define (CUSIP? s)
(char=? (string-ref s (sub1 (string-length s))) (integer->char (+ 0-char (cusip-check-digit s)))))
(module+ test
(require rackunit) (check-true (CUSIP? "037833100")) (check-true (CUSIP? "17275R102")) (check-true (CUSIP? "38259P508")) (check-true (CUSIP? "594918104")) (check-false (CUSIP? "68389X106")) (check-true (CUSIP? "68389X105")))</lang>
no output indicates all tests passed.
Raku
(formerly Perl 6)
<lang perl6>sub divmod ($v, $r) { $v div $r, $v mod $r } my %chr = (flat 0..9, 'A'..'Z', <* @ #>) Z=> 0..*;
sub cuisp-check ($cuisp where *.chars == 9) {
my ($code, $chk) = $cuisp.comb(8); my $sum = [+] $code.comb.kv.map: { [+] (($^k % 2 + 1) * %chr{$^v}).&divmod(10) }; so (10 - $sum mod 10) mod 10 eq $chk;
}
- TESTING
say "$_: ", $_.&cuisp-check for < 037833100 17275R102 38259P508 594918104 68389X106 68389X105 ></lang>
- Output:
037833100: True 17275R102: True 38259P508: True 594918104: True 68389X106: False 68389X105: True
REXX
idiomatic
<lang rexx>/*REXX program validates that the last digit (the check digit) of a CUSIP is valid. */ @.= parse arg @.1 . if @.1== | @.1=="," then do; @.1= 037833100 /* Apple Incorporated */
@.2= 17275R102 /* Cisco Systems */ @.3= 38259P508 /* Google Incorporated */ @.4= 594918104 /* Microsoft Corporation */ @.5= 68389X106 /* Oracle Corporation (incorrect)*/ @.6= 68389X105 /* Oracle Corporation */ end
do j=1 while @.j\=; chkDig=CUSIPchk(@.j) /*calculate check digit from func*/ OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate check digit with func*/ say 'CUSIP ' @.j right(OK, 6) "valid." /*display the CUSIP and validity.*/ end /*j*/
exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ CUSIPchk: procedure; arg x 9; $=0; abc= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
do k=1 for 8 y=substr(x, k, 1) select when datatype(y,'W') then #=y when datatype(y,'U') then #=pos(y, abc) + 9 when y=='*' then #=36 when y=='@' then #=37 when y=='#' then #=38 otherwise return 0 /*invalid character.*/ end /*select*/ if k//2==0 then #=#+# /*K even? Double it*/ $=$ + #%10 + #//10 end /*k*/ return (10- $//10) // 10</lang>
output when using the default input:
CUSPID 037833100 is valid. CUSPID 17275R102 is valid. CUSPID 38259P508 is valid. CUSPID 594918104 is valid. CUSPID 68389X106 isn't valid. CUSPID 68389X105 is valid.
conciser function
<lang rexx>/*REXX program validates that the last digit (the check digit) of a CUSIP is valid. */ @.= parse arg @.1 . if @.1== | @.1=="," then do; @.1= 037833100 /* Apple Incorporated */
@.2= 17275R102 /* Cisco Systems */ @.3= 38259P508 /* Google Incorporated */ @.4= 594918104 /* Microsoft Corporation */ @.5= 68389X106 /* Oracle Corporation (incorrect)*/ @.6= 68389X105 /* Oracle Corporation */ end
do j=1 while @.j\=; chkDig=CUSIPchk(@.j) /*calculate check digit from func*/ OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate check digit with func*/ say 'CUSIP ' @.j right(OK, 6) "valid." /*display the CUSIP and validity.*/ end /*j*/
exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ CUSIPchk: procedure; arg x 9; $=0; abc= '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#'
/* [↓] if Y isn' found, then POS returns zero.*/ do k=1 for 8; y=substr(x,k,1) /*get a character. */ #=pos(y, abc) - 1 /*get its position.*/ if # == -1 then return 0 /*invalid character*/ if k//2== 0 then #=#+# /*K even? double it*/ $=$ + #%10 + #//10 end /*k*/ return (10-$//10) // 10</lang>
output is the same as the idiomatic REXX version.
Ring
<lang ring>
- Project : CUSIP
inputstr = list(6) inputstr[1] = "037833100" inputstr[2] = "17275R102" inputstr[3] = "38259P508" inputstr[4] = "594918104" inputstr[5] = "68389X106" inputstr[6] = "68389X105" for n = 1 to len(inputstr)
cusip(inputstr[n])
next
func cusip(inputstr)
if len(inputstr) != 9 see " length is incorrect, invalid cusip" return ok v = 0 sum = 0 for i = 1 to 8 flag = 0 x = ascii(inputstr[i]) if x >= ascii("0") and x <= ascii("9") v = x - ascii("0") flag = 1 ok if x >= ascii("A") and x <= ascii("Z") v = x - 55 flag = 1 ok if x = ascii("*") v= 36 flag = 1 ok if x = ascii("@") v = 37 flag = 1 ok if x = ascii("#") v = 38 flag = 1 ok if flag = 0 see " found a invalid character, invalid cusip" + nl ok if (i % 2) = 0 v = v * 2 ok sum = sum + floor(v / 10) + v % 10 next sum = (10 - (sum % 10)) % 10 if sum = (ascii(inputstr[9]) - ascii("0")) see inputstr + " is valid" + nl else see inputstr + " is invalid" + nl ok
</lang> Output:
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 is invalid 68389X105 is valid
Ruby
Following pseudocode
<lang ruby>
- !/usr/bin/env ruby
def check_cusip(cusip)
abort('CUSIP must be 9 characters') if cusip.size != 9
sum = 0 cusip.split().each_with_index do |char, i| next if i == cusip.size - 1 case when char.scan(/\D/).empty? v = char.to_i when char.scan(/\D/).any? pos = char.upcase.ord - 'A'.ord + 1 v = pos + 9 when char == '*' v = 36 when char == '@' v = 37 when char == '#' v = 38 end
v *= 2 unless (i % 2).zero? sum += (v/10).to_i + (v % 10) end
check = (10 - (sum % 10)) % 10 return 'VALID' if check.to_s == cusip.split().last 'INVALID'
end
CUSIPs = %w[
037833100 17275R102 38259P508 594918104 68389X106 68389X105
]
CUSIPs.each do |cusip|
puts "#{cusip}: #{check_cusip(cusip)}"
end
</lang>
Output:
037833100: VALID 17275R102: VALID 38259P508: VALID 594918104: VALID 68389X106: INVALID 68389X105: VALID
More concise
Since it uses methods like chain, to_h, sum, and infinite Range syntax (0..), this needs a Ruby version > 2.5 <lang Ruby> TABLE = ("0".."9").chain("A".."Z", %w(* @ #)).zip(0..).to_h
def valid_CUSIP?(str)
sum = str[0..-2].chars.each_slice(2).sum do |c1,c2| TABLE[c1].divmod(10).sum + (TABLE[c2]*2).divmod(10).sum end str[-1].to_i == (10 - (sum % 10)) % 10
end
CUSIPs = %w(037833100 17275R102 38259P508 594918104 68389X106 68389X105) CUSIPs.each{|cusip| puts "#{cusip}: #{valid_CUSIP? cusip}"} </lang>
Rust
<lang rust>fn cusip_check(cusip: &str) -> bool {
if cusip.len() != 9 { return false; }
let mut v = 0; let capital_cusip = cusip.to_uppercase(); let char_indices = capital_cusip.as_str().char_indices().take(7);
let total = char_indices.fold(0, |total, (i, c)| { v = match c { '*' => 36, '@' => 37, '#' => 38, _ if c.is_digit(10) => c.to_digit(10).unwrap() as u8, _ if c.is_alphabetic() => (c as u8) - b'A' + 1 + 9, _ => v, };
if i % 2 != 0 { v *= 2 } total + (v / 10) + v % 10 });
let check = (10 - (total % 10)) % 10; (check.to_string().chars().nth(0).unwrap()) == cusip.chars().nth(cusip.len() - 1).unwrap()
}
fn main() {
let codes = [ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105", ]; for code in &codes { println!("{} -> {}", code, cusip_check(code)) }
}</lang>
Output:
037833100 -> True 17275R102 -> True 38259P508 -> True 594918104 -> True 68389X106 -> False 68389X105 -> True
Scala
- Output:
See it running in your browser by ScalaFiddle (JavaScript, non JVM) or by Scastie (JVM).
<lang Scala>object Cusip extends App {
val candidates = Seq("037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105")
for (candidate <- candidates) printf(f"$candidate%s -> ${if (isCusip(candidate)) "correct" else "incorrect"}%s%n")
private def isCusip(s: String): Boolean = { if (s.length != 9) false else { var sum = 0 for (i <- 0 until 7) { val c = s(i) var v = 0 if (c >= '0' && c <= '9') v = c - 48 else if (c >= 'A' && c <= 'Z') v = c - 55 // lower case letters apparently invalid else if (c == '*') v = 36 else if (c == '@') v = 37 else if (c == '#') v = 38 else return false if (i % 2 == 1) v *= 2 // check if odd as using 0-based indexing sum += v / 10 + v % 10 } s(8) - 48 == (10 - (sum % 10)) % 10 } }
}</lang>
SNOBOL4
<lang snobol4>#!/usr/local/bin/snobol4 -r
- cusip.sno
- -- Committee on Uniform Security Identification Procedures
- -r : read data placed after the end label.
- Verify check digit and size of cusip code.
define("cusipt()i") :(cusipt_end)
cusipt
chars = &digits &ucase "*@#" cusipt = table() i = 0
cusipt_1
chars pos(i) len(1) . c :f(return) cusipt[c] = i i = i + 1 :(cusipt_1)
cusipt_end
define("check_cusip(line)c,i") :(check_cusip_end)
check_cusip
eq(size(line), 9) :f(freturn) check_cusip = 0 i = 0
check_cusip_1
line pos(i) len(1) . c value = t[c] value = eq(remdr(i, 2), 1) t[c] * 2 check_cusip = check_cusip + (value / 10) + remdr(value, 10) i = lt(i, 7) i + 1 :s(check_cusip_1) check_cusip = remdr(10 - remdr(check_cusip, 10), 10) eq(substr(line, 9, 1), check_cusip) :s(return)f(freturn)
check_cusip_end
- main ***
t = cusipt()
read line = input :f(end)
check_cusip(line) :f(bad_cusip) output = line " valid." :(read)
bad_cusip
output = line " not valid." :(read)
end 037833100 17275R102 38259P508 594918104 68389X106 68389X105 68389X10 68389X1059 68389x105</lang>
- Output:
037833100 valid. 17275R102 valid. 38259P508 valid. 594918104 valid. 68389X106 not valid. 68389X105 valid. 68389X10 not valid. 68389X1059 not valid. 68389x105 not valid.
Swift
<lang swift>struct CUSIP {
var value: String
private static let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
init?(value: String) { if value.count == 9 && String(value.last!) == CUSIP.checkDigit(cusipString: String(value.dropLast())) { self.value = value } else if value.count == 8, let checkDigit = CUSIP.checkDigit(cusipString: value) { self.value = value + checkDigit } else { return nil } }
static func checkDigit(cusipString: String) -> String? { guard cusipString.count == 8, cusipString.allSatisfy({ $0.isASCII }) else { return nil }
let sum = cusipString.uppercased().enumerated().reduce(0, {sum, pair in let (i, char) = pair var v: Int
switch char { case "*": v = 36 case "@": v = 37 case "#": v = 38 case _ where char.isNumber: v = char.wholeNumberValue! case _: v = Int(char.asciiValue! - 65) + 10 }
if i & 1 == 1 { v *= 2 }
return sum + (v / 10) + (v % 10) })
return String((10 - (sum % 10)) % 10) }
}
let testCases = [
"037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"
]
for potentialCUSIP in testCases {
print("\(potentialCUSIP) -> ", terminator: "")
switch CUSIP(value: potentialCUSIP) { case nil: print("Invalid") case _: print("Valid") }
}</lang>
- Output:
037833100 -> Valid 17275R102 -> Valid 38259P508 -> Valid 594918104 -> Valid 68389X106 -> Invalid 68389X105 -> Valid
Tcl
Direct translation of pseudocode
<lang Tcl>proc ordinal-of-alpha {c} { ;# returns ordinal position of c in the alphabet (A=1, B=2...)
lsearch {_ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} [string toupper $c]
}
proc Cusip-Check-Digit {cusip} { ;# algorithm Cusip-Check-Digit(cusip) is
if {[string length $cusip] != 8} { ;# Input: an 8-character CUSIP return false } set sum 0 ;# sum := 0 for {set i 1} {$i <= 8} {incr i} { ;# for 1 ≤ i ≤ 8 do set c [string index $cusip $i-1] ;# c := the ith character of cusip if {[string is digit $c]} { ;# if c is a digit then set v $c ;# v := numeric value of the digit c } elseif {[string is alpha $c]} { ;# else if c is a letter then set p [ordinal-of-alpha $c] ;# p := ordinal position of c in the alphabet (A=1, B=2...) set v [expr {$p + 9}] ;# v := p + 9 } elseif {$c eq "*"} { ;# else if c = "*" then set v 36 ;# v := 36 } elseif {$c eq "@"} { ;# else if c = "@" then set v 37 ;# v := 37 } elseif {$c eq "#"} { ;# else if c = "#" then set v 38 ;# v := 38 } ;# end if if {$i % 2 == 0} { ;# if i is even then set v [expr {$v * 2}] ;# v := v × 2 } ;# end if
incr sum [expr {$v / 10 + $v % 10}] ;# sum := sum + int ( v div 10 ) + v mod 10 } ;# repeat
expr {(10 - ($sum % 10)) % 10} ;# return (10 - (sum mod 10)) mod 10
} proc check-cusip {cusip} {
set last [string index $cusip end] set cusip [string range $cusip 0 end-1] expr {$last eq [Cusip-Check-Digit $cusip]}
}</lang>
More idiomatic Tcl
<lang Tcl>proc check-cusip {code} {
if {[string length $code] != 9} { return false } set alphabet 0123456789abcdefghijklmnopqrstuvwxyz@# set code [split [string tolower $code] ""] foreach char $code idx {1 2 3 4 5 6 7 8 9} { set v [string first $char $alphabet] if {$v == -1} {return false} if {$idx % 2 == 0} { incr v $v } set v [::tcl::mathop::+ {*}[split $v ""]] incr sum $v } expr {$sum % 10 == 0}
}</lang>
Common test harness
<lang Tcl>proc test {} {
foreach {cusip name} { 037833100 "Apple Incorporated" 17275R102 "Cisco Systems" 38259P508 "Google Incorporated" 594918104 "Microsoft Corporation" 68389X106 "Oracle Corporation (incorrect)" 68389X105 "Oracle Corporation" } { puts [format %-40s%s $name [expr {[check-cusip $cusip] ? "valid" : "invalid"}]] puts [format %-40s%s $name [expr {[cusip-check $cusip] ? "valid" : "invalid"}]] }
} test</lang>
Output
- Output:
Apple Incorporated valid Cisco Systems valid Google Incorporated valid Microsoft Corporation valid Oracle Corporation (incorrect) invalid Oracle Corporation valid
VBA
<lang vb>Private Function Cusip_Check_Digit(s As Variant) As Integer
Dim Sum As Integer, c As String, v As Integer For i = 1 To 8 c = Mid(s, i, 1) If IsNumeric(c) Then v = Val(c) Else Select Case c Case "a" To "z" v = Asc(c) - Asc("a") + 10 Case "A" To "Z" v = Asc(c) - Asc("A") + 10 Case "*" v = 36 Case "@" v = 37 Case "#" v = 38 Case Else Debug.Print "not expected" End Select End If If i Mod 2 = 0 Then v = v * 2 Sum = Sum + Int(v \ 10) + v Mod 10 Next i Cusip_Check_Digit = (10 - (Sum Mod 10)) Mod 10
End Function</lang>
- Output:
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 not valid 68389X105 is valid
Visual Basic .NET
<lang vbnet>Module Module1
Function IsCUSIP(s As String) As Boolean If s.Length <> 9 Then Return False End If
Dim sum = 0 For i = 0 To 7 Dim c = s(i)
Dim v As Integer If "0" <= c AndAlso c <= "9" Then v = Asc(c) - 48 ElseIf "A" <= c AndAlso c <= "Z" Then v = Asc(c) - 55 ' Lower case letters are apparently invalid ElseIf c = "*" Then v = 36 ElseIf c = "#" Then v = 38 Else Return False End If
If i Mod 2 = 1 Then v *= 2 ' check if odd as using 0-based indexing End If sum += v \ 10 + v Mod 10 Next Return Asc(s(8)) - 48 = (10 - (sum Mod 10)) Mod 10 End Function
Sub Main() Dim candidates As New List(Of String) From { "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105" }
For Each candidate In candidates Console.WriteLine("{0} -> {1}", candidate, If(IsCUSIP(candidate), "correct", "incorrect")) Next End Sub
End Module</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Vlang
<lang vlang>fn is_cusip(s string) bool {
if s.len != 9 { return false } mut sum := 0 for i in 0..8 { c := s[i] mut v :=0 match true { c >= '0'[0] && c <= '9'[0] { v = c - 48
}
c >= 'A'[0] && c <= 'Z'[0] { v = c - 55
}
c == '*'[0] { v = 36
}
c == '@'[0] { v = 37
}
c == '#'[0] { v = 38
}
else { return false
}
} if i % 2 == 1 { v *= 2 } // check if odd as using 0-based indexing sum += v/10 + v%10 } return int(s[8]) - 48 == (10 - (sum%10)) % 10
}
fn main() {
candidates := [ "037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105",
]
for candidate in candidates { mut b :=' ' if is_cusip(candidate) { b = "correct" } else { b = "incorrect" } println("$candidate -> $b") }
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
Wren
<lang ecmascript>var isCusip = Fn.new { |s|
if (s.count != 9) return false var sum = 0 for (i in 0..7) { var c = s[i].bytes[0] var v if (c >= 48 && c <= 57) { // '0' to '9' v = c - 48 } else if (c >= 65 && c <= 90) { // 'A' to 'Z' v = c - 55 } else if (s[i] == "*") { v = 36 } else if (s[i] == "@") { v = 37 } else if (s[i] == "#") { v = 38 } else { return false } if (i%2 == 1) v = v * 2 // check if odd as using 0-based indexing sum = sum + (v/10).floor + v%10 } return s[8].bytes[0] - 48 == (10 - (sum%10)) % 10
}
var candidates = [
"037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105"
] for (candidate in candidates) {
var b = (isCusip.call(candidate)) ? "correct" : "incorrect" System.print("%(candidate) -> %(b)")
}</lang>
- Output:
037833100 -> correct 17275R102 -> correct 38259P508 -> correct 594918104 -> correct 68389X106 -> incorrect 68389X105 -> correct
XPL0
<lang XPL0>string 0; \use zero-terminated strings
func Valid(Cusip); \Return 'true' if valid CUSIP code char Cusip; int Sum, I, C, V; [Sum:= 0; for I:= 0 to 8-1 do
[C:= Cusip(I); ChOut(0, C); case of C>=^0 & C<=^9: V:= C-^0; C>=^A & C<=^Z: V:= C-^A+10; C=^*: V:=36; C=^@: V:=37; C=^#: V:=38 other V:= -1; if I&1 then V:= V*2; Sum:= Sum + V/10 + rem(0); ];
C:= Cusip(I); ChOut(0, C); V:= rem( (10-rem(Sum/10)) / 10 ); return V = C-^0; ];
int Cusip, N; [Cusip:= ["037833100",
"17275R102", "38259P508", "594918104", "68389X106", "68389X105"];
for N:= 0 to 6-1 do
[Text(0, if Valid(Cusip(N)) then " is valid" else " is invalid"); CrLf(0); ];
]</lang>
- Output:
037833100 is valid 17275R102 is valid 38259P508 is valid 594918104 is valid 68389X106 is invalid 68389X105 is valid
Yabasic
<lang Yabasic>sub cusip(inputStr$)
local i, v, sum, x$ Print inputStr$; If Len(inputStr$) <> 9 Print " length is incorrect, invalid cusip" : return For i = 1 To 8 x$ = mid$(inputStr$, i, 1) switch x$ Case "*": v = 36 : break Case "@": v = 37 : break Case "#": v = 38 : break default: if x$ >= "A" and x$ <= "Z" then v = asc(x$) - Asc("A") + 10 elsif x$ >= "0" and x$ <= "9" then v = asc(x$) - asc("0") else Print " found a invalid character, invalid cusip" return end if End switch If and(i, 1) = 0 v = v * 2 sum = sum + int(v / 10) + mod(v, 10) Next sum = mod(10 - mod(sum, 10), 10) If sum = asc(mid$(inputStr$, 9, 1)) - Asc("0") Then Print " is valid" Else Print " is invalid" End If
End Sub
// ------=< MAIN >=------
Data "037833100", "17275R102", "38259P508" Data "594918104", "68389X106", "68389X105", ""
Print do
Read inputStr$ if inputStr$ = "" break cusip(inputStr$)
loop </lang>
zkl
<lang zkl>fcn cusipCheckDigit(cusip){
var [const] vs=[0..9].chain(["A".."Z"],T("*","@","#")).pump(String); try{ sum:=Walker.cycle(1,2).zipWith(fcn(n,c){ v:=vs.index(c)*n; v/10 + v%10 }, cusip[0,8]).reduce('+); ((10 - sum%10)%10 == cusip[8].toInt()) and cusip.len()==9 }catch{ False }
}</lang> <lang zkl>foreach cusip in (T("037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105")){
println(cusip,": ",cusipCheckDigit(cusip));
}</lang>
- Output:
037833100: True 17275R102: True 38259P508: True 594918104: True 68389X106: False 68389X105: True
- Programming Tasks
- Checksums
- WikipediaSourced
- 11l
- 360 Assembly
- Action!
- Action! Tool Kit
- Ada
- ALGOL 68
- ALGOL W
- AppleScript
- Arturo
- AutoHotkey
- AWK
- BCPL
- C
- C sharp
- C++
- Caché ObjectScript
- Clojure
- CLU
- Common Lisp
- D
- Dyalect
- Excel
- F Sharp
- Factor
- Fortran
- FreeBASIC
- Go
- Groovy
- Haskell
- Icon
- Unicon
- J
- Java
- JavaScript
- Julia
- Kotlin
- Langur
- Lua
- Mathematica
- Wolfram Language
- Modula-2
- Nanoquery
- Nim
- Objeck
- Perl
- Phix
- PHP
- PicoLisp
- PowerShell
- Python
- Quackery
- Racket
- Raku
- REXX
- Ring
- Ruby
- Rust
- Scala
- SNOBOL4
- Swift
- Tcl
- VBA
- Visual Basic .NET
- Vlang
- Wren
- XPL0
- Yabasic
- Zkl