【问题标题】:Given a word, how do I get the list of all words, that differ by one letter?给定一个单词,我如何获得所有单词的列表,它们相差一个字母?
【发布时间】:2013-12-09 20:57:33
【问题描述】:

假设我有“CAT”这个词。这些词与“CAT”相差一个字母(不是完整列表)

  • 剪切
  • 拍拍
  • 脂肪
  • 婴儿床

有没有一种优雅的方式来生成它?显然,一种方法是通过蛮力。

伪代码:

while (0 to length of word)
    while (A to Z)
        replace one letter at a time, and check if the resulting word is a valid word

如果我有一个 10 个字母的单词,循环将运行 26 * 10 = 260 次。

有没有更好、更优雅的方法来做到这一点?

【问题讨论】:

  • 你只需要 25*10 次检查

标签: algorithm


【解决方案1】:

给定一个单词列表,例如

words = set(line.strip().lower() for line in open('/usr/share/dict/words'))

您可以构建和索引“通配符”单词,在其中将单词的每个字符替换为通配符(例如“?”),以便例如“gat”和“fat”都被索引为“?at ":

def wildcard(s, idx):
    return s[:idx] + '?' + s[idx+1:]

def wildcarded(s):
    for idx in xrange(len(s)):
        yield wildcard(s, idx)

# list(wildcarded('cat')) returns ['?at', 'c?t', 'ca?']

from collections import defaultdict
index = defaultdict(list)

for word in words:
    for w in wildcarded(word):
        index[w].append(word)

现在,如果您要查找与“cat”相差一个字母的所有单词,只需查找“?at”、“c?t”和“ca?”并连接结果:

def near_words(word):
    ret = []
    for w in wildcarded(word):
        ret += index[w]
    return ret

print near_words('cat')
# outputs ['cat', 'bat', 'zat', 'jat', 'kat', 'rat', 'sat', 'pat', 'hat', 'oat', 'gat', 'vat', 'nat', 'fat', 'lat', 'wat', 'eat', 'yat', 'mat', 'tat', 'cat', 'cut', 'cot', 'cit', 'cay', 'car', 'cap', 'caw', 'cat', 'can', 'cam', 'cal', 'cad', 'cab', 'cag']
print near_words('stack')
# outputs ['stack', 'stack', 'smack', 'spack', 'slack', 'snack', 'shack', 'swack', 'stuck', 'stack', 'stick', 'stock', 'stank', 'stack', 'stark', 'stauk', 'stalk', 'stack']

如果最大字长为L,字数为N,则索引由O(NL)指针组成,而查找算法及时运行O(L + number of results)

如果你想查找所有与K 字母而不是1 不同的单词,这种方法不能很好地概括,但它是一个非常难以完全概括的问题(这是寻找邻居的问题在汉明空间)。

【讨论】:

    【解决方案2】:
    1. 弄清楚你的性能要求到底是什么。

    2. 完全按照您上面描述的方式实现它。

    3. 计时,看看你是否已经满足这些要求。

    4. 仅在需要时进行优化(我敢打赌这不是必需的,因为在适合 RAM 的单词哈希表中查找 260 次并不那么慢。)

    【讨论】:

    • CAT 只是一个例子。当单词由更多字母组成时,我们就是在用不存在的单词浪费时间。
    • 250 个哈希查找不会花费任何时间
    • 是的,@Dialectus。 CAT 只需要 78 次查找。一直到 (gulp) 260 需要 10 个字母。:-) 我不知道性能要求,但我强烈怀疑天真的实现会满足任何合理的要求。
    【解决方案3】:

    人类语言的字典大小和字长都很小(~10**5 和~100),因此除非测量结果在您的情况下显示,否则可以采用蛮力方法:

    #!/usr/bin/env python
    import string
    
    ALL_WORDS = set(open('/usr/share/dict/words').read().lower().split())
    ALPHABET = string.ascii_lowercase
    
    def known(words): return set(w for w in words if w in ALL_WORDS)
    
    def one_letter(word):
        # http://norvig.com/spell-correct.html
        splits = ((word[:i], word[i:]) for i in range(len(word) + 1))
        replaces  = (a + c + b[1:] for a, b in splits for c in ALPHABET if b)
        return set(replaces)
    
    from pprint import pprint
    pprint(known(one_letter("cat")))
    

    输出

    set(['bat',
         'cab',
         'cad',
         'cal',
         'cam',
         'can',
         'cap',
         'car',
         'cat',
         'caw',
         'cot',
         'cut',
         'eat',
         'fat',
         'hat',
         'mat',
         'nat',
         'oat',
         'pat',
         'rat',
         'sat',
         'tat',
         'vat'])
    

    【讨论】:

      【解决方案4】:

      您需要一个有效单词字典来检查,否则问题不会生成“单词”而是“字符串”。有许多在线免费可用,或者如果您使用的是 Linux,大多数发行版都在/usr/share/dict/ 中提供字典文件。

      有两种方法可以采取:

      1. 对于单词中的每个字母,将其替换为所有其他 25 个字符并检查它是否在字典中。使用哈希表存储字典单词以进行高效查询。您只需要使用与您的搜索词长度相同的词来填充哈希表。这将是 O(MN + 25N) = O(MN),其中 M 是字典中长度为 N 的单词的数量,N 是单词的长度。

      2. 对于每个与您的搜索词长度相同的字典词,检查有多少字符不同。这将是 O(MN)。

      虽然两者都属于同一个复杂度类别,但后者降低了 O(25N) 项和与哈希表相关的开销。

      【讨论】:

      • 两者都涉及一次替换一个字母。这类似于伪代码,对吗?
      • @user 第一个解决方案替换所以在这方面是相似的。第二种解决方案计算变化。
      【解决方案5】:

      对于:l = 字长,w = 字表中的字数:

      你的算法是 O(l.(l log w)) 用于树词表,加上首先构建词表的成本(即 O(w log (w)) ) (我假设这里有一棵树,如果你愿意,你可以用哈希重做这个)。

      这是 O(l.w)

      正如另一个答案已经暗示的那样,您并不关心单词是否有 a、b 或 z 来代替您要更改的字符,您只关心它不是您开头的字母。所以测试你不想要的一种组合,而不是所有可以做的组合。

      所以:

      for(each candidate word from the wordlist) {
        difference = 0
        for(each letter in your original word) {
          does it match? if not, difference++
        }
        if difference = 1, store the candidate word as a solution
      }
      

      现在,您会争辩说您正在查看 78 次比较与数千次比较,但这并不准确:为了使用词汇表来查看是否有候选人,您的方法涉及创建内容 -在您开始之前寻址结构(树或哈希),以及在您运行后查找哈希。上面的解决方案还允许您为每个被测单词读取一次单词列表文件(无需将其保存在内存中进行重新扫描)。您的解决方案可能会更快地同时对多个单词执行此操作,但上述方法更适合单个单词查找,并且在每种情况下都更节省内存。

      感谢其他答案发现单词差异的“计算差异”方法......

      【讨论】:

      • "上述解决方案允许您每个单词读取一次单词表文件。"很好,嗯,那里有功能。 :-P
      • 为了花生画廊的利益而改变;)
      • 很公平 ;)
      【解决方案6】:

      无论如何,您都需要遍历所有字母来检查它。但另一种方法是检查字典中的单词,它对应于掩码?AT,C?T,CA? (其中 ? 可以是每个符号)

      【讨论】:

      • 我不确定我是否理解。检查掩码,是不是和我的伪代码一样?
      • 不,但它可能会慢得多,因为您将查看字典中的每个单词 k 次(其中 K 是单词中的字母数)
      • 这是你的算法的一个反转版本,它在字典中搜索合适的单词,而你遍历所有单词并检查这个单词是否在字典中可用。根据字典大小/单词大小,一种或另一种可能更有效。
      【解决方案7】:

      如果字符串的长度始终匹配,一种方法是一次删除一个字母并比较两个字符串的结果,10 个字符将是 10 个循环。

      问候, /t

      【讨论】:

      • 我认为这比一次替换一个字母效率低,假设我们有一个包含所有单词的数据库。例如,如果我们有 1000 个三个字母的单词,这将涉及 1000 * 3 = 3000 次比较。但如果我们一次替换一个字母,则为 26 * 3 = 78。
      【解决方案8】:

      迭代单词列表并为每个单词计算不同的字母。如果计数大于 1,则转到下一个单词。

      更快的解决方案,如果字典是静态的并且有很多单词要检查:创建一个字母矩阵。行是单词的第一个字母,列是单词的第二个字母。单元格是以给定的第一个和第二个字母开头的单词列表。当您想查找给定单词的相似单词时,只需遍历一行,然后仅遍历一列。如果不在相交单元格上,则每个迭代单词的所有其他字母都必须匹配。在相交的单元格上,一个字母必须不同。

      【讨论】:

        【解决方案9】:

        如果你真的想要优化运行时(我仍然说你可能不需要在任何合理的性能情况下)然后浏览字典一次,然后运行你的算法每一个字。

        创建一个从损坏的单词到每个对应正确拼写单词的列表的映射。

        我估计这 20,000 个单词,每秒至少处理 30 个单词,处理时间不会超过 11 分钟。

        将生成的哈希表存储在磁盘上,并在需要时将其加载到内存中。然后通过简单地在哈希表中查找输入单词并找到对应的正确拼写单词列表来执行处理。

        内存密集,但速度超快 - 如果您担心 260 次查找的性能,您必须处理数万个单词,而这样的解决方案可能是您能得到的最佳解决方案。

        【讨论】:

          猜你喜欢
          • 2015-01-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-17
          相关资源
          最近更新 更多