我 8 岁的侄女昨天在学校上了一堂摩尔斯电码课,她的任务是将各种短语转换为摩尔斯电码。其中一个短语包括她的年龄,而不是写作---..
,她写作3-2.
是因为(用她的话),“这样写的少。” 这个初级的“压缩算法”激发了我的好奇心,所以我写了一点代码来实现它。
但是,我们在此过程中进行了一些更改。我向她指出,如果你写的只是.....-----
,没有任何方法可以判断作者的意思是50
或eeeeettttt
。实际上,每个单词的每个字母和每个单词之间都有一个停顿,所以这不是问题,但我们的方案没有这个问题。我拿出一些方格纸,建议用另一个符号填充每个符号的莫尔斯电码,以方便编码并消除方案中的歧义。我很好的建议使用+
,因为“没有人用句子写过这些。” (哎呀,我最近毕业于数学学位,但很公平。)
由于我们中的一些人确实使用+
,并且我们都使用连字符和句点/点,这会与我们对摩尔斯电码的标准定义相冲突,因此这些符号分别替换为p
、h
和d
。当然,这给我们带来了如何处理未在我们的扩展摩尔斯电码中定义的符号的问题。我的侄女想简单地忽略它们,所以我们就这么做了。为了文本信息的大小写敏感,代码中的大写字母不小写;它们只是按原样进行并用+
.
算法总结:
- 莫尔斯电码右填充为 5 个字符
+
- 我们扩展了摩尔斯电码来代替、
p
for和for 。+
d
.
h
-
- 未在我们的“扩展”摩尔斯电码中定义的符号将完整地传递。
- 除非仅出现一个连续字符,否则将替换连续的符号,在这种情况下省略该数字。
潜在的陷阱:
- 我的填充方案可能会降低压缩的有效性。
- 使用大于 5 个字符的块可以提高压缩率
- 如果我的侄女或我对压缩算法有所了解,我们可能会使用它来使它们成功。
- 这显然不适合生产,但由于有许多用于此类目的的有效压缩算法,我暂时忽略了这个问题。
- ???
例子:
在我们的算法中,“Hello, World”转换为
H++++.++++.-..+.-..+---++,+++++++++W++++---++.-.++.-..+-..++
并压缩到
H4+.4+.-2.+.-2.+3-2+,9+W4+3-2+.-.2+.-2.+-2.2+
这是我放在一起的 Python 代码:
#!/usr/bin/python3
import itertools
import string
class MorseString(str):
def __init__(self, string):
# or, pad values during iteration but this seems neater
self._char_morse_map = {"a":".-+++", "b":"-...+", "c":"-.-.+", "d":"-..++", \
"e":".++++", "f":"..-.+", "g":"--.++", "h":"....+", \
"i":"..+++", "j":".---+", "k":"-.-++", "l":".-..+", \
"m":"--+++", "n":"-.+++", "o":"---++", "p":".--.+", \
"q":"--.-+", "r":".-.++", "s":"...++", "t":"-++++", \
"u":"..-++", "v":"...-+", "w":".--++", "x":"-..-+", \
"y":"-.--+", "z":"--..+", "1":".----", "2":"..---", \
"3":"...--", "4":"....-", "5":".....", "6":"-....", \
"7":"--...", "8":"---..", "9":"----.", "0":"-----",
" ":"+++++", ".":"d++++", "+":"p++++", "-":"h++++"}
self._morse_char_map = dict()
for letter, code in self._char_morse_map.items():
self._morse_char_map[code] = letter
self._string = string
# convert the string to "Morse code". Could also use c.lower()
self._morse_string = "".join([self._char_morse_map.get(c, c.ljust(5, "+")) for c in self._string])
def compress(self):
def grouper(n, k):
return str(n) + k if n > 1 else k
# could just use lambda
return "".join([grouper(len(list(g)), k) for k, g in itertools.groupby(self._morse_string)])
def decompress(self):
i = 0
start = 0
chars = list()
sc = self.compress()
while i < len(sc):
curr = sc[i]
i += 1
if not(curr in string.digits):
num = 1 if start + 1 == i else int(sc[start:i-1])
chars.append("".join(curr * num))
start = i
code = "".join(chars)
chars = list()
for i in range(0, len(code), 5):
piece = "".join(code[i:i+5])
chars.append(self._morse_char_map.get(piece, piece[0]))
return "".join(chars)
def main():
s0 = "Hello, World"
ms0 = MorseString(s0)
print(ms0._morse_string)
print(ms0.compress())
assert(s0 == ms0.decompress())
s1 = "Hello 2 world."
ms1 = MorseString(s1)
assert(s1 == ms1.decompress())
s2 = "The quick brown fox jumped over the lazy dog."
ms2 = MorseString(s2)
assert(s2 == ms2.decompress())
s3 = "abc -.+"
ms3 = MorseString(s3)
ms3
assert(s3 == ms3.decompress())
if __name__ == "__main__":
main()
有哪些简单的方法可以 a) 改进我们的算法,并且 b) 相对容易向我 8 岁的侄女解释?虽然最后一点显然是主观的,但我还是尽量满足她的好奇心。
我也欢迎对代码进行任何改进,因为它的结构不太好(我相当肯定它的结构很差,实际上,但它又快又脏),尽管这完全是为了我的利益,因为我还没有让我的侄女使用 Python (YET)。
更新
这是代码的更新版本,它试图结合 user1884905 对算法的修改和 Karl 对代码本身的改进。
import itertools
import string
_char_morse_map = {"a":".-", "b":"-...", "c":"-.-.", "d":"-..", \
"e":".", "f":"..-.", "g":"--.", "h":"....", \
"i":"..", "j":".---", "k":"-.-", "l":".-..", \
"m":"--", "n":"-.", "o":"---", "p":".--.", \
"q":"--.-", "r":".-.", "s":"...", "t":"-", \
"u":"..-", "v":"...-", "w":".--", "x":"-..-", \
"y":"-.--", "z":"--..", "1":".----", "2":"..---", \
"3":"...--", "4":"....-", "5":".....", "6":"-....", \
"7":"--...", "8":"---..", "9":"----.", "0":"-----",
" ":"", ".":"d", "+":"p", "-":"h"}
_morse_char_map = {
code: letter
for letter, code in _char_morse_map.items()
}
def encode(s):
return "".join(_char_morse_map.get(c, c) + "+" for c in s)
def decode(encoded):
return "".join(decode_gen(encoded))
def decode_gen(encoded):
word = ""
for c in encoded:
if c != "+":
word += c
else:
yield _morse_char_map.get(word, word) if word != "" else " "
word = ""
def compress(s):
def grouper(n, k):
return str(n) + k if n > 1 else k
return "".join(grouper(len(list(g)), k) for k, g in itertools.groupby(s))
def decompress(compressed):
return "".join(decompress_gen(compressed))
def decompress_gen(compressed):
digits = ""
for c in compressed:
if c in string.digits:
digits += c
else:
number = int(digits) if digits else 1
yield "".join(c * number)
digits = ""