A Middling Thruppence? Do They Like Us At All? 

Wherein we pace around the in the center of the room while checking the stock ticker, our broker on the line, buying and selling their dreams back to them.

THE WEEKLY CHALLENGE – PERL & RAKU #135


episode one:
“A Middle-of-the-Road Production”


Task 1

Middle 3-digits

Submitted by: Mohammad S Anwar

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

Method

First things first, we’re instructed not to do this if we can’t. So what would preclude us from delivering the middle three digits from a number? Well basically there are two qualifiers in that request, so two cases immediately come to mind. In the first situation we could not have three digits available to produce. So the  number must, by definition, have three or more digits. 

In the second case, we are asked for the “middle” trio. Not “middlemost” — to be in the middle one needs the exact same amount of stuff on either side. As such we are going to consider the request to be literal, not figurative. As a result of this hard-lined reading we will also be constrained to a number that has a single middle segment, equally padded on each side by at least one additional digit. We don’t ultimately care how many more additional digits there are beyond the one on either side, as long as the two segments are the same length, and there is a distinct individual center bracketed by two more. As this condition seems a bit like a broader rephrasing of the first — we not only need three digits, but three digits in the middle — we could wrap this up into a single error state, but as we’re asked for verbose, “sensible” error reporting, and the examples give two options, we’ll follow that lead.

The length of the number, the number of digits, must therefore fit the pattern of 3 digits plus 2n more, for any n greater than or equal to 0. Because 3 is odd, and 2 times anything is even, the result will be an odd number of digits.

So we can conclude our number must also have an odd number of digits.

Anything else? Well it does have to be a number; perhaps we should have led with that one. So that means whatever it is, it must be composed solely from the digits 0 through 9. As we already called it a number perhaps this is a bit pedantic, but whatever, we’re being thorough. But that does open up a can of worms over negative signs / hyphens. Furthermore, I think we’ll ignore alternate number representation characters for this one and stick to Arabic numerals.

PERL 5 SOLUTION

To get what we want, we’ll start by identifying the exclusionary states and gracefully bowing out unless we pass the preflight checks. We can neatly sidestep the hyphen situation by first using a regular expression to gently remove a single leading hyphen, which also nicely gives us an absolute value at the same time. If after that the string still contains a number we toss it out as malformed nonsense. We’re serious people here and have no time for such shenanigans. Once that is done, though, we know certain things about the number, and can assume we have the same equal number of 0-or-more digits around the innermost triplet. So we move from there by lopping digits in pairs from the front and back until there’s only three remaining. 

We could, I suppose, compute the center positions and do everything mathematically with a single substr, but this lisp-y way is more fun. Maybe we’ll do it the right way next time.

use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';


## main
say middle_three( shift @ARGV ) if @ARGV;



sub middle_three ( $num ) {
    $num = abs( $num );
    my $len = length $num;
    if ( $len < 3 ) {
        return "input too short";
    }
    unless ( $len % 2) {
        return "even number of digits";
    }
    
    while ( length $num > 3 ) {
        substr $num, -1, 1, '';
        substr $num,  0, 1, '';
    }
    
    return $num;
}
raku solution

Raku give us an opportunity to use a given/when construct to filter out the contradicted cases, and after that’s done I’ve come around and gone ahead and calculated out the substring location for the triplet, and yes, of course that solution is far and away easier and probably clearer as well.

I do still like lopping down the sides until the desired extract is all that remains, whittling out the solution from the raw number to reveal its core, but admit it really isn’t perhaps the best way to go about things. We should just let that go and speak no more about it. (But it is cool.)

sub mid3 ( $num is copy ) {

    $num .= abs;
    
    given $num.chars {
        when    $_ < 3      { return "input too short" }
        when    $_ %% 2     { return "even number of digits" }
    }
    
    $num.substr: ($num.chars/2).floor-1, 3
}

episode two:
“Do-Si-Do? We Dosed it All!”


task 2

Validate SEDOL

Submitted by: Mohammad S Anwar

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.

For more information about SEDOL, please checkout the wikipedia page.

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

Method

The SEDOL, the Stock Exchange Daily Official List, is a system of cataloging certain financial instruments used in the United Kingdom. SEDOL codes are issued by a central authority to specify specific stocks, bonds, and securities offerings presented to the marketplace, allowing differentiation using a unique alphanumeric sequence of 7 characters.

The SEDOL codes are a product of the London Stock Exchange — analogous systems in place are the CUSIP codes in the  United States and Canada and the international ISIN standard.

The 7-character alphanumeric code consists of 6 digits followed by a checksum value. To compute the checksum, each character is first given a value; numeric digits are simply their value; then the alphabetic portion follows the sequence 0 through 9 and continues counting upwards to 35.

