SEDOLs
You are encouraged to solve this task according to the task description, using any language you may know.
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
Produce this output:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
For extra credit, check each input is correctly formed, especially with respect to valid characters allowed in a SEDOL string.
Ada
<lang ada>with Ada.Text_IO; use Ada.Text_IO;
procedure Test_SEDOL is
subtype SEDOL_String is String (1..6); type SEDOL_Sum is range 0..9;
function Check (SEDOL : SEDOL_String) return SEDOL_Sum is Weight : constant array (SEDOL_String'Range) of Integer := (1,3,1,7,3,9); Sum : Integer := 0; Item : Integer; begin for Index in SEDOL'Range loop Item := Character'Pos (SEDOL (Index)); case Item is when Character'Pos ('0')..Character'Pos ('9') => Item := Item - Character'Pos ('0'); when Character'Pos ('B')..Character'Pos ('D') | Character'Pos ('F')..Character'Pos ('H') | Character'Pos ('J')..Character'Pos ('N') | Character'Pos ('P')..Character'Pos ('T') | Character'Pos ('V')..Character'Pos ('Z') => Item := Item - Character'Pos ('A') + 10; when others => raise Constraint_Error; end case; Sum := Sum + Item * Weight (Index); end loop; return SEDOL_Sum ((-Sum) mod 10); end Check;
Test : constant array (1..10) of SEDOL_String := ( "710889", "B0YBKJ", "406566", "B0YBLH", "228276", "B0YBKL", "557910", "B0YBKR", "585284", "B0YBKT" );
begin
for Index in Test'Range loop Put_Line (Test (Index) & Character'Val (Character'Pos ('0') + Check (Test (Index)))); end loop;
end Test_SEDOL;</lang> The function Check raises Constraint_Error upon an invalid input. The calculated sum is trimmed using (-sum) mod 10, which is mathematically equivalent to (10 - (sum mod 10)) mod 10.
Sample output:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
ALGOL 68
<lang algol68>[]INT sedol weights = (1, 3, 1, 7, 3, 9); STRING reject = "AEIOUaeiou";
PROC strcspn = (STRING s,reject)INT: (
INT out:=0; FOR i TO UPB s DO IF char in string(s[i], LOC INT, reject) THEN return out FI; out:=i OD; return out: out
);
PROC sedol checksum = (REF STRING sedol6)INT: (
INT out;
INT len := UPB sedol6; INT sum := 0; IF sedol6[len-1] = REPR 10 THEN len-:=1; sedol6[len]:=null char FI; IF len = 7 THEN putf(stand error, ($"SEDOL code already checksummed? ("g")"l$, sedol6)); out := ABS ( BIN ABS sedol6[6] AND 16r7f); return out FI; IF len > 7 OR len < 6 OR strcspn(sedol6, reject) /= 6 THEN putf(stand error, ($"not a SEDOL code? ("g")"l$, sedol6)); out := -1; return out FI; FOR i TO UPB sedol6 DO sum+:=sedol weights[i]* IF is digit(sedol6[i]) THEN ABS sedol6[i]- ABS "0" ELIF is alpha(sedol6[i]) THEN (ABS to upper(sedol6[i])-ABS "A") + 10 ELSE putf(stand error, $"SEDOL with not alphanumeric digit"l$); out:=-1; return out FI OD; out := (10 - (sum MOD 10)) MOD 10 + ABS "0"; return out: out
);
main: (
STRING line; on logical file end(stand in, (REF FILE f)BOOL: done); DO getf(stand in, ($gl$,line)); INT sr := sedol checksum(line); IF sr > 0 THEN printf(($ggl$, line, REPR sedol checksum(line))) FI OD; done: SKIP
)</lang> Output:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
AutoHotkey
ahk forum: discussion <lang AutoHotkey>MsgBox % SEDOL("710889") ;7108899 MsgBox % SEDOL("B0YBKJ") ;B0YBKJ7 MsgBox % SEDOL("406566") ;4065663 MsgBox % SEDOL("B0YBLH") ;B0YBLH2 MsgBox % SEDOL("228276") ;2282765 MsgBox % SEDOL("B0YBKL") ;B0YBKL9 MsgBox % SEDOL("557910") ;5579107 MsgBox % SEDOL("B0YBKR") ;B0YBKR5 MsgBox % SEDOL("585284") ;5852842 MsgBox % SEDOL("B0YBKT") ;B0YBKT7
SEDOL(w) {
Static w1:=1,w2:=3,w3:=1,w4:=7,w5:=3,w6:=9 Loop Parse, w s += ((c:=Asc(A_LoopField))>=Asc("A") ? c-Asc("A")+10 : c-Asc("0")) * w%A_Index% Return w mod(10-mod(s,10),10)
}</lang>
AWK
Validate or calculate checksum of SEDOL codes read from standard input (one per line)
<lang awk>function ord(a) {
return amap[a]
}
function sedol_checksum(sed) {
sw[1] = 1; sw[2] = 3; sw[3] = 1 sw[4] = 7; sw[5] = 3; sw[6] = 9 sum = 0 for(i=1; i <= 6; i++) { c = substr(toupper(sed), i, 1) if ( c ~ /digit:/ ) { sum += c*sw[i] } else { sum += (ord(c)-ord("A")+10)*sw[i] } } return (10 - (sum % 10)) % 10
}
BEGIN { # prepare amap for ord
for(_i=0;_i<256;_i++) { astr = sprintf("%c", _i) amap[astr] = _i }
}
/[AEIOUaeiou]/ {
print "'" $0 "' not a valid SEDOL code" next
} {
if ( (length($0) > 7) || (length($0) < 6) ) { print "'" $0 "' is too long or too short to be valid SEDOL" next } sedol = substr($0, 1, 6) sedolcheck = sedol_checksum(sedol) if ( length($0) == 7 ) { if ( (sedol sedolcheck) != $0 ) { print sedol sedolcheck " (original " $0 " has wrong check digit" } else { print sedol sedolcheck } } else { print sedol sedolcheck }
}</lang>
BASIC
<lang qbasic>DECLARE FUNCTION getSedolCheckDigit! (str AS STRING) DO
INPUT a$ PRINT a$ + STR$(getSedolCheckDigit(a$))
LOOP WHILE a$ <> ""
FUNCTION getSedolCheckDigit (str AS STRING)
IF LEN(str) <> 6 THEN PRINT "Six chars only please" EXIT FUNCTION END IF str = UCASE$(str) DIM mult(6) AS INTEGER mult(1) = 1: mult(2) = 3: mult(3) = 1 mult(4) = 7: mult(5) = 3: mult(6) = 9 total = 0 FOR i = 1 TO 6 s$ = MID$(str, i, 1) IF s$ = "A" OR s$ = "E" OR s$ = "I" OR s$ = "O" OR s$ = "U" THEN PRINT "No vowels" EXIT FUNCTION END IF IF ASC(s$) >= 48 AND ASC(s$) <= 57 THEN total = total + VAL(s$) * mult(i) ELSE total = total + (ASC(s$) - 55) * mult(i) END IF
NEXT i getSedolCheckDigit = (10 - (total MOD 10)) MOD 10
END FUNCTION</lang>
C
Notes: it reads the codes from standard input, one per line (linefeed terminated); the input encoding must meet the following specifications: single byte encoding, digits (0-9) must have codes that follow the same order of the digits (0, 1, 2, ...) and similar for letters, the encoding must match the one used with the compiled source (likely, ASCII based encodings). This should happen 99% of the time (for ASCII, ISO-8859 family and UTF-8 have the same byte encoding for alphanumeric characters).
<lang c>#include <stdio.h>
- include <ctype.h>
- include <string.h>
int sedol_weights[] = {1, 3, 1, 7, 3, 9}; const char *reject = "AEIOUaeiou";
int sedol_checksum(const char *sedol6) {
int len = strlen(sedol6); int sum = 0, i;
if ( len == 7 ) { fprintf(stderr, "SEDOL code already checksummed? (%s)\n", sedol6); return sedol6[6] & 0x7f; } if ( (len > 7) || (len < 6) || ( strcspn(sedol6, reject) != 6 )) { fprintf(stderr, "not a SEDOL code? (%s)\n", sedol6); return -1; } for(i=0; i < 6; i++) { if ( isdigit(sedol6[i]) ) { sum += (sedol6[i]-'0')*sedol_weights[i]; } else if ( isalpha(sedol6[i]) ) { sum += ((toupper(sedol6[i])-'A') + 10)*sedol_weights[i]; } else { fprintf(stderr, "SEDOL with not alphanumeric digit\n"); return -1; } } return (10 - (sum%10))%10 + '0';
}
- define MAXLINELEN 10
int main() {
char line[MAXLINELEN]; int sr, len; while( fgets(line, MAXLINELEN, stdin) != NULL ) { len = strlen(line); if ( line[len-1] == '\n' ) line[len-1]='\0'; sr = sedol_checksum(line); if ( sr > 0 ) printf("%s%c\n", line, sr); } return 0;
}</lang>
Fed the input list from the task description, the output is:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
C#
<lang csharp> static int[] sedol_weights = new int[] { 1, 3, 1, 7, 3, 9 }; static int sedolChecksum(string sedol) {
int len = sedol.Length; int sum = 0;
if (len == 7) //SEDOL code already checksummed? return (int)sedol[6];
if ((len > 7) || (len < 6) || System.Text.RegularExpressions.Regex.IsMatch(sedol, "[AEIOUaeiou]+")) //invalid SEDOL return -1;
for (int i = 0; i < 6; i++) { if (Char.IsDigit(sedol[i])) sum += (((int)sedol[i] - 48) * sedol_weights[i]);
else if (Char.IsLetter(sedol[i])) sum += (((int)Char.ToUpper(sedol[i]) - 55) * sedol_weights[i]);
else return -1;
}
return (10 - (sum % 10)) % 10;
} </lang>
Common Lisp
Implemented from scratch using the description on Wikipedia as specification.
<lang lisp>(defun append-sedol-check-digit (sedol &key (start 0) (end (+ start 6)))
(assert (<= 0 start end (length sedol))) (assert (= (- end start) 6)) (labels ((char-value (char)
(let ((code (char-code char))) (cond ((<= #.(char-code #\A) code #.(char-code #\Z)) (+ 10 (- code #.(char-code #\A)))) ((<= #.(char-code #\a) code #.(char-code #\z)) (+ 10 (- code #.(char-code #\a)))) ((<= #.(char-code #\0) code #.(char-code #\9)) (- code #.(char-code #\0))) (t (error "unsupported character ~S" char))))))
(loop :with checksum = 0 :for weight :in '(1 3 1 7 3 9) :for index :upfrom start :do (incf checksum (* weight (char-value (char sedol index)))) :finally (let* ((posn (- 10 (mod checksum 10)))
(head (subseq sedol start end)) (tail (char "0123456789" posn))) (return (concatenate 'string head (list tail)))))))</lang>
D
Partially translated from the C implementation. <lang d>import std.stdio; import std.ctype; import std.string; int weights[] = [1, 3, 1, 7, 3, 9];
char sedol_checksum(char[]sedol6) {
int sum = 0; if ( sedol6.length != 6 ) { writefln("Invalid pre-checksum SEDOL code (%s)", sedol6); return cast(char)-1; } foreach(i,num;sedol6) { if ( isdigit(num) ) { sum += (num-'0')*weights[i]; } else if ( isalpha(num) ) { sum += (std.ctype.toupper(num) - 'A' + 10)*weights[i]; } else { writefln("SEDOL with non alphanumeric digit\n"); return cast(char)-1; } } return cast(char)('0' + 10 - (sum%10));
}
int main() {
char[]line; char ss; while((line = chomp(readln())).length) { ss = sedol_checksum(line); if ( ss > 0 ) writefln("%s", line~ss); } return 0;
}</lang>
E
<lang e>def weights := [1,3,1,7,3,9] def Digit := ('0'..'9') def Letter := ('B'..'D'|'F'..'H'|'J'..'N'|'P'..'T'|'V'..'Z') def sedolCharValue(c) {
switch (c) { match digit :Digit { return digit - '0' } match letter :Letter { return letter - 'A' } }
}
def checksum(sedol :String) {
require(sedol.size() == 6) var sum := 0 for i => c in sedol { sum += weights[i] * sedolCharValue(c) } return E.toString((10 - sum %% 10) %% 10)
}
def addChecksum(sedol :String) {
return sedol + checksum(sedol)
}
for sedol in "710889
B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT".trim().split("\n") { println(addChecksum(sedol.trim()))
}</lang>
Forth
<lang forth>create weight 1 , 3 , 1 , 7 , 3 , 9 ,
- char>num ( '0-9A-Z' -- 0..35 )
dup [char] 9 > 7 and - [char] 0 - ;
- check+ ( sedol -- sedol' )
6 <> abort" wrong SEDOL length" 0 ( sum ) 6 0 do over I + c@ char>num weight I cells + @ * + loop 10 mod 10 swap - 10 mod [char] 0 + over 6 + c! 7 ;
- sedol" [char] " parse check+ type ;
sedol" 710889" 7108899 ok sedol" B0YBKJ" B0YBKJ7 ok sedol" 406566" 4065663 ok sedol" B0YBLH" B0YBLH2 ok sedol" 228276" 2282765 ok sedol" B0YBKL" B0YBKL9 ok sedol" 557910" 5579107 ok sedol" B0YBKR" B0YBKR5 ok sedol" 585284" 5852842 ok sedol" B0YBKT" B0YBKT7 ok</lang>
Fortran
<lang fortran>MODULE SEDOL_CHECK
IMPLICIT NONE CONTAINS FUNCTION Checkdigit(c) CHARACTER :: Checkdigit CHARACTER(6), INTENT(IN) :: c CHARACTER(36) :: alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" INTEGER, DIMENSION(6) :: weights = (/ 1, 3, 1, 7, 3, 9 /), temp INTEGER :: i, n
DO i = 1, 6 temp(i) = INDEX(alpha, c(i:i)) - 1 END DO temp = temp * weights n = MOD(10 - (MOD(SUM(temp), 10)), 10) Checkdigit = ACHAR(n + 48) END FUNCTION Checkdigit
END MODULE SEDOL_CHECK
PROGRAM SEDOLTEST
USE SEDOL_CHECK IMPLICIT NONE CHARACTER(31) :: valid = "0123456789BCDFGHJKLMNPQRSTVWXYZ" CHARACTER(6) :: codes(10) = (/ "710889", "B0YBKJ", "406566", "B0YBLH", "228276" , & "B0YBKL", "557910", "B0YBKR", "585284", "B0YBKT" /) CHARACTER(7) :: sedol INTEGER :: i, invalid
DO i = 1, 10 invalid = VERIFY(codes(i), valid) IF (invalid == 0) THEN sedol = codes(i) sedol(7:7) = Checkdigit(codes(i)) ELSE sedol = "INVALID" END IF WRITE(*, "(2A9)") codes(i), sedol END DO
END PROGRAM SEDOLTEST</lang> Output
710889 7108899 B0YBKJ B0YBKJ7 406566 4065663 B0YBLH B0YBLH2 228276 2282765 B0YBKL B0YBKL9 557910 5579107 B0YBKR B0YBKR5 585284 5852842 B0YBKT B0YBKT7
Haskell
<lang haskell>import Data.Char
char2value c | c `elem` "AEIOU" = error "No vowels."
| c >= '0' && c <= '9' = ord c - ord '0' | c >= 'A' && c <= 'Z' = ord c - ord 'A' + 10
sedolweight = [1,3,1,7,3,9]
checksum sedol = show ((10 - (tmp `mod` 10)) `mod` 10)
where tmp = sum $ zipWith (*) sedolweight $ map char2value sedol
main = mapM_ (\sedol -> putStrLn $ sedol ++ checksum sedol)
[ "710889", "B0YBKJ", "406566", "B0YBLH", "228276", "B0YBKL", "557910", "B0YBKR", "585284", "B0YBKT" ]</lang>
J
There are several ways to perform this in J. This most closely follows the algorithmic description at Wikipedia: <lang j>sn =. '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' ac0 =: (, 10 | 1 3 1 7 3 9 +/@:* -)&.(sn i. |:)</lang> However, because J is so concise, having written the above, it becomes clear that the negation (-) is unneccsary.
The fundamental operation is the linear combination (+/@:*) and neither argument is "special". In particular, the coefficients are just another array participating in the calculation, and there's no reason we can't modify them as easily as the input array. Having this insight, it is obvious that manipulating the coefficients, rather than the input array, will be more efficient (because the coefficients are fixed at small size, while the input array can be arbitrarily large).
Which leads us to this more efficient formulation: <lang j>ac1 =: (, 10 | (10 - 1 3 1 7 3 9) +/@:* ])&.(sn i. |:)</lang> which reduces to: <lang j>ac1 =: (, 10 | 9 7 9 3 7 1 +/@:* ])&.(sn i. |:)</lang> Which is just as concise as ac0, but faster.
Following this train of thought, our array thinking leads us to realize that even the modulous isn't neccesary. The number of SEDOL numbers is finite, as is the number of coefficients; therefore the number of possible linear combinations of these is finite. In fact, there are only 841 possible outcomes. This is a small number, and can be efficiently stored as a lookup table (even better, since the outcomes will be mod 10, they are restricted to the digits 0-9, and they repeat).
Which leads us to: <lang j>ac2 =. (,"1 0 (841 $ '0987654321') {~ 1 3 1 7 3 9 +/ .*~ sn i. ])</lang> Which is more than twice as fast as even the optimized formulation (ac1), though it is slightly longer.
Java
<lang java>import java.util.Scanner;
public class SEDOL{ public static void main(String[] args){ Scanner sc = new Scanner(System.in); while(sc.hasNext()){ String sedol = sc.next(); System.out.println(sedol + getSedolCheckDigit(sedol)); } }
private static final int[] mult = {1, 3, 1, 7, 3, 9};
public static int getSedolCheckDigit(String str){ if(!validateSedol(str)){ System.err.println("SEDOL strings must contain six characters with no vowels."); return -1; } str = str.toUpperCase(); int total = 0; for(int i = 0;i < 6; i++){ char s = str.charAt(i); total += Character.digit(s, 36) * mult[i]; } return (10 - (total % 10)); }
public static boolean validateSedol(String str){ return (str.length() == 6) && !str.toUpperCase().matches(".*?[AEIOU].*?"); } }</lang>
JavaScript
<lang javascript>function sedol(input) {
return input + sedol_check_digit(input);
}
var weight = [1, 3, 1, 7, 3, 9, 1]; function sedol_check_digit(char6) {
if (char6.search(/^[0-9BCDFGHJKLMNPQRSTVWXYZ]{6}$/) == -1) throw "Invalid SEDOL number '" + char6 + "'"; var chars = char6.split(); var sum = 0; for (var i in chars) sum += weight[i] * char_to_value(chars[i]); var check = (10 - sum%10) % 10; return check.toString();
}
function char_to_value(ch) {
var val = ch.charCodeAt(0); if (48 <= val && val <= 57) return val - 48; else return val - 65 + 10;
}
var input = [
'710889', 'B0YBKJ', '406566', 'B0YBLH', '228276', 'B0YBKL', '557910', 'B0YBKR', '585284', 'B0YBKT', "BOATER" , "12345", "123456", "1234567"
];
var expected = [
'7108899', 'B0YBKJ7', '4065663', 'B0YBLH2', '2282765', 'B0YBKL9', '5579107', 'B0YBKR5', '5852842', 'B0YBKT7', null, null, '1234563', null
];
for (var i in input) {
try { var sedolized = sedol(input[i]); if (sedolized == expected[i]) print(sedolized); else print("error: calculated sedol for input " + input[i] + " is " + sedolized + ", but it should be " + expected[i] ); } catch (e) { print("error: " + e); }
}</lang> output
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7 error: Invalid SEDOL number 'BOATER' error: Invalid SEDOL number '12345' 1234563 error: Invalid SEDOL number '1234567'
M4
<lang M4>divert(-1) changequote(`[',`]') define([_bar],include(sedol.inp)) define([eachlineA],
[ifelse(eval($2>0),1, [$3(substr([$1],0,$2))[]eachline(substr([$1],incr($2)),[$3])])])
define([eachline],[eachlineA([$1],index($1,[ ]),[$2])]) define([_idx],
[index([0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ],substr($1,$2,1))])
define([_wsum],
[eval(_idx($1,0)+_idx($1,1)*3+_idx($1,2)+_idx($1,3)*7+_idx($1,4)*3+_idx($1,5)*9)])
define([checksum],
[$1[]eval((10-_wsum($1)%10)%10)
]) divert eachline(_bar,[checksum])</lang>
Modula-3
<lang modula3>MODULE SEDOL EXPORTS Main;
IMPORT IO, Fmt, Text, Stdio;
EXCEPTION BadSedol(TEXT);
VAR test := ARRAY [1..10] OF TEXT {"710889", "B0YBKJ", "406566", "B0YBLH",
"228276", "B0YBKL", "557910", "B0YBKR", "585284", "B0YBKT" };
PROCEDURE Check(sed: TEXT): INTEGER RAISES {BadSedol}=
VAR weights := ARRAY [0..5] OF INTEGER {1, 3, 1, 7, 3, 9}; result, d: INTEGER; char: CHAR; BEGIN IF Text.Length(sed) # 6 THEN RAISE BadSedol("ERROR: Must be 6 digits."); END; result := 0; FOR i := 0 TO 5 DO char := Text.GetChar(sed, i); CASE char OF | '0'..'9' => d := ORD(char) - ORD('0'); | 'B'..'D', 'F'..'H', 'J'..'N', 'P'..'T', 'V'..'Z' => d := ORD(char) - ORD('A') + 10; ELSE RAISE BadSedol("ERROR: Must be numbers or (non-vowel) letters."); END; INC(result, d * weights[i]); END; result := (10 - (result MOD 10)) MOD 10; RETURN result; END Check;
BEGIN
TRY FOR i := FIRST(test) TO LAST(test) DO IO.Put(test[i] & Fmt.Char(VAL(ORD('0') + Check(test[i]), CHAR)) & "\n"); END; EXCEPT | BadSedol(text) => IO.Put(text & "\n", Stdio.stderr); END;
END SEDOL.</lang> Output:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
OCaml
<lang ocaml>let char2value c =
assert (not (String.contains "AEIOU" c)); match c with '0'..'9' -> int_of_char c - int_of_char '0' | 'A'..'Z' -> int_of_char c - int_of_char 'A' + 10
let sedolweight = [1;3;1;7;3;9]
let explode s =
let result = ref [] in String.iter (fun c -> result := c :: !result) s; List.rev !result
let checksum sedol =
let tmp = List.fold_left2 (fun sum ch weight -> sum + char2value ch * weight) 0 (explode sedol) sedolweight in string_of_int ((10 - (tmp mod 10)) mod 10)
List.iter (fun sedol -> print_endline (sedol ^ checksum sedol))
[ "710889"; "B0YBKJ"; "406566"; "B0YBLH"; "228276"; "B0YBKL"; "557910"; "B0YBKR"; "585284"; "B0YBKT" ]</lang>
Perl
This program reads from standard input. <lang perl>sub sum
{my $n = 0; $n += $_ foreach @_; return $n;}
sub zip
{my $f = shift; my @a = @{shift()}; my @b = @{shift()}; my @result = (); push(@result, $f->(shift @a, shift @b)) while @a and @b; return @result;}
sub char_to_v
{my $c = shift; $c =~ /[AEIOU]/ and die "No vowels"; $c =~ /[A-Z]/ and $c = ord($c) - ord('A') + 10; return $c;}
my @weights = (1, 3, 1, 7, 3, 9); sub sedol
{my $s = shift; my @vs = map {char_to_v $_} split //, $s; my $checksum = sum (zip sub {$_[0] * $_[1]}, \@vs, \@weights); my $check_digit = (10 - $checksum % 10) % 10; return $s . $check_digit;}
while (<>)
{chomp; print sedol($_), "\n";}</lang>
PHP
<lang php>function char2value($c) {
assert(stripos('AEIOU', $c) === FALSE); return intval($c, 36);
}
$sedolweight = array(1,3,1,7,3,9);
function checksum($sedol) {
global $sedolweight; $tmp = array_sum(array_map(create_function('$ch, $weight', 'return char2value($ch) * $weight;'), str_split($sedol), $sedolweight) ); return strval((10 - ($tmp % 10)) % 10);
}
foreach (array('710889',
'B0YBKJ', '406566', 'B0YBLH', '228276', 'B0YBKL', '557910', 'B0YBKR', '585284', 'B0YBKT') as $sedol) echo $sedol, checksum($sedol), "\n";</lang>
Python
<lang python>def char2value(c):
assert c not in 'AEIOU', "No vowels" return int(c, 36)
sedolweight = [1,3,1,7,3,9]
def checksum(sedol):
tmp = sum(map(lambda ch, weight: char2value(ch) * weight, sedol, sedolweight) ) return str((10 - (tmp % 10)) % 10)
for sedol in
710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT .split(): print sedol + checksum(sedol)</lang>
R
<lang r># Read in data from text connection datalines <- readLines(tc <- textConnection("710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT")); close(tc)
- Check data valid
checkSedol <- function(datalines) {
ok <- grep("^[[:digit:][:upper:]]{6}$", datalines) if(length(ok) < length(datalines)) { stop("there are invalid lines") }
} checkSedol(datalines)
- Append check digit
appendCheckDigit <- function(x) {
if(length(x) > 1) return(sapply(x, appendCheckDigit)) ascii <- as.integer(charToRaw(x)) scores <- ifelse(ascii < 65, ascii - 48, ascii - 55) weights <- c(1, 3, 1, 7, 3, 9) chkdig <- (10 - sum(scores * weights) %% 10) %% 10 paste(x, as.character(chkdig), sep="")
} withchkdig <- appendCheckDigit(datalines)
- Print in format requested
writeLines(withchkdig)</lang>
Ruby
<lang ruby>def char2value(c)
raise "No vowels" if 'AEIOU'.include?(c) c.to_i(36)
end
Sedolweight = [1,3,1,7,3,9]
def checksum(sedol)
tmp = sedol.split().zip(Sedolweight).map { |ch, weight| char2value(ch) * weight }.inject(0) { |sum, x| sum + x } ((10 - (tmp % 10)) % 10).to_s
end
for sedol in %w{
710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT } puts sedol + checksum(sedol)
end</lang>
Scala
<lang scala>class SEDOL(s: String) {
require(s.size == 6 || s.size == 7, "SEDOL length must be 6 or 7 characters") require(s.size == 6 || s(6).asDigit == chksum, "Incorrect SEDOL checksum") require(s forall (c => !("aeiou" contains c.toLower)), "Vowels not allowed in SEDOL") def chksum = 10 - ((s zip List(1, 3, 1, 7, 3, 9) map { case (c, w) => c.asDigit * w } sum) % 10) override def toString = s.take(6) + chksum
}</lang>
Test cases:
scala> """710889 | B0YBKJ | 406566 | B0YBLH | 228276 | B0YBKL | 557910 | B0YBKR | 585284 | B0YBKT""".lines.map(_.trim).foreach(s => println(new SEDOL(s))) 7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7
Validations:
scala> new SEDOL("12") java.lang.IllegalArgumentException: requirement failed: SEDOL length must be 6 or 7 characters scala> new SEDOL("7108890") java.lang.IllegalArgumentException: requirement failed: Incorrect SEDOL checksum scala> new SEDOL("71088A") java.lang.IllegalArgumentException: requirement failed: Vowels not allowed in SEDOL
Smalltalk
<lang smalltalk>String extend [
includesAnyOf: aSet [ aSet do: [ :e | (self includes: e) ifTrue: [ ^true ] ]. ^false ]
].</lang>
<lang smalltalk>Object subclass: SEDOL [
|weight charList|
initialize [ weight := Array from: { 1. 3. 1. 7. 3. 9 }. charList := ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' asOrderedCollection) collect: [ :c | ('AEIOU' includes: c) ifTrue: [ nil ] ifFalse: [ c ] ]. ]
SEDOL class >> new [ ^ (self basicNew) initialize ]
"to be considered private" blindCheckDigit: aSEDOL [ |sum| sum := 0. aSEDOL keysAndValuesDo: [ :i :c | ('0123456789' includes: c) ifTrue: [ sum := sum + ((weight at: i) * (Number readFrom: (c asString readStream))). ] ifFalse: [ sum := sum + (((charList indexOf: c) + 9) * (weight at: i)) ] ]. ^ ((10 - (sum rem: 10)) rem: 10) displayString ]
checked: aSEDOL [ (aSEDOL size < 6) | (aSEDOL size > 7) | (aSEDOL asUppercase includesAnyOf: 'AEIOU' asSet ) ifTrue: [ SystemExceptions.InvalidArgument signalOn: aSEDOL reason: 'Not a valid SEDOL' ] ifFalse: [ |t| t := aSEDOL copyFrom: 1 to: 6. ^ t , (self blindCheckDigit: t) ] ]
].</lang>
<lang smalltalk>|sedol| sedol := SEDOL new. { '710889'.
'B0YBKJ'. '406566'. 'B0YBLH'. '228276'. 'B0YBKL'. '557910'. 'B0YBKR'. '585284'. 'B0YBKT' } do: [ :c | (sedol checked: c) displayNl ]</lang>
Standard ML
<lang sml>fun char2value c =
if List.exists (fn x => x = c) (explode "AEIOU") then raise Fail "no vowels" else if Char.isDigit c then ord c - ord #"0" else if Char.isUpper c then ord c - ord #"A" + 10 else raise Match
val sedolweight = [1,3,1,7,3,9]
fun checksum sedol = let
val tmp = ListPair.foldlEq (fn (ch, weight, sum) => sum + char2value ch * weight) 0 (explode sedol, sedolweight)
in
Int.toString ((10 - (tmp mod 10)) mod 10)
end
app (fn sedol => print (sedol ^ checksum sedol ^ "\n"))
[ "710889", "B0YBKJ", "406566", "B0YBLH", "228276", "B0YBKL", "557910", "B0YBKR", "585284", "B0YBKT" ];</lang>
Tcl
<lang tcl>namespace eval sedol {
variable chars {0 1 2 3 4 5 6 7 8 9 "" B C D "" F G H "" J K L M N "" P Q R S T "" V W X Y Z} variable weight {1 3 1 7 3 9 1}
proc checksum {alnum6} { variable chars variable weight set sum 0 set col 0 foreach char [split [string toupper [string range $alnum6 0 5]] ""] { if {[set idx [lsearch -exact $chars $char]] == -1} { error "invalid character: $char" } incr sum [expr {$idx * [lindex $weight $col]}] incr col } return [expr {(10 - ($sum % 10)) % 10}] } proc valid {alnum7} { expr {[checksum [string range $alnum7 0 5]] == [string index $alnum7 6]} }
}
proc assert {condition {message "Assertion failed!"}} {
if { ! [uplevel 1 [list expr $condition]]} { return -code error $message }
}
set codes {710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT} set answers {7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7}
foreach code $codes answer $answers {
set sedol "${code}[sedol::checksum $code]" assert {$sedol eq $answer} "assertion failed: $sedol ne $answer" puts $sedol
}</lang>
Ursala
The straightforward approach closely follows the published specification, using a table-driven finite map (charval) from characters to numbers, and calculating the inner product as a cumulative sum of the weight vector zipped with the product function to the list of character values.
<lang Ursala>#import std
- import nat
alphabet = digits-- ~=`A-~r letters weights = <1,3,1,7,3,9> charval = -:@rlXS num alphabet iprod = sum:-0+ product*p/weights+ charval* checksum = difference/10+ remainder\10+ iprod</lang>
An optimization following the J solution avoids a run-time subtraction by complementing the coefficients at compile time using these definitions in place of those above. <lang Ursala>weights = difference/*10 <1,3,1,7,3,9> checksum = remainder\10+ iprod</lang>
A further performance improvement subsumes the character value lookup and multiplcation table within the same finite map in the version shown below.
<lang Ursala>lookup = -: (^/~& product^|/~& charval)*lsPrK0/weights alphabet iprod = sum:-0+ lookup*p/weights</lang>
To optimize further, we can build a separate smaller multiplication table for each coefficient, letting the coefficient be hard coded and allowing faster lookups. The zipwith operation (*p) is also avoided by having each map index directly into the input list.
<lang Ursala>lookups = (-:+ * ^/~&l product^|/charval ~&)* *-* -*weights alphabet iprod = sum:-0+ gang +^|(~&,~)*lNrXXK9 ^(~&,&h!)* lookups</lang> Here is a test program. <lang Ursala>#show+
examples = ^T(~&,~&h+ %nP+ checksum)*t
-[ 710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL 557910 B0YBKR 585284 B0YBKT]-</lang> output:
7108899 B0YBKJ7 4065663 B0YBLH2 2282765 B0YBKL9 5579107 B0YBKR5 5852842 B0YBKT7