【问题标题】:Translating Morse code with no spaces翻译没有空格的摩尔斯电码
【发布时间】:2012-01-11 01:23:21
【问题描述】:

我有一些摩尔斯电码丢失了字母之间的空格,我的挑战是找出信息的内容。到目前为止,由于可能存在大量组合,我有点迷失了。

这是我收到的消息的所有信息。

  • 输出为英文
  • 总会有有意义的翻译
  • 这里是示例消息-..-...-...-...-..-.-.-.-.-..-.-.-.-.-.-.-.-.-.-..-...-.
  • 消息长度不应超过 70 个字符
  • 莫尔斯电码取自较长的流,因此第一组或最后一组可能被截断,因此没有有效的翻译

有人有聪明的解决办法吗?

【问题讨论】:

  • 没有聪明的解决方案。计算出组合并对照字典检查它们。如果整个消息的正确解码不止一次,如果没有进一步的信息,您就无法知道哪个是正确的。
  • 我几乎可以肯定,您可以使用包含所有有效摩尔斯字母的大量正则表达式找到 a 有效解释,这样当它遇到无效序列时它会回溯,直到它构建一个有效的;但是,我想不出一种优雅的方式来获取所有可能的翻译,而且我相当肯定会有许多模棱两可的潜在翻译是有效的,但毫无意义。
  • 我在想也许可以通过一些代码运行它们来检查字母频率以帮助确定女巫可能是正确的。
  • 我坚持的部分是“包含所有有效摩尔斯字母的大量正则表达式”。我不知道如何做到这一点。
  • 消息多长时间?部分问题在于,对于任何序列,严格有效但完全无益的解码是用 E 替换单个点,用 T 替换单个破折号,但你最终会得到 ETETTTTETETEEETE……你还有什么其他信息,以及有空格吗?

标签: puzzle morse-code


【解决方案1】:

这不是一个简单的问题,因为正如 ruakh 所建议的,给定消息有许多可行的句子。例如,“JACK AND JILL WENT UP THE HILL”与“JACK AND JILL WALK CHISELED”具有相同的编码。由于这些都是语法句子并且每个单词中的单词都很常见,因此在不深入研究自然语言的情况下如何选择一个或另一个(或任何其他 40141055989476564163599 个与此消息具有相同编码的不同英语单词序列中的任何其他序列)并不明显处理。

无论如何,这里有一个动态编程解决方案来解决寻找最短句子的问题(如果出现平局,则使用最少的字符)。它还可以计算与给定消息具有相同编码的句子总数。它需要一个文件中的英语单词词典。

下一个增强功能应该是更好地衡量一个句子的可能性:也许是词频、莫尔斯语的误报率(例如,“I”是一个常见词,但它经常作为其他莫尔斯语序列的一部分出现代码序列)。棘手的部分是制定一个好的分数函数,该函数可以用动态规划计算的方式表示。

MORSE = dict(zip('ABCDEFGHIJKLMNOPQRSTUVWXYZ', [
    '.-', '-...', '-.-.', '-..', '.', '..-.', '--.', '....',
    '..', '.---', '-.-', '.-..', '--', '-.', '---', '.--.',
    '--.-', '.-.', '...', '-', '..-', '...-', '.--', '-..-',
    '-.--', '--..'
]))

# Read a file containing A-Z only English words, one per line.
WORDS = set(word.strip().upper() for word in open('dict.en').readlines())
# A set of all possible prefixes of English words.
PREFIXES = set(word[:j+1] for word in WORDS for j in xrange(len(word)))

def translate(msg, c_sep=' ', w_sep=' / '):
    """Turn a message (all-caps space-separated words) into morse code."""
    return w_sep.join(c_sep.join(MORSE[c] for c in word)
                      for word in msg.split(' '))

def encode(msg):
    """Turn a message into timing-less morse code."""
    return translate(msg, '', '')

def c_trans(morse):
    """Construct a map of char transitions.

    The return value is a dict, mapping indexes into the morse code stream
    to a dict of possible characters at that location to where they would go
    in the stream. Transitions that lead to dead-ends are omitted.
    """
    result = [{} for i in xrange(len(morse))]
    for i_ in xrange(len(morse)):
        i = len(morse) - i_ - 1
        for c, m in MORSE.iteritems():
            if i + len(m) < len(morse) and not result[i + len(m)]:
                continue
            if morse[i:i+len(m)] != m: continue
            result[i][c] = i + len(m)
    return result

