Extended Straddling Checkerboard: Difference between revisions

From Rosetta Code
Content added Content deleted
(Attempted to make this into a draft task now a way forward seems reasonably clear.)
(added transcription of ct37w)
Line 3: Line 3:


;Task
;Task
Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, described in the reference below.
Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, as described in the reference below.


You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999'. So we would then have F/L (98) and SUPP (99).
You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999', so we would then have F/L (98) and SUPP (99) as follows:
<pre>
A E I N O T CODE
0 1 2 3 4 5 6

B C D F G H J K L M
70 71 72 73 74 75 76 77 78 79

P Q R S U V W X Y Z
80 81 82 83 84 85 86 87 88 89

SPC (.) ACK REQ MSG RV GRD SND F/L SUP
90 91 92 93 94 95 96 97 98 99

0 1 2 3 4 5 6 7 8 9
000 111 222 333 444 555 666 777 888 999
</pre>


There is no need to create a word dictionary for CODE (6). It suffices to just include CODE followed by some 3 digit number in the message to be encoded.
There is no need to create a word dictionary for CODE (6). It suffices to just include CODE followed by some 3 digit number in the message to be encoded.

Revision as of 16:08, 25 March 2024

Extended Straddling Checkerboard is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

An extended Straddling Checkerboard, is like the regular Straddling checkerboard, but allows word dictionaries and arbitrary functional codes such as FIGURE, where you can specify a number literally.

Task

Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, as described in the reference below.

You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999', so we would then have F/L (98) and SUPP (99) as follows:

  A   E   I   N   O   T  CODE
  0   1   2   3   4   5   6

  B   C   D   F   G   H   J   K   L   M
 70  71  72  73  74  75  76  77  78  79

  P   Q   R   S   U   V   W   X   Y   Z
 80  81  82  83  84  85  86  87  88  89

SPC (.) ACK REQ MSG  RV GRD SND F/L SUP
 90  91  92  93  94  95  96  97  98  99

  0   1   2   3   4   5   6   7   8   9
 000 111 222 333 444 555 666 777 888 999

There is no need to create a word dictionary for CODE (6). It suffices to just include CODE followed by some 3 digit number in the message to be encoded.

Test your solution by encoding and decoding the message:

'Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March'

Related task
Reference

Julia

Translation of: Wren
const row1, row2, row3, row4 = "AEINOT", "BCDFGHJKLM", "PQRSUVWXYZ", " ."
const emap = Dict{String,String}()
for (row, k) in [(row1, -1), (row2, 69), (row3, 79), (row4, 89)]
    for i in eachindex(row)
        emap[string(row[i])] = string(i + k)
    end
end
const dmap = Dict{String,String}(v => k for (k, v) in emap)

const ewords = Dict{String,String}(
    "ACK" => "92",
    "REQ" => "93",
    "MSG" => "94",
    "RV" => "95",
    "GRID" => "96",
    "SEND" => "97",
    "SUPP" => "99",
)
const dwords = Dict{String,String}(v => k for (k, v) in ewords)

const efigs, spc, dot, fsl, drow1 = "0123456789", "90", "91", "98", "012345"

function encode(s)
    s, res = uppercase(s), ""
    words = split(s, r"\s")
    wc = length(words)
    for i = 1:wc
        word, add = words[i], ""
        if haskey(ewords, word)
            add = ewords[word]
        elseif haskey(ewords, word[begin:end-1]) && word[end] == "."
            add = ewords[word[begin:end-1]] * dot
        elseif startswith(word, "CODE")
            add = "6" * word[begin+4:end]
        else
            figs = false
            for c in word
                if contains(efigs, c)
                    if figs
                        add *= c^3
                    else
                        figs = true
                        add *= fsl * c^3
                    end
                else
                    ec = get(emap, string(c), "")
                    isempty(ec) && error("Message contains unrecognized character $c.")
                    if figs
                        add *= fsl * ec
                        figs = false
                    else
                        add *= ec
                    end
                end
            end
            if figs && i <= wc - 1
                add *= fsl
            end
        end
        res *= add
        if i <= wc - 1
            res *= spc
        end
    end
    return res
end

