如何分析或改进我的侄女基于摩尔斯电码的简单压缩算法?

时间:2013-03-28 03:03:22

标签: python algorithm python-3.x morse-code

我8岁的侄女昨天在学校接受了摩尔斯电码的教训,她的任务是将各种短语转换为莫尔斯电码。其中一个短语包括她的年龄,而不是写 ---.. ,她写了 3-2. ,因为(用她的话来说),“写作较少那样。”这个基本的“压缩算法”引起了我的好奇心,所以我写了一些代码来实现它。

然而,我们在此过程中做了一些改变。我向她指出,如果你只是写了 .....----- ,那么就无法判断作者是50还是eeeeettttt。实际上,每个单词的每个字母和每个单词之间都有一个暂停,所以这不是问题,但我们的方案没有那个。我拿出了一些方格纸并建议用另一个符号填充每个符号的莫尔斯码,以便于编码并消除方案中的歧义。我建议使用+,因为“没有人用句子写那些。” (哎呀,我最近刚毕业时获得了数学学位,但还算公平。)

由于我们中的一些+写,并且我们都使用连字符和句点/点,这会与我们的莫尔斯码的标准定义冲突,这些符号被替换为{分别为{1}},ph。当然,这给我们带来了如何处理未在我们扩展的摩尔斯电码中定义的符号的问题。我的侄女想要简单地忽略它们,这就是我们所做的。为了区分大小写保留文本消息,大写字母在代码中不是低位的;它们只是按原样传递,并用d填充。

算法摘要:

  1. 使用+
  2. 将莫尔斯代码右键填充为5个字符
  3. 我们将莫尔斯代码扩展为+替代p+取代d.取代h
  4. 我们的“扩展”摩尔斯电码中未定义的符号将完整传递。
  5. 除非仅出现一个连续字符,否则将替换符号运行,在这种情况下,数字将被省略。
  6. 潜在的陷阱:

    1. 我的填充方案可能会降低压缩效率。
    2. 使用大于5个字符的块
    3. 可能会改善压缩效果
    4. 如果我的侄女或我对压缩算法一无所知,我们可能会使用它来使它们成功。
    5. 这显然不适合制作,但由于有许多有效的压缩算法用于此类目的,我暂时忽略了这个问题。
    6. ???
    7. 实施例

      在我们的算法中,“Hello,World”转换为

      -

      并压缩到

      H++++.++++.-..+.-..+---++,+++++++++W++++---++.-.++.-..+-..++
      

      以下是我汇总的Python代码:

      H4+.4+.-2.+.-2.+3-2+,9+W4+3-2+.-.2+.-2.+-2.2+
      

      有哪些简单方法可以a)改进我们的算法,以及b)相对容易向我8岁的侄女解释?虽然最后一点显然是主观的,但我仍然试图尽可能地放纵她的好奇心。

      我欢迎对代码进行任何改进,因为它的结构不是非常好(我相当确定它的结构非常差,实际上,但它很快而且很脏),尽管这对我有利,因为我没有得到我的侄女使用Python(YET)。

      更新

      这是代码的更新版本,它试图将user1884905对算法的修改和Karl对代码本身的改进结合起来。

      #!/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()
      

2 个答案:

答案 0 :(得分:4)

可以对输出进行简单的更改(至少在大多数情况下)是保持两个字母之间的暂停的想法,让你的+ - 符号表示暂停(即加号)用作新字符字符而不是用它们作为填充。)

这会使所有数字0-9更长一个字符,但会使相当多的常用字母更短。

例如aeit将变为.-+.+..+和{{ 1}}而不是-+.-+++.++++..+++。 (并且-++++可以表示为space,而不是+

所以你的+++++示例会变成:

Hello, World

而不是

H+.+.-..+.-..+---+,++W+---+.-.+.-..+-..+

并压缩到

H++++.++++.-..+.-..+---++,+++++++++W++++---++.-.++.-..+-..++

而不是

H+.+.-2.+.-2.+3-+,2+W+3-+.-.+.-2.+-2.+

理解这种推理,霍夫曼编码似乎只是自然的下一步,其基本思想是让最常见的角色占用尽可能少的空间。

修改

另请参阅此维基百科图片dichotomatic search表格,其中显示了靠近树顶部最常出现的字符。

Morse code tree

答案 1 :(得分:2)

我不会为此而上课;因此,您为每个字符串重新创建映射。使用类仍然可以避免这种情况,但实际上只需设置这些映射然后编写一个普通函数来编码字符串就更简单了。 “莫尔斯代码字符串”与普通字符串并没有根本的区别,并且将压缩和解压缩功能附加到它上面并没有任何意义。只写一堆函数;当你有一个非常有意义的抽象时,担心OOP。

正如所写,你的减压功能毫无意义;压缩不是解压缩的一部分,并且您已将莫尔斯码的解压缩与解码组合回同一函数中的普通字符串。这是一团糟。


self._morse_char_map = dict()
for letter, code in self._char_morse_map.items():
    self._morse_char_map[code] = letter

这用dict理解写得更整齐:

self._morse_char_map = {
    code: letter
    for letter, code in self._char_morse_map.items()
}

"".join([...])

这里不需要方括号;只需将生成器表达式提供给join,并利用特殊语法规则。


chars = list()

这写得更整齐chars = [],但让我们试着在更高的层次上改进这个......


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

一种技术:不是设置一个空列表而是反复将事物累加到''.join,而是编写一个生成器并让结果通过。当逻辑正确地分成它自己的函数时,这变得更容易:

def decompress(compressed):
    return ''.join(decompress_gen(compressed))


def decompress_gen(compressed):
    start = 0
    i = 0
    while i < len(compressed):
        curr = compressed[i]
        i += 1
        if not(curr in string.digits):
            num = 1 if start + 1 == i else int(compressed[start:i-1])
            yield "".join(curr * num)
            start = i

现在,显然我们真的想用compressed循环迭代for个符号;手动递增索引看起来非常糟糕。为了使这项工作,我们需要一次查看一个角色的数据,并记住我们已经看过的数字的任何部分。我们可以继续算术,但是让我们保留int的使用,而不是建立一个属于计数的字符缓冲区:

def decompress_gen(compressed):
    number_digits = ''
    for char in compressed:
        if char in string.digits:
            number_digits += char
        else:
            number = int(number_digits) if number_digits else 1
            yield "".join(char * number)
            number_digits = ''

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)

此时,code是一个字符串,因此在创建''.join时不需要piece。但同样,我们可以在这里使用生成器(好吧,生成器表达式):

return ''.join(
    self._morse_char_map.get(piece, piece[0])
    for piece in (
        code[i: i + 5]
        for i in range(0, len(code), 5)
    )        
)