def find_words(ctr, i, prefix=''):
    """Find all legal words starting from position i.

    We generate all possible words starting from position i in the
    morse code stream, assuming we already have the given prefix.
    ctr is a char transition dict, as produced by c_trans.
    """
    if prefix in WORDS:
        yield prefix, i
    if i == len(ctr): return
    for c, j in ctr[i].iteritems():
        if prefix + c in PREFIXES:
            for w, j2 in find_words(ctr, j, prefix + c):
                yield w, j2

def w_trans(ctr):
    """Like c_trans, but produce a word transition map."""
    result = [{} for i in xrange(len(ctr))]
    for i_ in xrange(len(ctr)):
        i = len(ctr) - i_ - 1
        for w, j in find_words(ctr, i):
            if j < len(result) and not result[j]:
                continue
            result[i][w] = j
    return result

def shortest_sentence(wt):
    """Given a word transition map, find the shortest possible sentence.

    We find the sentence that uses the entire morse code stream, and has
    the fewest number of words. If there are multiple sentences that
    satisfy this, we return the one that uses the smallest number of
    characters.
    """
    result = [-1 for _ in xrange(len(wt))] + [0]
    words = [None] * len(wt)
    for i_ in xrange(len(wt)):
        i = len(wt) - i_ - 1
        for w, j in wt[i].iteritems():
            if result[j] == -1: continue
            if result[i] == -1 or result[j] + 1 + len(w) / 30.0 < result[i]:
                result[i] = result[j] + 1 + len(w) / 30.0
                words[i] = w
    i = 0
    result = []
    while i < len(wt):
        result.append(words[i])
        i = wt[i][words[i]]
    return result

def sentence_count(wt):
    result = [0] * len(wt) + [1]
    for i_ in xrange(len(wt)):
        i = len(wt) - i_ - 1
        for j in wt[i].itervalues():
            result[i] += result[j]
    return result[0]

msg = 'JACK AND JILL WENT UP THE HILL'
print sentence_count(w_trans(c_trans(encode(msg))))
print shortest_sentence(w_trans(c_trans(encode(msg))))

【讨论】:

  • 对于类 UNIX 系统的用户,/usr/share/dict/words 和字典一样好用(即它可以用来代替 'dict.en')。
【解决方案2】:

我不知道这是否“聪明”,但我会尝试广度优先搜索(与 BRPocock 的正则表达式理念中隐含的深度优先搜索相反)。假设您的字符串如下所示:

.---.--.-.-.-.--.-...---...-...-..
J   A C   K  A N D  J   I L   L

您从状态('', 0) 开始('' 是您目前解码的内容;0 是您在摩尔斯电码字符串中的位置)。从位置零开始,可能的初始字符为. E.- A.-- W.--- J.---- 1。因此,将状态 ('E', 1)('A', 2)('W', 3)('J', 4)('1', 5) 推送到您的队列中。在将状态 ('E', 1) 出列后,您会将状态 ('ET', 2)('EM', 3)('EO', 4) 加入队列。

现在,您的可能状态队列将快速增长 — {.-} 和 {...--.--} 都是字母和所有的{.....-.-..---..-.---.---},所以在每次通过时,你的状态数量都会增加一个因子至少三个——所以你需要有一些用户反馈机制。特别是,您需要某种方式询问您的用户“这个字符串以EOS3AIOSF 开头是否合理?”,如果用户说“否”,您将需要从队列中丢弃状态("EOS3AIOSF", 26)。理想的做法是向用户展示一个 GUI,该 GUI 每隔一段时间会显示所有当前状态,并让他/她选择哪些值得继续。 (当然,“用户”也就是你。英语缺少代词:如果“你”指的是程序,那么什么代词指的是用户程序员?!)