Of note here is that although the vowels — A, E, I, O, and U — are excluded from the sequence of assignable characters, they are not excluded from the list for value calculation. Thus “B” is valued to 11, as “A”, even though it will be sequentially skipped-over and never be used, takes the value 10. Likewise “E”, the next excluded character, takes the 15 valuation, and the following character, “F” is assigned 16. 

Each derived value is then assigned a multiplier based on its digit position, from left to right:

  • 1 → 1 ×
  • 2 → 3 ×
  • 3 → 1 ×
  • 4 → 7 ×
  • 5 → 3 ×
  • 6 → 9 ×
  • 7 → 1 × (the checksum digit)

The sum of all position value products, including the checksum digit, will be a multiple of 10. Thus the checksum is that value required to make this true, to make up the difference to bring the final digit to 0.

To calculate the checksum digit we sum the first 6 digits according to the procedure, then apply the formula

checksum = ( 10 – DIGITSUM modulo 10 ) modulo 10

It follows that should we be given a 7-character SEDOL code, we can calculate the checksum from the first 6 digits given and compare the result to the existing 7th digit to validate whether the code has been correctly constructed. So that is what we shall do.

PERL 5 SOLUTION

After basic input validation that we have nothing but alphanumeric characters and no vowels, and that we have 7 characters, we compute a lookup table for the  valid characters, pointing to their numeric valuations. From there the first 6 positions of the number are examined using substr, and the derived value times its position multiplier is added to a running sum for the checksum digit. 

When the partial traversal is complete, the checksum is compared to the given last digit, and if they match the SEDOL code is correctly constructed. This doesn’t guarantee that the given code points to the correct financial instrument, or any instrument at all, as it may be as-yet unassigned. But you know at least it hasn’t been digitally mangled somehow in transit, or quickly made-up by a yahoo in an internet cafe. It could still be more carefully made up by a bad actor, but at least they care enough to do things right. 

I mean, if you’re going to lie to my face, at least make an effort. A low-effort scam is just insulting.

use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';


## main
say validate_sedol( shift @ARGV ) if @ARGV;



sub validate_sedol ( $candidate ) {

    return 0 unless $candidate =~ m/^ [B-DF-HJ-NP-TV-Z0-9]{6} \d $/x;

    ## assign alphanumeric values
    my $val = -1;
    my %clookup = map { $_ => ++$val } (0..9, 'A'..'Z');

    ## fixed SEDOL weight values 
    my $ws = 0;
    my @weights = (1, 3, 1, 7, 3, 9, 1);

    $ws += $weights[$_] * $clookup{ substr $candidate, $_, 1 } for (0..5);

    my $cs_calculated = ( 10 - $ws % 10) % 10;
    my $cs_digit      = substr $candidate, 6, 1;

    return $cs_digit == $cs_calculated 
        ? 1 
        : 0 ;
}
Raku Solution

In Raku, we have a couple of operators that allow us to make some interesting refactoring choices. The lookup hash for alphanumeric values can be produced in a single line by returning the index value pairs from an array and reversing them, providing the basis for the hash in one action. Likewise computing the weighted sum can be done by producing  an intermediate array of weight-multiplied values with a map function, then summing the elements. We could ultimately refactor out both of the $cs_calculated and $cs_digit variables as well, at the cost of clarity about what we’re doing here, but I’m going to leave that be for now.

sub validate_sedol ( $code ) {

    return 0 unless $code ~~ m:i/^ [<[ A..Z 0..9 ]> && <-[A E I O U]>] ** 6 <[0..9]> $/ ;

    ## assign alphanumeric values
    my %clookup = (|('0'..'9'), |('A'..'Z')).kv.reverse;

    ## fixed SEDOL weight values 
    my @weights = 1, 3, 1, 7, 3, 9, 1;
    
    my $ws = (0..5).map({ @weights[$_] * %clookup{ $code.substr($_, 1) } })
                   .sum;

    my $cs_calculated = (10 - $ws % 10) % 10;
    my $cs_digit      = $code.substr: 6, 1;
    
    return $cs_digit == $cs_calculated 
        ?? 1
        !! 0
}


The Perl Weekly Challenge, that idyllic glade wherein we stumble upon the holes for these sweet descents, is now known as

The Weekly Challenge – Perl and Raku

It is the creation of the lovely Mohammad Sajid Anwar and a veritable swarm of contributors from all over the world, who gather, as might be expected, weekly online to solve puzzles. Everyone is encouraged to visit, learn and contribute at

https://theweeklychallenge.org