找到所有可能的 N 长度字谜 - 快速替代

Posted

技术标签:

【中文标题】找到所有可能的 N 长度字谜 - 快速替代【英文标题】:Find all the possible N-length anagrams - fast alternatives 【发布时间】:2017-12-04 18:03:53 【问题描述】:

我得到一个字母序列,并且必须生成给定序列的所有 N 长度字谜,其中 N 是序列的长度。

我在 python 中遵循一种有点幼稚的方法,我正在采用所有排列来实现这一点。我发现了一些类似的线程,例如this one,但我更喜欢 Python 中的面向数学的方法。那么有什么比排列更高效的替代方案呢?我下面的尝试有什么特别错误的吗?

from itertools import permutations
def find_all_anagrams(word):

pp = permutations(word)
perm_set = set()
for i in pp:
    perm_set.add(i)
ll = [list(i) for i in perm_set]
ll.sort()
print(ll)

【问题讨论】:

见***.com/questions/40752319/… 【参考方案1】:

如果有很多重复的字母,关键是每个字谜只产生一次,而不是产生所有可能的排列并消除重复。

这是一种可能的算法,它只生成每个字谜一次:

from collections import Counter

def perm(unplaced, prefix):
  if unplaced:
    for element in unplaced:
      yield from perm(unplaced - Counter(element), prefix + element)
  else:
    yield prefix

def permutations(iterable):
  yield from perm(Counter(iterable), "")

这实际上与产生所有排列的经典递归没有太大区别;唯一的区别是它使用collections.Counter(一个多重集)来保存尚未放置的元素,而不仅仅是使用列表。

在迭代过程中产生的Counter对象的数量肯定是过多的,几乎可以肯定有一种更快的写法;我选择这个版本是因为它的简单性和(希望)它的清晰

【讨论】:

非常感谢,我将从这里开始挖掘文档。 我已将缺少的递归调用添加到perm() @jfsebastian:谢谢。我不知道我是如何做到如此糟糕地复制和粘贴的。【参考方案2】:

对于具有许多相似字符的长词来说,这非常慢。与理论上的最大性能相比,速度较慢。例如,permutations("mississippi") 将生成一个比必要的更长的列表。它的长度为 39916800,但集合的大小为 34650。

>>> len(list(permutations("mississippi")))
39916800
>>> len(set(permutations("mississippi")))
34650

因此,您的方法的最大缺陷是您生成所有字谜,然后删除重复项。使用只生成唯一字谜的方法。

编辑:

这里有一些工作,但极其丑陋且可能有错误的代码。当您阅读本文时,我正在使它变得更好。它确实为密西西比州提供了 34650,所以我认为没有任何重大错误。再次警告。丑!

# Returns a dictionary with letter count
# get_letter_list("mississippi") returns 
# 'i':4, 'm':1, 'p': 2, 's':4
def get_letter_list(word):
    w = sorted(word)
    c = 0
    dd = 
    dd[w[0]]=1
    for l in range(1,len(w)):
        if w[l]==w[l-1]:
            d[c]=d[c]+1
            dd[w[l]]=dd[w[l]]+1
        else:
            c=c+1
            d.append(1)
            dd[w[l]]=1
    return dd

def sum_dict(d):
    s=0
    for x in d:
        s=s+d[x]
    return s

# Recursively create the anagrams. It takes a letter list
# from the above function as an argument.
def create_anagrams(dd):
    if sum_dict(dd)==1: # If there's only one letter left
        for l in dd:
            return l # Ugly hack, because I'm not used to dics
    a = []
    for l in dd:
        if dd[l] != 0:
            newdd=dict(dd)
            newdd[l]=newdd[l]-1
            if newdd[l]==0:
                newdd.pop(l)
            newl=create(newdd)
        for x in newl:
            a.append(str(l)+str(x))
    return a

>>> print (len(create_anagrams(get_letter_list("mississippi"))))
34650

它的工作原理是这样的:对于每个唯一的字母 l,创建所有唯一的排列,其中字母 l 的出现次数少一次,然后将 l 附加到所有这些排列中。

对于“mississippi”,这比 set(permutations(word)) 快得多,而且远非最佳编写。例如,字典很慢,这段代码可能有很多地方需要改进,但它表明算法本身比你的方法快得多。

【讨论】:

你能给我举个例子吗?如果我不先生成它,我怎么知道它是否是重复的? 你不需要知道是否有重复。您只需要一个不会创建重复项的算法。致力于更好的答案 atm。 这是一个simple "all uniq anagrams" algorithm(它不包括重复项,即它只为 "mississippi" 生成 34650 个变体)。虽然短序列的时间性能可能比 set(itertools.permutations(..))【参考方案3】:

也许我错过了什么,但你为什么不这样做:

from itertools import permutations

def find_all_anagrams(word):
    return sorted(set(permutations(word)))

【讨论】:

不是我没有得到我想要的值,而是性能问题。有没有更高效的选择?这有点蛮力【参考方案4】:

你可以简化为:

from itertools import permutations

def find_all_anagrams(word):
    word = set(''.join(sorted(word)))
    return list(permutations(word))

在permutations 的文档中,代码很详细,似乎已经优化。

【讨论】:

什么意思?我只能看到排列函数是如何实现的,而不是使用示例?我错过了什么吗? 不,你没有错过任何东西;但是 IMO 大部分由非常常用的库实现的功能都被过度优化并且已经遵循数学逻辑。无论如何,如果您想要更快的替代方案,则必须对其进行基准测试以确保确定。【参考方案5】:

我不知道python,但我想帮助你:可能还有很多其他性能更高的算法,但我考虑过这个:它是完全递归的,它应该涵盖所有情况排列。我想从一个基本的例子开始:

ABC的排列

现在,该算法以这种方式工作:Length 次您将字母向右移动,但最后一个字母将成为第一个字母(您可以通过队列轻松做到这一点)。

回到例子,我们将有:

ABC BCA 驾驶室

现在您使用从第二个字母到最后一个字母构建的子字符串重复第一步(也是唯一的)步骤。

很遗憾,使用此算法,您不能考虑重复排列。

【讨论】:

以上是关于找到所有可能的 N 长度字谜 - 快速替代的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快?

在目标 c 中找到数组内所有字谜的快速方法是啥?

重复排列 - 非字谜算法

将所有字谜组合在一起[关闭]

这是一个字谜程序,我正在检查两个相同长度的字符串是不是相互字谜

子串字谜 · Find All Anagrams in a String