by Arne Sommer

# SEDOL in the Middle with Raku and Perl

[151] Published 23. October 2021.

This is my response to the Perl Weekly Challenge #135.

## Challenge #135.1: Middle 3-digits

You are given an integer.

Write a script find out the middle 3-digits of the given integer, if possible otherwise throw sensible error.

Example 1: ```Input: \$n = 1234567 Output: 345 ``` Example 2: ```Input: \$n = -123 Output: 123 ``` Example 3: ```Input: \$n = 1 Output: too short ``` Example 4: ```Input: \$n = 10 Output: even number of digits ```

I have chosen to print the error message, as done in the examples, instead of throwing an error - as this would have given quite a different output.

File: m3d ```#! /usr/bin/env raku unit sub MAIN (Int \$n); # [1] \$n = \$n.abs if \$n < 0; # [2] given \$n # [3] { when .chars %% 2 { say "even number of digits"; } # [4] when .chars < 3 { say "too short"; } # [5] default { say .substr((.chars - 3) /2, 3); } # [6] } ```

[1] Ensure that we get an integer.

[2] Remove the sign, if any (i.e. if negative).

[3] using `given`/`when`, which is Raku speak for «switch» gives compact code. Note that `given` sets `\$_` to the spcified expression, so that we can use dot notation on nothing - which is shorthand for doing it on `\$_`.

See docs.raku.org/language/control#index-entry-switch_(given) for more information about `given`/`when`.

[4] An even number of digits is an error.

[5] A number with less than three digits is also an error.

[6] We get here if all is well. Print the middle three digits.

Running it:

```\$ ./m3d 1234567 345 \$ ./m3d -123 Usage: ./m3d <n> \$ ./m3d 1 too short \$ ./m3d 10 even number of digits ```

The second example (-123 ) does not satisfy the `Int` constraint, and I have no idea why that is so. The problem is there even if we remove the type on the input (i.e. `unit sub MAIN (\$n);`), so here we have a problem - probably with `MAIN`.

We can do it manually instead:

File: m3d-args ```#! /usr/bin/env raku my \$n = @*ARGS[0]; die "Not an integer" unless \$n ~~ /^\-?<[1..9]><[0..9]>*\$/; # [1] \$n = \$n.abs if \$n < 0; if \$n.chars %% 2 { say "even number of digits"; } # [2] elsif \$n.chars < 3 { say "too short"; } else { say \$n.substr((\$n.chars - 3) /2, 3); } ```

[1] Start with an optional minus sign (`\-?`), one single non-zero digit (`<[1..9]>`) followed by zero or more digits (`<[0..9]>`). I am using explicit digits, as the `\d` regex will also match Unicode digits of all sorts.

[2] I have replaced the `given`/`when` block with a traditional `if`/`elsif`/`else`. Feel free to compare the two versions.

This version works as intended when given negative numbers:

```\$ ./m3d-args -123 123 ```

### A Perl Version

This is straight forward translation of the Raku version.

File: m3d-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; my \$n = \$ARGV[0] // ""; die "integer only" unless \$n =~ /^\-?[1-9]\d*\$/; \$n = abs(\$n) if \$n < 0; if (length(\$n) % 2 == 0) { say "even number of digits"; } elsif (length(\$n) < 3) { say "too short"; } else { say substr(\$n, (length(\$n) -3) / 2, 3); } ```

Running it gives the same result as the last Raku version:

```\$ ./m3d-perl 1234567 345 \$ ./m3d-perl -123 123 \$ ./m3d-perl 1 too short \$ ./m3d-perl 10 even number of digits ```

## Challenge #135.2: Validate SEDOL

You are given 7-characters alphanumeric SEDOL.

Write a script to validate the given SEDOL. Print 1 if it is a valid SEDOL otherwise 0.

Example 1: ```Input: \$SEDOL = '2936921' Output: 1 ``` Example 2: ```Input: \$SEDOL = '1234567' Output: 0 ``` Example 3: ```Input: \$SEDOL = 'B0YBKL9' Output: 1 ```

This is pretty straight forward:

