Extended Straddling Checkerboard: Difference between revisions

→‎{{header|jq}}: more idiomatic version of decode
(→‎{{header|Wren}}: Made a bit more general.)
(→‎{{header|jq}}: more idiomatic version of decode)
 
(11 intermediate revisions by 4 users not shown)
Line 1:
{{draft task}}
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
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).
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 the following checkerboard:
Related tasks:
<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.
 
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:
:* [[Straddling_checkerboard|Straddling checkerboard ]]
 
;Reference:
References:
:* 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}}==
{{trans|Wren}}
<syntaxhighlight lang="julia">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^2
else
figs = true
add *= fsl * c^2
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 += 2
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")
</syntaxhighlight>{{out}}
<pre>
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
 
Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175
 
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:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>
 
=={{header|Python}}==
See the discussion for the different checkerboard table handling.
<syntaxhighlight lang="python">""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """
 
Line 25 ⟶ 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 44 ⟶ 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 55 ⟶ 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 81 ⟶ 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 108 ⟶ 546:
numericmode = not numericmode
pos += 1
 
else:
for p in prefixes:
Line 117 ⟶ 556:
if c == code:
codemode = True
 
pos += n
break
Line 131 ⟶ 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 141 ⟶ 585:
=={{header|Wren}}==
{{libheader|Wren-str}}
For consistency with the Julia 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 188 ⟶ 631:
if (efigs.contains(c)) {
if (figs) {
add = add + c * 32
} else {
figs = true
add = add + fsl + c * 32
}
} else {
Line 225 ⟶ 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 270 ⟶ 712:
 
Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175
 
Decoded:
2,442

edits