你可以用给定单词的字母制作4个字母或更多的常用英文单词(每个字母只能使用一次)

时间:2012-01-15 21:03:31

标签: python algorithm permutation puzzle

在块日历的背面,我找到了以下谜语:

  

你可以用这些字母制作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解决方案是什么?

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

6 个答案:

答案 0 :(得分:3)

这个怎么样?

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'])

答案 1 :(得分:3)

我认为我会分享这个稍微有趣的技巧,虽然它需要的代码比其他代码要多得多,而且不是真正的“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 ++算法的一个端口。如果您想以这种方式比较多个单词,这很有用..

答案 2 :(得分:2)

有一个生成器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)

答案 3 :(得分:2)

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

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)

答案 4 :(得分:2)

以下只检查字典中的每个单词以查看它是否具有适当的长度,然后检查它是否是“教科书”的排列。我从里面借了排列检查 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)

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

答案 5 :(得分:1)

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

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

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

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


("bbo", "bekoottx") 
("bo", "ekoottx") => mismatch

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