CUSIP

From Rosetta Code
Revision as of 22:19, 3 April 2017 by rosettacode>Gerard Schildberger (→‎{{header|REXX}}: added a 2nd REXX version.)
CUSIP is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
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.

The task here is to ensure the last digit (i.e. check digit) of the CUSIP code is correct, against the following codes.

  • Apple Inc.: 037833100
  • Cisco Systems: 17275R102
  • Google Inc.: 38259P508
  • Microsoft Corporation: 594918104
  • Oracle Corporation (Incorrect): 68389X106
  • Oracle Corporation: 68389X105


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 also

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

AWK

<lang AWK>

  1. 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) {

  1. 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

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>

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() - 64  // 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

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) - 64
     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

Perl 6

Works with: Rakudo version 2017.01

<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;

}

  1. 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

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.

REXX

idiomatic

<lang rexx>/*REXX program validates that the last digit (the check digit) of a CUSPID 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=CUSPIDchk(@.j)    /*calculate check digit from func*/
    OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate  check digit with func*/
    say 'CUSPID '    @.j    right(OK, 6)     "valid." /*display the CUSPID and validity*/
    end   /*j*/

exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ CUSPIDchk: 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.

more concise function

<lang rexx>/*REXX program validates that the last digit (the check digit) of a CUSPID 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=CUSPIDchk(@.j)    /*calculate check digit from func*/
    OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate  check digit with func*/
    say 'CUSPID '    @.j    right(OK, 6)     "valid." /*display the CUSPID and validity*/
    end   /*j*/

exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ CUSPIDchk: procedure; arg x 9; $=0; abc= '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#'

                                   do k=1  for 8;   y=substr(x,k,1) /*get a character. */
                                   #=pos(y, abc)                    /*get its position.*/
                                   if #==0 & y\==0  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.

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