Extended Straddling Checkerboard: Difference between revisions

→‎{{header|jq}}: more idiomatic version of decode
(Attempted to make this into a draft task now a way forward seems reasonably clear.)
(→‎{{header|jq}}: more idiomatic version of decode)
 
(8 intermediate revisions by 4 users not shown)
Line 3:
 
;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' and, if you do that, then digits only needed to be doubled rather than tripled. So we would then have F/Lthe (98)following and SUPP (99).checkerboard:
<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
00 11 22 33 44 55 66 77 88 99
</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.
Line 18 ⟶ 34:
;Reference:
:* Cipher Machines web page on Checkerboards: [https://www.ciphermachinesandcryptology.com/en/table.htm Checkerboards]
 
=={{header|jq}}==
'''Adapted from [[#Wren|Wren]]'''
 
'''Works with jq, the C implementation of jq'''
 
'''Works with gojq, the Go implementation of jq'''
 
'''Works with jaq, the Rust implementation of jq'''
<syntaxhighlight lang="jq">
### Generic utility
# Emit a stream of the constituent codepoints:
def chars: explode[] | [.] | implode;
 
### The checkerboard
def efigs: "0123456789";
 
def drow1: "012345";
 
def checkerboard:
def row1: "AEINOT";
def row2: "BCDFGHJKLM";
def row3: "PQRSUVWXYZ";
def row4: " .";
{ ewords: {
"SPC": "90", "DOT": "91",
"ACK": "92", "REQ": "93", "MSG": "94", "RV": "95",
"GRID": "96", "SEND": "97", "FSL": "98", "SUPP": "99"
},
emap: {},
dmap: {},
dwords:{}
}
| reduce range(0; row1|length) as $i (.; .emap[row1[$i:$i+1]] = ($i|tostring) )
| reduce range(0; row2|length) as $i (.; .emap[row2[$i:$i+1]] = ((70 + $i)|tostring))
| reduce range(0; row3|length) as $i (.; .emap[row3[$i:$i+1]] = ((80 + $i)|tostring))
| reduce range(0; row4|length) as $i (.; .emap[row4[$i:$i+1]] = ((90 + $i)|tostring))
 
| reduce (.emap|keys[]) as $k (.; .dmap[.emap[$k]] = $k)
| reduce (.ewords|keys[]) as $k (.; .dwords[.ewords[$k]] = $k) ;
 
def encode:
(ascii_upcase|split(" ")) as $words
| ($words|length) as $wc
| checkerboard
| .res = ""
| reduce range(0; $wc) as $i (.;
$words[$i] as $word
| .add = ""
| if .ewords[$word]
then .add = .ewords[$word]
elif .ewords[$word[0:-1]] and ($word|endswith("."))
then .add = .ewords[$word[0:-1]] + .ewords["DOT"]
elif $word|startswith("CODE")
then .add = "6" + $word[4:]
else .figs = false
| reduce ($word|chars) as $c (.;
if (efigs|contains($c))
then if .figs
then .add += 2 * $c
else .figs = true
| .add += .ewords["FSL"] + 2 * $c
end
else .emap[$c] as $ec
| if ($ec|not)
then "Message contains unrecognized character '\($c)'" | error
else if .figs
then .add += .ewords["FSL"] + $ec
| .figs = false
else .add += $ec
end
end
end )
| if .figs and ($i < $wc - 1)
then .add += .ewords["FSL"]
else .
end
end
| .res += .add
| if ($i < $wc - 1) then .res += .ewords["SPC"] else . end
)
| .res ;
 
def decode:
{s: .} + checkerboard
| .ewords["FSL"] as $fsl
| .res = ""
| .figs = false
| until (.s == "";
.s[0:1] as $c
| .ix = -1
| if .figs
then if (.s | startswith($fsl) | not)
then .res += $c
else .figs = false
end
| .s |= .[2:]
else .ix = (drow1|index($c))
| if .ix and .ix >= 0
then .res += .dmap[drow1[.ix:.ix+1]]
| .s |= .[1:]
elif $c == "6"
then .res += "CODE" + .s[1:4]
| .s |= .[4:]
elif $c == "7" or $c == "8"
then .s[1:2] as $d
| .res += .dmap[$c + $d]
| .s |= .[2:]
elif $c == "9"
then .s[1:2] as $d
| if $d == "0"
then .res += " "
elif $d == "1"
then .res += "."
elif $d == "8"
then .figs |= not
else .res += .dwords[$c + $d]
end
| .s |= .[2:]
end
end )
| .res ;
 
### Demonstration
def demo:
"Message:\n\(.)",
(encode
| "\nEncoded:\n\(.)",
"\nDecoded:\n\(decode)" );
 
"Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
| demo
</syntaxhighlight>
{{output}}
<pre>
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
 
Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
 
Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>
 
=={{header|Julia}}==
Line 60 ⟶ 220:
if contains(efigs, c)
if figs
add *= c^32
else
figs = true
add *= fsl * c^32
end
else
Line 96 ⟶ 256:
if s[i:i+1] != fsl
res *= c
i += 32
else
figs = false
Line 140 ⟶ 300:
 
Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175
0727923909290884848290798374919062919097907384825751829098222000000000989099905490758190708890981119890790827175
 
Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>
 
=={{header|Phix}}==
{{trans|Julia}}
Note this includes the two tiny tweaks to encode digits in two characters, as per "Moot point" on the talk page.
<syntaxhighlight lang="phix">
with javascript_semantics
constant emap = new_dict(),
dmap = new_dict(),
ewds = new_dict(),
dwds = new_dict(),
spc = "90", dot = "91", fsl = "98"
for d in {{"AEINOT",-1},{"BCDFGHJKLM",69},{"PQRSUVWXYZ",79},{" .",89}} do
integer k = d[2]
for i,ch in d[1] do
string ik = sprintf("%d",i+k)
setd(ch,ik,emap)
setd(ik,ch,dmap)
end for
end for
for d in {{"ACK","92"},{"REQ","93"},{"MSG","94"},{"RV","95"},
{"GRID","96"},{"SEND","97"},{"SUPP","99"}} do
string {k,v} = d
setd(k,v,ewds)
setd(v,k,dwds)
end for
 
function encode(string s)
string res = ""
sequence words = split(upper(s))
integer wc = length(words)
for i=1 to wc do
string wrd = words[i], a = ""
if getd_index(wrd,ewds) then
a = getd(wrd,ewds)
elsif getd_index(wrd[1..-2]) and wrd[$] == '.' then
a = getd(wrd[1..-2],ewds) & dot
elsif begins("CODE",wrd) then
a = "6" & wrd[5..$]
else
bool figs = false
for c in wrd do
if find(c,"0123456789") then -- (efigs)
if not figs then figs = true; a &= fsl end if
-- a &= repeat(c,3)
a &= repeat(c,2)
elsif not getd_index(c,emap) then
throw("Message contains unrecognized character %c.",{c})
else
if figs then figs = false; a &= fsl end if
a &= getd(c,emap)
end if
end for
if figs and i<=wc-1 then a &= fsl end if
end if
res &= a
if i <= wc-1 then res &= spc end if
end for
return res
end function
 
function decode(string s)
string res = ""
integer sc = length(s), figs = false, i = 1
while i <= sc do
integer c = s[i]
if figs then
if s[i..i+1] != fsl then
res &= c
-- i += 3
i += 2
else
figs = false
i += 2
end if
elsif find(c,"012345") then -- row 1
res &= getd(c&"",dmap)
i += 1
elsif c == '6' then
res &= "CODE" & s[i+1..i+3]
i += 4
elsif c == '7'
or c == '8' then
res &= getd(s[i..i+1],dmap)
i += 2
elsif c == '9' then
integer d = s[i+1]
if d == '0' then
res &= " "
elsif d == '1' then
res &= "."
elsif d == '8' then
figs = not figs
else
res &= getd(s[i..i+1],dwds)
end if
i += 2
end if
end while
return res
end function
 
constant msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March",
enc = encode(msg),
unc = decode(enc)
printf(1,"Message:\n%s\n\nEncoded:\n%s\n\nDecoded:\n%s\n",{msg,enc,unc})
</syntaxhighlight>
{{out}}
<pre>
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
 
Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175
 
Decoded:
Line 147 ⟶ 424:
 
=={{header|Python}}==
See the discussion for the different checkerboard table handling.
<syntaxhighlight lang="python">""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """
 
Line 161 ⟶ 439:
'SUPP': 'π',
}
SDICT = {v: k for (k, v) in WDICT.items()} # reversed WDICT for reverse lookup on decode
SDICT = {v: k for (k, v) in WDICT.items()}
 
# CT37w at https://www.ciphermachinesandcryptology.com/en/table.htm
# 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', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', '/π', 'π/',],]
 
# Modified CT37w: web site CT37w, but exchange '/' (FIG) char and 'π'
# to help differentiate the '999' encoding for a '9' from a terminator code
CT37w_mod = [['', '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):
def xcb_encode(message, table=CT37w, code='κ', wdict=WDICT):
"""
Encode with extended straddling checkerboard. Default checkerboard is
Line 180 ⟶ 466:
numericmode, codemode = False, False
codemodecount = 0
if table[-1][-1] == '/':
nchangemode = '99'
digit_repeats = 3
else:
nchangemode = '98'
digit_repeats = 2
 
# 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
Line 191 ⟶ 484:
codemode = False
 
else: # numeric numbers are triplicate encoded so '3' -> '333'
if not numericmode:
numericmode = True
encoded.append(nchangemode) # FIG
 
encoded.append(c*3digit_repeats)
 
else:
codemode = False
if numericmode:
encoded.append(nchangemode) # end numericmode with the FIG numeric code for '/' (98)
encoded.append(nchangemode)
numericmode = False
 
Line 217 ⟶ 511:
 
 
def xcb_decode(s, nchangemodetable='98'CT37w, 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, numericmode, codemode = 0, False, False
numericmode = False
codemode = False
decoded = []
if table[-1][-1] == '/':
nchangemode = '99'
digit_repeats = 3
else:
nchangemode = '98'
digit_repeats = 2
numbers = {c*digit_repeats: c for c in list('0123456789')}
while pos < len(s):
if numericmode:
if s[pos:pos+3digit_repeats] in numbers:
decoded.append(numbers[s[pos:pos+3digit_repeats]])
pos += 2digit_repeats - 1
elif s[pos:pos+2] == nchangemode:
numericmode = False
pos += 1
elif decoded[-1] == '9': # error, so backtrack if last was 9
decoded.pop()
numericmode = False
pos -= digit_repeats - 1
 
elif codemode:
Line 244 ⟶ 546:
numericmode = not numericmode
pos += 1
 
else:
for p in prefixes:
Line 253 ⟶ 556:
if c == code:
codemode = True
 
pos += n
break
Line 267 ⟶ 571:
print('Encoded: ', xcb_encode(MESSAGE))
print('Decoded: ', xcb_decode(xcb_encode(MESSAGE)))
print('Encoded: ', xcb_encode(MESSAGE, CT37w_mod))
print('Decoded: ', xcb_decode(xcb_encode(MESSAGE, CT37w_mod), CT37w_mod))
 
</syntaxhighlight>{{out}}
<pre>
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
Encoded: 072792390929088484829094919062919097907384825751829099222000000000999098905490758190708890991119990790827175
Encoded: 072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175
Decoded: ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
Encoded: 0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
Decoded: ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>
Line 277 ⟶ 585:
=={{header|Wren}}==
{{libheader|Wren-str}}
For consistency with the Python example, this uses the CT-37w checkerboard with F/L changed to 98 and SUPP to 99.
<syntaxhighlight lang="wren">import "./str" for Str
 
Line 324 ⟶ 631:
if (efigs.contains(c)) {
if (figs) {
add = add + c * 32
} else {
figs = true
add = add + fsl + c * 32
}
} else {
Line 361 ⟶ 668:
if (s[i..i+1] != fsl) {
res = res + c
i = i + 3
} else {
figs = false
i = i + 2
}
i = i + 2
} else if ((ix = drow1.indexOf(c)) >= 0) {
res = res + dmap[drow1[ix]]
Line 406 ⟶ 712:
 
Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175
 
Decoded:
2,460

edits