File: validate-sedol ```#! /usr/bin/env raku subset SEDOL where * ~~ /<[0..9 BCDFGHJKLMNPQRSTVWXZ]>**6<[0..9]>/; # [1] unit sub MAIN (SEDOL \$SEDOL); my @weight = (1, 3, 1, 7, 3, 9, 1); my \$sum = (^6).map({ \$SEDOL.substr(\$_, 1).parse-base(35) * @weight[\$_] }).sum; # [2] my \$check = (10 - (\$sum % 10)) % 10; say + (\$SEDOL.substr(6) eq \$check); # [3] ```

[1] We validate the input (length 7, and only legal characters) with a custom type, set up with `subset`. Note that the result is that strings that does not match, will give an error instead of 0.

[2] Using `parse-base` gives us the mapping between digit/letter and value for free. Note the use of `map` instead of a regular `for` loop, which gives a list that we can reduce to a sum with the aptly named `sum` method.

See docs.raku.org/routine/parse-base for more information about `parse-base`.

[3] The comparison gives a Boolean value, and we have to coerce that value into a 0 or 1 with the Numeric Coercion Prefix `+`.

See docs.raku.org/routine/+ for more information about the Prefix Operator `+`.

Running it:

```\$ ./validate-sedol 2936921 1 \$ ./validate-sedol 1234567 0 \$ ./validate-sedol B0YBKL9 Usage: ./validate-sedol <SEDOL> ```

Oops!

The problem with the third example is the letter `Y`. The wikipedia page clearly says that SEDOL identifier does not contain vowels. So this will fail, as `Y` is a wovel. But is it really a wovel? According to www.merriam-webster.com/words-at-play/why-y-is-sometimes-a-vowel-usage `Y` is sometimes a wovel.

Then we have to find which Y-camp SEDOL belongs to. The wikipedia page does not say anything about it, but the code example does treat `Y` as a wovel. It is unclear if this is based on knowledge of SEDOL rules, or is pure chance.

So I found a SEDOL lookup site, Stock Marked MBA, and had a go at the offending third example. It is in use («Abertis Infrastructuras SA»). So we have to allow `Y`.

It is possible to make the program shorter, practically a oneliner. Let us do that at the same time:

File: validate-sedol-onelinerish ```#! /usr/bin/env raku subset SEDOL where * ~~ /<[0..9 BCDFGHJKLMNPQRSTVWXYZ]>**6<[0..9]>/; unit sub MAIN (SEDOL \$SEDOL); say + (\$SEDOL.substr(6) eq (10 − (((^6).map({ \$SEDOL.substr(\$_, 1).parse-base(35) * (1, 3, 1, 7, 3, 9, 1)[\$_] }).sum) % 10)) % 10); ```

Running it:

```\$ ./validate-sedol-onelinerish 2936921 1 \$ ./validate-sedol-onelinerish 1234567 0 \$ ./validate-sedol-onelinerish B0YBKL9 1 ```

Looking good.

It is possible to turn it into a true oneliner, but it is hard enough to understand already.

### Perl

This is a straight forward translation of the Raku version.

File: validate-sedol-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; my \$SEDOL = \$ARGV[0] // ""; die "SEDOL with wrong length and/or characters" unless \$SEDOL =~ /^[0-9|BCDFGHJKLMNPQRSTVWXYZ]{6}[0-9]\$/; my @weight = (1, 3, 1, 7, 3, 9, 1); my @alphabet = (0..9, 'A'..'Z'); my %alphabet = ( map { \$alphabet[\$_] => \$_ } (0 .. @alphabet -1)); # [1] my \$sum; for my \$index (0..5) { \$sum += \$alphabet{ substr(\$SEDOL, \$index, 1) } * \$weight[\$index]; } my \$check = (10 - (\$sum % 10)) % 10; say 0 + (substr(\$SEDOL, 6) eq \$check); ```

[1] Perl does not have `parse-base`, but using reverse lookup on a specially constructed hash to get the values works quite well. Note the use of `map` to set up the hash.

Running it gives the same result as the Raku versions:

```\$ ./validate-sedol-perl 2936921 1 \$ ./validate-sedol-perl 1234567 0 \$ ./validate-sedol-perl B0YBKL9 1 ```

And that's it.