在不到一秒的时间内执行一个字谜检查器算法

Posted

技术标签:

【中文标题】在不到一秒的时间内执行一个字谜检查器算法【英文标题】:Executing an anagram checker algorithm in less than a second 【发布时间】:2020-05-07 20:23:16 【问题描述】:

我创建了这个算法来检查两个字符串是否是彼此的字谜。 在本练习中,如果两个字符串具有相同的字符或仅相差一个,我认为它们是彼此的字谜。例如,数学和数学是字谜,但即使是数学和数学也是字谜。 我需要在不到一秒的时间内执行此算法,但测试中包含一些示例,有时需要超过 10 分钟。很明显,这可以做得更好。嵌套的 for 循环是问题所在,但如果没有它,我就想不出一个可能的解决方案。

#len(word1) <= len(word2)
def check(word1, word2): 
   lword1 = len(word1)
   lword2 = len(word2)
   sword1 = sorted(word1)
   sword2 = sorted(word2)

   # lword1 == lword2
   if lword1 == lword2: 
       return sword1 == sword2

   # lword1 < lword2, word2 has one more character
   sword2_copy = sword2.copy()
   for c in sword2:
       if c in sword1:
           sword1.remove(c)
           sword2_copy.remove(c)

   return len(sword1) == 0 and len(sword2_copy) == 1


def main(fin, fout, k):

   words = [line.rstrip('\n') for line in open(fin)] 
   words = [x.strip(' ') for x in words]

   d = 

   for w1 in words:
       for w2 in words:
           if len(w1) == len(w2) or len(w1) == len(w2) - 1:
               if check(w1, w2):
                   if w1 not in d.keys():
                       d[w1] = [w2]
                   else:
                       d[w1].append(w2)

   highV = list(d.values())[0]
   highK = list(d.keys())[0]
   for key, value in d.items():
       if len(value) > len(highV) or (len(value) == len(highV) and key < highK):
           highK = key
           highV = value

   highV.sort()

   with open(fout, 'w') as f:
       for i in range(len(highV)):
           f.write(highV[i]+' ')
           if (i + 1) % k == 0: 
               f.write('\n')

   return int(len(highV))

【问题讨论】:

请修正代码缩进。 在长达一分钟的计算中产生什么输入? @usr2564301 超过 10000 个字符串的文本文件,每个字符串长度超过 20 个字符。我基本上需要找到文件中字谜最多的单词。 您是在寻求高效的答案吗?这可能涉及将此代码迁移到 C。Python 只是没有与 C 相同的内存访问。 @JasonV 我正在寻找一种方法来删除需要很长时间的嵌套循环。在一个包含 20000 个单词的文本文件上,它执行 if 4 亿次。 【参考方案1】:

您应该从集合中检查计数器:

from collections import Counter
str = 'Testanagram'
counter = Counter(str)
print(counter)
> Counter('a': 3, 'T': 1, 'e': 1, 's': 1, 't': 1, 'n': 1, 'g': 1, 'r': 1, 'm': 1)

使用这个,你应该更快 - 你也可以从另一个计数器中减去一个计数器来获得差异

【讨论】:

谢谢,但不幸的是我不能在这个测试中使用导入。我正在寻找的是一种删除嵌套 for 循环的方法,这是最耗时的方法。 不用减去;只需使用Counter(str1) == Counter(str2) Counter 在您需要检查 'off by one' 时并没有太大帮助。您仍然可以使用 Counter 来计算字符数,但是然后确定是否只有一个字符关闭的逻辑对于任一单词不超过一个会大大减慢速度。 @Grismar 我认为只需减去然后检查最多剩下一个元素就足够了。 我认为你需要有两个减法。请参阅我发布的答案,它具有(非常简单和优雅)基于Counter 的解决方案,我认为这就是您的建议。但是,这比 OP 的建议和我自己的建议要慢得多。【参考方案2】:

这似乎工作得很快,虽然对于我的单词列表(235,886 个,来自/usr/share/dict/words 的完整列表)大约需要 2 秒,所以对你来说可能仍然太慢。但是 - 老实说,您打算多久在整个列表中运行一次?

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

