Task

For each number list of 6-digit SEDOLs, calculate and append the checksum digit.



That is, given this input:

710889
B0YBKJ
406566
B0YBLH
228276
B0YBKL
557910
B0YBKR
585284
B0YBKT
B00030

Produce this output:

7108899
B0YBKJ7
4065663
B0YBLH2
2282765
B0YBKL9
5579107
B0YBKR5
5852842
B0YBKT7
B000300

Extra credit

Check each input is correctly formed, especially with respect to valid characters allowed in a SEDOL string.



Related tasks

   Luhn test
   ISIN


#lang racket
;;; Since the Task gives us unchecksummed and checksummed SEDOLs, and
;;; we'll just take a list of the output SEDOLs and remove their last
;;; characters for the input
(define output-SEDOLS
  (list "7108899" "B0YBKJ7" "4065663"
        "B0YBLH2" "2282765" "B0YBKL9"
        "5579107" "B0YBKR5" "5852842"
        "B0YBKT7" "B000300"))
(define (output->input-SEDOL S) (substring S 0 6))
(define input-SEDOLS (map output->input-SEDOL output-SEDOLS))
 
;;; checksum calculation
(define (SEDOL-character-value c)
  (if (char-numeric? c)
      (- (char->integer c) (char->integer #\0))
      (+ 10 (- (char->integer c) (char->integer #\A)))))
(define (SEDOL-character-sum S)
  (for/sum ((c S)  ; if we run out of c's before the final 1 in weight, we'll have the unchecksummed weighted sum
            (weight (in-list '(1 3 1 7 3 9 1))))
    (* weight (SEDOL-character-value c))))
(define (SEDOL-checksum S) (number->string (modulo (- 10 (SEDOL-character-sum S)) 10)))
 
;;; build output from input
(define (SEDOL-append-checksum S) (string-append S (SEDOL-checksum S)))
 
;;; Extra credit -- according to wikipedia:
;;; "SEDOLs are seven characters in length"
;;; "vowels are never used"
;;; there seems to be no statement as to case, but we'll assert that too!
;;;
;;; valid-SEDOL? is a predicate... it doesn't report a reason
(define (invalid-SEDOL? S)
  (define (invalid-SEDOL-character? c)
    (if
     (and (not (char-upper-case? c)) (not (char-numeric? c)))
     (format "contains non upper case/non numeric ~a" c)
     (case c [(#\A #\E #\I #\O #\U) (format "contains vowel ~a" c)] [else #f])))
  (cond
    [(< (string-length S) 7) "too few characters"]
    [(> (string-length S) 7) "too many characters"]
    [(not (zero? (modulo (SEDOL-character-sum S) 10))) "invalid checksum"]
    [(for/first ((c S) #:when (invalid-SEDOL-character? c)) c) => identity]
    [else #f])) ; a.k.a. valid!
 
(module+ main
  (for* ((S input-SEDOLS))
    (displayln (SEDOL-append-checksum S)))
  (newline)
  (displayln "Extra Credit!")
  (displayln (invalid-SEDOL? "B0YBKT7")) ; expect #f output
  (displayln (invalid-SEDOL? "B000301")) ; expect "invalid checksum" output
  )
 
(module+ test
  (require rackunit)  
  (check-= (SEDOL-character-value #\3) 3 0)
  (check-= (SEDOL-character-value #\B) 11 0)
  (check-equal? (invalid-SEDOL? "B000301") "invalid checksum")
  (for ((S output-SEDOLS))
    (check-false (invalid-SEDOL? S))
    (check-equal? (SEDOL-append-checksum (substring S 0 6))
                  S (format "test SEDOL for ~a" S))))