Fun with Algorithms
Estimated Reading Time: 15 minutes
Difficulty: Intermediate
This post is purely illustrative and showcases a few implementations that achieve the same goal. Feel free to use any of them in your implementation.
I often spend time researching and implementing algorithms in multiple languages as a way to keep my programming skillset sharp, learn a new language and its syntactical sugar, and to have the algorithms at my disposal when and if I need them.
This article references
Container Container ISO 6346
Wikipedia ISO 6346
ISO 6346 is an international standard that describes the identification of a shipping container. The standard is maintained by the BIC (International Container Bureau) and covers the serial number, owner, country code, and size of any given shipping container.
An example of a compliant ISO 6346 Code is CSQU3054383
| Owner Code | Category Identifier | Serial Number | Check Digit |
|---|---|---|---|
| CSQ | U | 305438 | 3 |
| [A-Z]{3} | [UJRZ] | \d{6} | \d |
It is relatively straightforward to validate via Regex as shown above. However, the fun algorithm portion that validates a shipping container code is as follows.
Calculation 1
An equivalent numerical value is assigned to each letter of the alphabet, beginning with 10 for the letter A (11 and multiples thereof are omitted) for Owner Code and Category Identifier.
| A | B | C | D | E | F | G | H | I | J | K | L | M |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 |
| N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 |
Calculation 2
Each of the numbers calculated in step 1 is multiplied by 2position, where position is the exponent to base 2. Position starts at 0, from left to right.
| C | S | Q | U | 3 | 0 | 5 | 4 | 3 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| 13 | 30 | 28 | 32 | 3 | 0 | 5 | 4 | 3 | 8 |
| 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 |
| 13 | 60 | 112 | 256 | 48 | 0 | 320 | 512 | 768 | 4096 |
Calculation 3
- Sum up all results of Step above = 6185
- Divide them by 11 = 562.272..
- Round the result down towards zero i.e. make the result a whole number (integer) = 562
- Multiply the integer value by 11 = 6182
- Subtract the result of (4) from the result of (1): This is the check digit = 3
Calculation 1 Implementations
While there are multiple calculations as outlined above, the Calculation 1 implementation is the most interesting. The goal for me was to retrieve the number mapped to an A-Z character without the use of if statements.
Implementation 1 (Dictionary)
This seems simple enough, create a dictionary/object that represents these values. This will be the most common implementation seen on the web.
const alphaReductionDictionary: { [character: string]: number } = {
"A": 10, "B": 12, "C": 13, "D": 14, "E": 15, "F": 16, "G": 17, "H": 18, "I": 19, "J": 20, "K": 21, "L": 23, "M": 24,
"N": 25, "O": 26, "P": 27, "Q": 28, "R": 29, "S": 30, "T": 31, "U": 32, "V": 34, "W": 35, "X": 36, "Y": 37, "Z": 38
}
Implementation 2 (ASCII Character Code w/ Math)
However, I wanted to see if this can be solved via ASCII char code and math alone, as this seems to be a common theme in alphanumeric codes: omit multiples of n
const charCodeReduced = shippingContainerChar.charCodeAt(0) - 55; // this reduces ASCII A to 10, as per algorithm
const charCodeRemainder = charCodeReduced % 11; // identifies occurrences of multiples of 11, and the position of items that arent
const weight = Math.trunc((charCodeReduced - charCodeRemainder) / 10); // generates a number that indicates any addition to charCode needed to skip multiples of 11
return Math.trunc((charCodeReduced + weight) / 11) + charCodeReduced; // generates an integer as per the ISO 6346 specification
Implementation 3 (Most Elegant)
While I have two solid implementations, I realized that position played a part in the calculation, and with A starting at 10, 0-9 fit nicely in front of it. I thought I was original in this concept however, it seems that this is a valid way of solving this problem as well. It took some digging, but it was found on bitcoinwiki.
const characterIndexMap: string = "0123456789A?BCDEFGHIJK?LMNOPQRSTU?VWXYZ";
return Number(characterIndexMap.indexOf(shippingContainerChar, 1));
With these implementations in place, creating a function that completes the algorithm is easy. Note, the full implementation allows the implementations above to be injected into the calculation process.
export const isISO6346Compliant = function (containerNumber: string, getCharValue: (c: string) => number): boolean {
// fail fast if any parameter is null or undefined
if (!containerNumber || /^\s+$/.test(containerNumber) || !getCharValue) return false;
// validate for ISO 6346 compliant container number
const isoPattern: RegExp = /^[A-Z]{3}[UJRZ]\d{6}\d$/;
// fail fast if the containerNumber does not meet ISO 6346 standards
if (!isoPattern.test(containerNumber)) return false;
// get check digit (last character of containerNumber)
const checkDigit: number = Number(containerNumber[containerNumber.length - 1]);
/*
* Calculation Step 3a
*
* calculate sum of container number chars multiplied by exponent to base 2,
* the last character (check digit) is omitted from calculation as per spec
*/
let calcSumExpB2: number = 0;
for (let i = 0; i < containerNumber.length - 1; i++) {
let extractedValue: number = 0;
const contNumChar: string = containerNumber[i];
/*
* the first 4 characters are alphabetical, they need to be converted with
* multiples of 11 omitted
*/
extractedValue = (i < 4)? getCharValue(contNumChar) : Number(contNumChar);
// multiply the extracted value by exponent to base 2 using the position of the char
calcSumExpB2 += extractedValue * Math.pow(2, i);
}
// Calculation Step 3b: Division by 11
let stepCalc = calcSumExpB2 / 11;
// Calculation Step 3c: Erase decimal digits
stepCalc = Math.trunc(stepCalc);
// Calculation Step 3d: Multiply by 11
stepCalc *= 11;
// Calculation Step 3e: a) - d) === CheckDigit
return calcSumExpB2 - stepCalc === checkDigit;
}
There it is!
This exercise was a lot of fun and showcased various ways of implementing an algorithm. The full source code for this exercise can be found here.