function decode(s)
    res, sc, figs, i = "", length(s), false, 1
    while i <= sc
        ch = s[i]
        c = string(ch)
        if figs
            if s[i:i+1] != fsl
                res *= c
                i += 3
            else
                figs = false
                i += 2
            end
        elseif !((ix = findfirst(==(ch), drow1)) isa Nothing)
            res *= dmap[string(drow1[ix])]
            i += 1
        elseif c == "6"
            res *= "CODE" * s[i+1:i+3]
            i += 4
        elseif c == "7" || c == "8"
            d = string(s[i+1])
            res *= dmap[c*d]
            i += 2
        elseif c == "9"
            d = string(s[i+1])
            if d == "0"
                res *= " "
            elseif d == "1"
                res *= "."
            elseif d == "8"
                figs = !figs
            else
                res *= dwords[c*d]
            end
            i += 2
        end
    end
    return res
end

const msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
println("Message:\n$msg")
enc = encode(msg)
println("\nEncoded:\n$enc")
dec = decode(enc)
println("\nDecoded:\n$dec")
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
0727923909290884848290798374919062919097907384825751829098222000000000989099905490758190708890981119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Python

""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """

from functools import reduce

WDICT = {
    'CODE': 'κ',
    'ACK': 'α',
    'REQ': 'ρ',
    'MSG': 'μ',
    'RV': 'ν',
    'GRID': 'γ',
    'SEND': 'σ',
    'SUPP': 'π',
}
SDICT = {v: k for (k, v) in WDICT.items()} # reversed WDICT for reverse lookup on decode

# web site CT37w, but '/' (FIG) char is 98 not 99 to help differentiate from code for 9 of '999'
CT37w = [['',  'A', 'E', 'I', 'N', 'O', 'T', 'κ', '',  '',  '',],
         ['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
         ['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
         ['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', '/', 'π',],]


def xcb_encode(message, nchangemode='98', code='κ', table=CT37w, wdict=WDICT):
    """
        Encode with extended straddling checkerboard. Default checkerboard is
        CT37w at https://www.ciphermachinesandcryptology.com/en/table.htm
        The numeric mode has the numbers as repeated in triplicate
        The CODE mode expects a 3-digit numeric code
    """
    encoded = []
    numericmode, codemode = False, False
    codemodecount = 0
    # replace terms found in dictionary with a single char symbol that is in the table
    s = reduce(lambda x, p: x.replace(
        p[0], p[1]), wdict.items(), message.upper())
    for c in s:
        if c.isnumeric(): 
            if codemode: # codemode symbols are preceded by the CODE digit '6' then as-is
                encoded.append(c)
                codemodecount += 1
                if codemodecount >= 3:
                    codemode = False

            else: # numeric numbers are triplicate encoded so '3' -> '333'
                if not numericmode:
                    numericmode = True
                    encoded.append(nchangemode) # FIG

                encoded.append(c*3)

        else:
            codemode = False
            if numericmode:
                encoded.append(nchangemode) # end numericmode with the FIG numeric code for '/' (98)
                numericmode = False

            if c == code:
                codemode = True
                codemodecount = 0

            for row in table:
                if c in row:
                    k = row.index(c)
                    encoded.append(str(row[0]) + str(k-1))
                    break

    return ''.join(encoded)


def xcb_decode(s, nchangemode='98', code='κ', table=CT37w, sdict=SDICT):
    """ Decode extended straddling checkerboard """
    numbers = {c*3: c for c in list('0123456789')}
    prefixes = sorted([row[0] for row in table], reverse=True)
    pos = 0
    numericmode = False
    codemode = False
    decoded = []
    while pos < len(s):
        if numericmode:
            if s[pos:pos+3] in numbers:
                decoded.append(numbers[s[pos:pos+3]])
                pos += 2
            elif s[pos:pos+2] == nchangemode:
                numericmode = False
                pos += 1

        elif codemode:
            if (s[pos:pos+3]).isnumeric():
                decoded.append(s[pos:pos+3])
                pos += 2

            codemode = False

        elif s[pos:pos+2] == nchangemode:
            numericmode = not numericmode
            pos += 1
        else:
            for p in prefixes:
                if s[pos:].startswith(p):
                    n = len(p)
                    row = next(i for i, r in enumerate(table) if p == r[0])
                    c = table[row][int(s[pos+n])+1]
                    decoded.append(c)
                    if c == code:
                        codemode = True
                    pos += n
                    break

        pos += 1

    return reduce(lambda x, p: x.replace(p[0], p[1]), sdict.items(), ''.join(decoded))


if __name__ == '__main__':

    MESSAGE = 'Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March'
    print(MESSAGE)
    print('Encoded: ', xcb_encode(MESSAGE))
    print('Decoded: ', xcb_decode(xcb_encode(MESSAGE)))
Output:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
Encoded:  072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175
Decoded:  ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Wren

Library: Wren-str

For consistency with the Python example, this uses the CT-37w checkerboard with F/L changed to 98 and SUPP to 99.

import "./str" for Str

var row1 = "AEINOT"
var row2 = "BCDFGHJKLM"
var row3 = "PQRSUVWXYZ"
var row4 = " ."

var emap = {}
for (i in 0...row1.count) emap[row1[i]] = i.toString
for (i in 0...row2.count) emap[row2[i]] = (70 + i).toString
for (i in 0...row3.count) emap[row3[i]] = (80 + i).toString
for (i in 0...row4.count) emap[row4[i]] = (90 + i).toString
var ewords = {
    "ACK": "92", "REQ": "93", "MSG": "94", "RV": "95",
    "GRID": "96", "SEND": "97", "SUPP": "99"
}
var efigs = "0123456789"
var spc = "90"
var dot = "91"
var fsl = "98"

var dmap = {}
var dwords = {}
for (k in emap.keys) dmap[emap[k]] = k
for (k in ewords.keys) dwords[ewords[k]] = k
var drow1 = "012345"

var encode = Fn.new { |s|
    s = Str.upper(s)
    var res = ""
    var words = s.split(" ")
    var wc = words.count
    for (i in 0...wc) {
        var word = words[i]
        var add = ""
        if (ewords.containsKey(word)) {
            add = ewords[word]
        } else if (ewords.containsKey(word[0...-1]) && word[-1] == ".") {
            add = ewords[word[0...-1]] + dot
        } else if (word.startsWith("CODE")) {
            add = "6" + word[4..-1]
        } else {
            var figs = false
            for (c in word) {
                if (efigs.contains(c)) {
                    if (figs) {
                        add = add + c * 3
                    } else {
                        figs = true
                        add = add + fsl + c * 3
                    }
                } else {
                    var ec = emap[c]
                    if (!ec) {
                        Fiber.abort("Message contains unrecognized character '%(c)'.")
                    }
                    if (figs) {
                        add = add + fsl + ec
                        figs = false
                    } else {
                        add = add + ec
                    }
                }
            }
            if (figs && i < wc - 1) add = add + fsl
        }
        res = res + add
        if (i < wc - 1) res = res + spc
    }
    return res
}

var decode = Fn.new { |s|
    var res = ""
    var sc = s.count
    var figs = false
    var i = 0
    while (i < sc) {
        var c = s[i]
        var ix = -1
        if (figs) {
            if (s[i..i+1] != fsl) {
                res = res + c
                i = i + 3
            } else {
                figs = false
                i = i + 2
            }
        } else if ((ix = drow1.indexOf(c)) >= 0) {
            res = res + dmap[drow1[ix]]
            i = i + 1
        } else if (c == "6") {
            res = res + "CODE" + s[i+1..i+3]
            i = i + 4
        } else if (c == "7" || c == "8") {
            var d = s[i+1]
            res = res + dmap[c + d]
            i = i + 2
        } else if (c == "9") {
            var d = s[i+1]
            if (d == "0") {
                res = res + " "
            } else if (d == "1") {
                res = res + "."
            } else if (d == "8") {
                figs = !figs
            } else {
                res = res + dwords[c + d]
            }
            i = i + 2
        }
    }
    return res
}
               
var msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
System.print("Message:\n%(msg)")
var enc = encode.call(msg)
System.print("\nEncoded:\n%(enc)")
var dec = decode.call(enc)
System.print("\nDecoded:\n%(dec)")
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH