【问题标题】:How many common English words of 4 letters or more can you make from the letters of a given word (each letter can only be used once)你能从给定单词的字母中拼出多少个4个或更多字母的常用英语单词(每个字母只能使用一次)
【发布时间】:2012-01-15 21:03:30
【问题描述】:

在块日历的背面,我发现了以下谜语:

你能从这些字母中拼出多少个4个或更多字母的常用英语单词 “教科书”这个词(每个字母只能使用一次)。

我想出的第一个解决方案是:

from itertools import permutations

with open('/usr/share/dict/words') as f:
    words = f.readlines()

words = map(lambda x: x.strip(), words)

given_word = 'textbook'

found_words = []

ps = (permutations(given_word, i) for i in range(4, len(given_word)+1))

for p in ps:
    for word in map(''.join, p):
        if word in words and word != given_word:
            found_words.append(word)
print set(found_words)  

这给出了结果set(['tote', 'oboe', 'text', 'boot', 'took', 'toot', 'book', 'toke', 'betook']),但在我的机器上花了超过 7 分钟。

我的下一个迭代是:

with open('/usr/share/dict/words') as f:
    words = f.readlines()

words = map(lambda x: x.strip(), words)

given_word = 'textbook'

print [word for word in words if len(word) >= 4 and sorted(filter(lambda letter: letter in word, given_word)) == sorted(word) and word != given_word]

几乎立即返回答案,但作为答案给出:['book', 'oboe', 'text', 'toot']

解决这个问题的最快、正确和最 Pythonic 的解决方案是什么?

编辑:添加了我之前的排列解决方案及其不同的输出)。

【问题讨论】:

  • 在看到您的评论之前删除了我的答案,原因与您指出的相同。谢谢
  • 您可以通过对 dict 进行一些预处理并为每个字母分配一个素数表示来非常有效地解决这个问题。如果以后有时间我会写一个解决方案。
  • @Voo 我会等待选择正确答案,直到您提交解决方案。我很期待。
  • 这个问题似乎跑题了,因为它是关于编程难题的 (codegolf.stackexchange.com)

标签: python algorithm permutation puzzle


【解决方案1】:

我想我会分享这个稍微有趣的技巧,尽管它需要的代码比其他的要多一些,而且并不是真正的“pythonic”。这将比其他解决方案花费更多的代码,但如果我查看其他解决方案需要的时间,应该会相当快。

我们正在做一些预处理以加快计算速度。基本方法如下:我们为字母表中的每个字母分配一个质数。例如。 A = 2,B = 3,依此类推。然后,我们为字母表中的每个单词计算一个哈希,它只是单词中每个字符的主要表示的乘积。然后,我们将每个单词存储在由哈希索引的字典中。

现在,如果我们想找出哪些单词与 textbook 等价,我们只需为该单词计算相同的哈希值并在字典中查找即可。通常(比如在 C++ 中)我们不得不担心溢出,但在 python 中它甚至比这更简单:列表中具有相同索引的每个单词都将包含完全相同的字符。

这是经过轻微优化的代码,在我们的例子中,我们只需要担心字符也会出现在给定的单词中,这意味着我们可以使用比其他方式小得多的素数表(明显的优化只是为单词中出现的字符分配一个值 - 无论如何它足够快,所以我没有打扰,这样我们可以只预处理一次并为几个单词执行)。素数算法经常很有用,所以无论如何你都应该拥有一个;)

from collections import defaultdict
from itertools import permutations

PRIMES = list(gen_primes(256)) # some arbitrary prime generator

def get_dict(path):
    res = defaultdict(list)
    with open(path, "r") as file:
        for line in file.readlines():
            word = line.strip().upper()
            hash = compute_hash(word)
            res[hash].append(word)
    return res

def compute_hash(word):
    hash = 1
    for char in word:
        try:
            hash *= PRIMES[ord(char) - ord(' ')]
        except IndexError:
            # contains some character out of range - always 0 for our purposes
            return 0
    return hash

def get_result(path, given_word):
    words = get_dict(path)
    given_word = given_word.upper()
    result = set()
    powerset = lambda x: powerset(x[1:]) + [x[:1] + y for y in powerset(x[1:])] if x else [x]
    for word in (word for word in powerset(given_word) if len(word) >= 4):
        hash = compute_hash(word)
        for equiv in words[hash]:
            result.add(equiv)
    return result

if __name__ == '__main__':
    path = "dict.txt"
    given_word = "textbook"
    result = get_result(path, given_word)
    print(result)

在我的 ubuntu 单词列表(98k 单词)上运行得相当快,但不是我所说的 pythonic,因为它基本上是 c++ 算法的一个端口。如果您想以这种方式比较多个单词,这很有用..

【讨论】:

  • 非常清晰的解释和代码,谢谢。您的代码也返回了正确的单词kobeottototo,现在我想知道为什么我的排列解决方案没有出现这些。
  • @BioGeek 你没有同等地处理大写/小写。
【解决方案2】:

这个怎么样?

from itertools import permutations, chain

with open('/usr/share/dict/words') as fp:
    words = set(fp.read().split())

given_word = 'textbook'

perms = (permutations(given_word, i) for i in range(4, len(given_word)+1))
pwords = (''.join(p) for p in chain(*perms))
matches = words.intersection(pwords)

print matches

给了

>>> print matches
set(['textbook', 'keto', 'obex', 'tote', 'oboe', 'text', 'boot', 'toto', 'took', 'koto', 'bott', 'tobe', 'boke', 'toot', 'book', 'bote', 'otto', 'toke', 'toko', 'oket'])

【讨论】:

    【解决方案3】:

    有一个生成器itertools.permutations,您可以使用它收集具有指定长度的序列的所有排列。这样更容易:

    from itertools import permutations
    
    GIVEN_WORD = 'textbook'
    
    with open('/usr/share/dict/words', 'r') as f:
        words = [s.strip() for s in f.readlines()]
    
    print len(filter(lambda x: ''.join(x) in words, permutations(GIVEN_WORD, 4)))
    

    编辑#1:哦!它说“4个或更多”;)忘记我说的!

    编辑 #2:这是我想出的第二个版本:

    LETTERS = set('textbook')
    
    with open('/usr/share/dict/words') as f:
        WORDS = filter(lambda x: len(x) >= 4, [l.strip() for l in f])
    
    matching = filter(lambda x: set(x).issubset(LETTERS) and all([x.count(c) == 1 for c in x]), WORDS)
    print len(matching)
    

    【讨论】:

    • 没有深入研究,但是这段代码给了我不同的结果,并且执行时间超过了 20 倍。
    • 请注意我的版本只关心每个匹配的单词,每个字母只包含一次。
    • 你的第二个版本只返回每个字母不同的单词(在这种情况下:toke),但toteoboetextboottooktootbookbetook 也是有效的解决方案。
    • 引用:“每个字母只能使用一次”。 ;)
    • @Gandaro 好吧,但是教科书中有两个ts,每个都可以使用一次;)
    【解决方案4】:

    创建整个幂集,然后检查字典单词是否在集合中(字母顺序无关紧要):

    powerset = lambda x: powerset(x[1:]) + [x[:1] + y for y in powerset(x[1:])] if x else [x]
    
    pw = map(lambda x: sorted(x), powerset(given_word))
    filter(lambda x: sorted(x) in pw, words)
    

    【讨论】:

    • 酷,我以前从未听说过 powerset 的概念。小挑剔,您当前的实现不会过滤掉长度为 4 或更多的单词。
    【解决方案5】:

    下面只是检查字典中的每个单词,看它是否具有适当的长度,然后它是否是“教科书”的排列。我借用了排列检查 Checking if two strings are permutations of each other in Python 但稍作改动。

    given_word = 'textbook'
    
    with open('/usr/share/dict/words', 'r') as f:
        words = [s.strip() for s in f.readlines()]
    
    matches = []
    for word in words:
        if word != given_word and 4 <= len(word) <= len(given_word):
            if all(word.count(char) <= given_word.count(char) for char in word):
                matches.append(word)
    print sorted(matches)
    

    这几乎立即完成并给出正确的结果。

    【讨论】:

    • 没有 lambda,没有映射,没有过滤器:最后,this 是 Pythonic。虽然使用生成器推导而不是列表推导和累积循环应该更有效。
    • @Evpok 我可以理解(并同意)为什么列表推导不需要 map 和 filter,但我不明白为什么创建几十个迷你函数会特别是 pythonic 而不是使用 lambdas ?
    • 参见Guido's answer :“[...] 一旦 map()、filter() 和 reduce() 消失了,你真的不需要写很短的 local职能 […]”。如果使用推导式,为什么还需要 lambda?
    • @Evpok 显然不适用于生成器,但适用于许多其他情况。传递琐碎的函数(比如比较器)通常很有用(基本上是任何类型的回调)。根据我的经验,柯里化也是一个非常有用的功能。但肯定取决于编码风格。
    • @Voo 同意比较器,我真的很喜欢函数式编码风格,我经常滥用functools.partial。但 Python 并不是一种函数式语言。
    【解决方案6】:

    对于较长的单词,排列变得非常大。例如,尝试反革命

    我会过滤字典中从 4 到 len(word) 的单词(教科书为 8)。 然后我会用正则表达式“oboe”.matches(“[textbook]+”)过滤。

    剩下的单词,我会排序,并将它们与你单词的排序版本(“beoo”,“bekoottx”)进行比较,并跳转到匹配字符的下一个索引,以查找不匹配的字符数:

    ("beoo", "bekoottx") 
    ("eoo", "ekoottx") 
    ("oo", "koottx") 
    ("oo", "oottx") 
    ("o", "ottx") 
    ("", "ttx") => matched
    
    
    ("bbo", "bekoottx") 
    ("bo", "ekoottx") => mismatch
    

    由于我不谈论 python,所以我将实现作为练习留给观众。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-11
      相关资源
      最近更新 更多