wordlist = [word.strip() for word in wordlist]
wordlist = [(word,''.join(sorted([kar for kar in word]))) for word in wordlist]

worddict = 
for word in wordlist:
    if word[1] in worddict:
        worddict[word[1]].append(word[0])
    else:
        worddict[word[1]] = [word[0]]

for word in wordlist:
    if len(worddict[word[1]]) > 1:
        print (worddict[word[1]])

结果:

['aal', 'ala']
['aam', 'ama']
['aba', 'baa']
['abac', 'caba']
['abactor', 'acrobat']
['abaft', 'bafta']
['abalone', 'balonea']
...
(27,390 lines omitted for brevity)
['ozotype', 'zootype']
['gazy', 'zyga']
['glazy', 'zygal']
[Finished in 2.1s]

它创建一个字典,其中每个单词的排序字符作为 key,并创建一个仅包含该单词本身作为其初始 value 的列表。如果该键已存在于字典中,则将该词附加到其列表中。然后只需打印所有超过 1 个项目的列表即可。

副作用是所有字谜单词出现多次。这就是你的逻辑:在这个单词列表中的incomputableuncompatible 的字谜,因此uncompatible(根据这个列表中的定义)是@987654327 的字谜@。 QED。¹

它找到的最大的字谜词集是这个:

['angor', 'argon', 'goran', 'grano', 'groan', 'nagor', 'orang', 'organ', 'rogan']

还有一对有趣的对立面

['misrepresentation', 'representationism']

该列表甚至包含“pythonic”这个词:

['hypnotic', 'phytonic', 'pythonic', 'typhonic']

¹ 尝试后:仅打印每个组合一次似乎是微不足道的。它将列表减少到 12,189 个“唯一”集,并且检查又花了 0.1 秒。

【讨论】:

【参考方案3】:

主要优化在check()

使用collections.Counter 之类的方法,解决方案看起来更简单,但速度较慢:

def check_with_counter(word1, word2):
    c1 = Counter(word1)
    c2 = Counter(word2)
    return sum(((c1 - c2) + (c2 - c1)).values()) < 2

与您的解决方案类似,但速度要快得多(大约一个数量级)

def check_faster(word1, word2):
    # checks faster than check() and works for words of any length (in any order)
    ld = len(word1) - len(word2)
    if ld in [0, 1]:
        sword_long = list(word1)
        sword_short = list(word2)
        if ld == 0:
            return sword_long == sword_short
    elif ld == -1:
        sword_long = list(word2)
        sword_short = list(word1)
    else:
        return False

    for c in sword_short:
        try:
            sword_long.remove(c)
        except ValueError:
            pass

    return len(sword_long) < 2

并将其用于更快的run()

def run_faster(fin, fout, k):
    words = [line.rstrip('\n') for line in open(fin)]
    words = [x.strip(' ') for x in words]

    d = 

    for w1 in words:
        for w2 in words:
            if check_faster(w1, w2):
                if w1 not in d.keys():
                    d[w1] = [w2]
                else:
                    d[w1].append(w2)

    most = 0
    most_anagrams = []
    for word, anagrams in d.items():
        if len(anagrams) > most:
            most = len(anagrams)
            most_anagrams = anagrams

    most_anagrams.sort()

    with open(fout, 'w') as f:
        for i in range(len(most_anagrams)):
            f.write(most_anagrams[i]+' ')
            if (i + 1) % k == 0:
                f.write('\n')

    return int(len(most_anagrams))

【讨论】:

以上是关于在不到一秒的时间内执行一个字谜检查器算法的主要内容,如果未能解决你的问题,请参考以下文章

Graphite 在不到一秒的时间间隔内聚合数据

Elasticsearch我们如何将 100 秒的 elasticsearch 查询优化为不到一秒。

限流算法之漏桶算法和令牌桶算法

怎么让JAVA循环一秒一秒的执行

Mysql对字谜求解器的多个查询

优化子串字谜比较算法