【讨论】:

  • 你可以省略一半你使用的代词。用“the”代替“your”等。以第三人称谈论节目,先用自己说话。
  • @mydogisbox:谢谢。作为记录,我的抱怨是开玩笑的——我知道一个程序可以被称为“它”——但我很欣赏这种努力。 :-)
  • 只有在搜索树中可以有短分支时,广度优先才优于深度优先。由于这棵树中的每个分支都具有完全相同的深度(输入的长度),因此这只会产生使用最坏情况成倍增加内存的效果。
  • @PaulHankin:正如我在上一段中解释的那样,我依靠用户来修剪/修剪树枝。 (实际上,您正在做同样的事情,但使用的是单词列表。可能最好将这两种方法融合在一起:您的逻辑将帮助程序猜测哪些分支更有可能并提出可能的单词-中断,而使用我在对您的回答的评论中建议的优先级队列将允许它仅向用户建议最可能的分支修剪选项。)
【解决方案3】:

维护 3 样东西:到目前为止的单词列表 S,目前为止的单词 W,以及当前的符号 C。

  • S 应该只是好词,例如。 '快速'
  • W 应该是一个单词的有效前缀,例如。 ['兄弟']
  • C 应该是某个字母的有效前缀,例如。 '.-'

现在,给定一个新符号,比如说'-',我们用它扩展C(在这种情况下,我们得到'.--')。 如果 C 是一个完整的字母(在这种情况下是字母 'W'),我们可以选择将它添加到 W 中,或者通过添加更多符号来进一步扩展字母。 如果我们扩展 W,我们可以选择将它添加到 S(如果它是一个有效的单词),或者继续进一步扩展它。

这是一个搜索,但大多数路径会很快终止(只要您的 W 不是您可以停止的任何单词的有效前缀,并且只要 C 不是您可以停止的任何字母的前缀)。

为了提高效率,您可以使用动态编程来避免冗余工作并使用尝试来有效地测试前缀。

代码可能是什么样的?省略测试字符串是否为英文单词的函数“is_word”和测试字符串是否为任何有效单词开头的函数“is_word_prefix”,如下所示:

morse = {
    '.-': 'A',
    '-...': 'B',
    etc.
}

def is_morse_prefix(C):
    return any(k.startswith(C) for k in morse)

def break_words(input, S, W, C):
    while True:
        if not input:
            if W == C == '':
                yield S
            return
        i, input = input[0], input[1:]
        C += i
        if not is_morse_prefix(C):
            return
        ch = morse.get(C, None)
        if ch is None or not is_word_prefix(W + ch):
            continue
        for result in break_words(input, S, W + ch, ''):
            yield result
        if is_word(W + ch):
            for result in break_words(input, S + ' ' + W + ch, '', ''):
                yield result

for S in break_words('....--', [], '', ''):
    print S

【讨论】:

  • 你在我发帖后添加了开头和结尾可以是垃圾的条件。您可以通过尝试输入、输入[1:]、输入[2:] 直到找到好词之前忽略垃圾来调整代码。对于最后的垃圾,您可以通过放弃 W==C=='' 在开头的测试来忽略它,并且总是产生 S。
  • 使用is_wordis_word_prefix 的棘手部分是摩尔斯电码中的消息可以包含非单词,例如专有名称。我想这些函数对于估计给定 W 的概率比直接拒绝给定 W 更有用。这意味着它不会帮助您的路径“快速终止”(尽管它可能允许使用某种优先级队列来专注于似乎最有可能取得成果的路径)。
  • 这是产生最可能输出的代码的一种变体(它需要动态编程,尽管效率很低)。但是,给出的条件是它是英语,我认为将其解释为消息仅包含字典单词是合理的,尤其是因为这是一个学术程序而不是实用程序。
  • 好吧,但是如果仅根据“W 是一个词吗?”来评估“最可能”,那么您会得到很多虚假的 I (..) 和 A (@987654325 @)。我猜想大部分 20 个符号的摩尔斯电码字符串可以被解码为仅由有效的一个和两个字母的单词组成,即使用更长的单词进行解释对人类来说更有意义。
  • 你能举个例子吗?在我看来,大多数时候你提取'A'或'I'你会在两边留下废话。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 2014-04-20
  • 1970-01-01
  • 1970-01-01
  • 2020-02-11
相关资源
最近更新 更多