【问题标题】:Efficient way to find anagrams of sentences from dictionary?从字典中查找句子字谜的有效方法?
【发布时间】:2016-03-17 03:59:18
【问题描述】:

我需要编写一个程序,该程序将带有字典和任意字符串的文件作为输入,然后输出该字典中构成给定字符串的字谜的所有单词组合。 例如,使用英语中最流行的 100 个单词和字符串 "i not work",我应该得到类似 [' on it work', ' into work', ' not i work', ' know or it', ' work it no', ' to work in'] 的内容。

问题是我的程序效率太低了:字典中有 100 个单词,字符串长度的实际限制是 7 个字符,之后的所有内容都需要很长时间。我尝试寻找与此事相关的各种算法均无济于事。

这是我搜索字谜的方法:

def sortstring(string):
    return ''.join(sorted(string))

def simplify(all_strings):
    possible_strings = defaultdict(list)
    for string in all_strings:
        possible_strings[sortstring(string).strip()].append(string)
    return possible_strings

def generate(database, length,curstring="", curdata=set()):
    if len(curstring.replace(" ", "")) > length:
        return set()
    if len((curstring).replace(" ", "")) == length:
        return curdata.union(set([curstring]))
    for i in database:
        if len((curstring+i).replace(" ", "")) <= length:
            curdata = curdata.union(generate(database.difference(set([i])),
                length, curstring+" "+i, curdata))
            database = database.difference(set([i]))
    return curdata

def analyse(database, input_string):
    cletters = countstring(input_string)
    strings = simplify(generate(database, cletters))
    data = list()
    sorted_string = sortstring(input_string).strip()
    if sorted_string in strings.keys():
        data = strings[sorted_string]
    return len(strings.values()), data

def countstring(string):
    a = countletters(string)
    return sum(a.values())

def countletters(string):
    result = {}
    for i in ascii_lowercase:
        result[i] = string.count(i)
    return result

谁能提出改进的方法?尽管我认为我使用的算法应该被完全抛弃,因为它的复杂性似乎太高了,因为它太慢了。 以防万一:该程序应该足够高效以支持数万个单词和多达数十个字符的字符串的字典。这比我做的要好得多。

【问题讨论】:

  • 几个测试:用2个字母的字符串,生成时间为0.00085s,用3个字母的字符串,生成时间为0.0039s,用4个字母的字符串,生成时间为0.018s,5个字母串,生成时间0.05s,6个字母串,生成时间0.48s,7个字母串,生成时间4.2s。
  • 首先编写一个函数,识别所有可以由字符串字母组成的单词,然后在回溯算法中使用这个函数。
  • 一种树遍历。空字符串是根。字典中可以由字母组成的单词是儿童。当您访问一个节点时,该节点的子节点是可以由剩余字母组成的单词。任何这样的词也会出现在上一级的可能词列表中——所以你应该能够很快地分辨出哪些词仍然是可能的。如果你到达一个节点,其中有剩余的字母不能组成任何单词——回溯。如果你到达一个没有字母的节点——从根到那个节点的路径就是你寻找的字谜之一。
  • 您不必在每个阶段从头开始重新生成列表——当您沿着树向下移动时,您会丢弃可能的单词,而不是添加新单词。整个字典只需要处理一次。
  • 我美化了你的代码。缺少空白伤害了我过度敏感的眼睛。

标签: python optimization combinations anagram


【解决方案1】:

我自己解决了部分问题。 解决了生成器代码中的 for-if 反模式:

def generate(database, length,letters,curstring="",curdata=set()):
if len(curstring.replace(" ",""))>length:
    return set()
if len((curstring).replace(" ",""))==length:
    return curdata.union(set([curstring]))
t=countletters(curstring)
for i in ascii_lowercase:
    if t[i]>letters[i]:
        return set()
for i in database:
    t=countletters(curstring+i)
    test=0
    for j in ascii_lowercase:
        if t[j]>letters[j]:
            test=1
    if test: continue
    if sum(t.values())<=length:
        curdata=curdata.union(generate(database.difference(set([i])),length,letters,curstring+" "+i,curdata))
        database=database.difference(set([i]))
return curdata

现在速度快了很多,但如果字典包含数万个单词和/或输入字符串很长,它仍然很慢。

【讨论】:

    【解决方案2】:

    这是实现我在 cmets 中建议的树方法的递归方法:

    def frequencyDict(s):
        s = s.lower()
        d = {}
        for c in s:
            if c.isalpha():
                if c in d:
                    d[c] += 1
                else:
                    d[c] = 1
        return d
    
    def canMake(w,fdict):
        d = frequencyDict(w)
        return all(d[c] <= fdict.get(c,0) for c in d)
    
    def candidates(wlist,fdict):
        return [w for w in wlist if canMake(w,fdict)]
    
    def anagrams(wlist,fdict):
        if len(wlist) == 0 or len(fdict) == 0:
            return "no anagrams"
        hits = []
        firstWords = candidates(wlist,fdict)
        if len(firstWords) == 0:
            return "no anagrams"
        for w in firstWords:
            #create reduced frequency dict
            d = fdict.copy() 
            for c in w:
                d[c] -= 1
                if d[c] == 0: del d[c]
            #if d is empty, the first word is also a the last word
            if len(d) == 0:
                hits.append(w)
            else:
                #create reduced word list
                rlist = [v for v in wlist if canMake(v,d)]
                tails = anagrams(rlist, d)
                if tails != "no anagrams":
                    hits.extend(w + " " + t for t in tails)
        if len(hits) == 0:
            return "no anagrams"
        else:
            return hits
    
    def findAnagrams(wlist,s):
        return anagrams(wlist,frequencyDict(s.lower()))
    
    f = open("linuxwords.txt")
    words = f.read().split('\n')
    f.close()
    words = [w.strip().lower() for w in words if not '-' in w]
    test = findAnagrams(words, "Donald Trump")
    

    从旧的 Linux 单词列表中找出所有 730 个“Donald Trump”字谜大约需要 20 秒。我最喜欢的是“干货领主”

    【讨论】:

    • 它似乎比我当前的实现要慢。在 2645 个字谜的情况下,该程序大约需要 3 秒才能找到所有字谜。
    • @Mashallah 我确信有优化。此外,我的代码找到所有 ordered 字谜(“damp nut lord”、“lord nut damp”等所有 3!= 6)排列。一种优化是编写一个返回 multisets 的函数,例如 {'damp','nut','lord'} 而不是 strings
    猜你喜欢
    • 1970-01-01
    • 2017-04-05
    • 2014-03-02
    • 2021-12-06
    • 2011-02-07
    • 2019-06-25
    • 1970-01-01
    • 2011-08-30
    • 1970-01-01
    相关资源
    最近更新 更多