Extended Straddling Checkerboard

From Rosetta Code
Revision as of 19:37, 22 March 2024 by Wherrera (talk | contribs) (typo)

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

Related tasks:

References:


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 '/' 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)

                encoded.append(c*3)

        else:
            codemode = False
            if numericmode:
                encoded.append(nchangemode) # end numericmode with the 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