[英]Generating permutations with n consecutive repeated elements
我需要生成p
不同字符的所有可能排列,长度为r
,字符串中包含重复字符。 我有一个额外的限制——在每个排列中的任何一点我只能有n
个连续的重复字符。
约束条件: (1 ≤ p, r, n ≤ 12)
例如:对于字母“AB”,r 值为 4,n 值为 2,我想要以下输出:
AABA
AABB
ABAA
ABAB
ABBA
BAAB
BABA
BABB
BBAA
BBAB
相邻相同字符的最大数量 n 为 2。
我通过查找字符串的笛卡尔积,然后遍历结果并消除任何不适合 n 值的内容,获得了所需的输出。 不过,我的方法非常慢! 如果需要,这是我的代码:
s = "AB"
q = 2
r = 4
pools = [s] * r
result = [[]]
final = []
for pool in pools:
result = [x+[y] for x in result for y in pool]
check = [''.join([l]*(q+1))for l in s]
for r in result:
add = True
z = ''.join(r)
for c in check:
if c in z:
add = False
break
if add:
final.append(z)
print(final)
# ['AABA', 'AABB', 'ABAA', 'ABAB', 'ABBA', 'BAAB', 'BABA', 'BABB', 'BBAA', 'BBAB']
我的解决方案适用于上面的输入,但对于更大的数字/字符串,它需要几分钟。 例如,使用我的解决方案,以下输入需要很长时间:
s = "ABCDEF"
q = 3
r = 10
我想要一个最好不到一秒钟的解决方案。 我写了一个更快的递归解决方案,但是我的机器因较大数字的内存错误而崩溃-所以请不要递归解决方案:)谢谢!
把它想象成一个树遍历问题。 节点是排列的初始段,每个段都可以以p
种方式或p-1
种方式扩展(取决于最后n
字符是否相同)。
一些未优化的代码:
from string import ascii_uppercase
def children(s,p,r,n):
chars = ascii_uppercase[:p]
if len(s)==r:
return []
elif len(s) >= n and s[-n:] == s[-1]*n:
return [s+c for c in chars if c != s[-1]]
else:
return [s+c for c in chars]
def extensions(s,p,r,n):
kids = children(s,p,r,n)
if kids == []:
return [s]
else:
perms = []
for kid in kids:
perms.extend(extensions(kid,p,r,n))
return perms
def perms(p,r,n):
return extensions('',p,r,n)
例如,
>>> perms(2,4,2)
['AABA', 'AABB', 'ABAA', 'ABAB', 'ABBA', 'BAAB', 'BABA', 'BABB', 'BBAA', 'BBAB']
perms(6,10,3)
大约需要一分钟才能生成长度为 10 的ABCDEF
的58792500
个排列,并且没有超过 3 的重复块。它是递归的,但递归的深度以 `r 为界,因此递归不应该崩溃(除非列表本身太大而无法保存在内存中)。 您可以研究非递归列表遍历算法并将其编写为生成器而不是返回列表的东西。
在编辑下面仍然是递归的,但它使用生成器,因此内存效率更高:
def yield_leaves(s,chars,r,n):
if len(s) == r:
yield s
else:
avoid = s[-1] if len(s) >= n and s[-n:] == s[-1]*n else ''
for c in chars:
if c != avoid:
yield from yield_leaves(s+c,chars,r,n)
def yield_perms(chars,r,n):
yield from yield_leaves('',chars,r,n)
我更改了界面以使其更接近您的代码。 例如,
p = list(yield_perms('ABCDEF',10,3))
相当于p = perms(6,10,3)
。 它实际上似乎稍微快了一点。
对于此类问题,您不应排除递归(尽管迭代方法通常更快)。 内存问题是实现之一。 递归与迭代并不是固有的。
这是一个使用递归定义生成器函数的示例:
def permute(letters,size,maxConsec):
if size == 1: yield from letters; return
for s in permute(letters,size-1,maxConsec):
for c in letters:
if size<=maxConsec or s[-1]!=c or not s.endswith(c*maxConsec):
yield s+c
for p in permute("AB",4,2): print(p)
AABA
AABB
ABAA
ABAB
ABBA
BAAB
BABA
BABB
BBAA
BBAB
考虑到要产生的排列的绝对数量,较大的字符串总是需要很长时间。 使用生成器函数的优点之一是不需要将所有中间结果存储在列表中。 因此,您可以将其用作迭代器来计算值,或者在最后有效地制作一个列表:
print( sum(1 for _ in permute("ABCDEF",10,3)) ) # 15.0 seconds
print( len(list(permute("ABCDEF",10,3))) ) # 16.2 seconds
58,792,500 permutations (out of 60,466,176 possible without constraint)
需要存储中间结果的迭代解决方案(无递归)将增加内存注意事项,但可以提供一些性能增益:
def permute(letters,size,maxConsec):
letterMax = [(c,c*maxConsec) for c in letters]
result = [""]
for sz in range(size):
canRepeat = sz < maxConsec
result = [ s+c for s in result for c,m in letterMax
if canRepeat or s[-1]!=c or not s.endswith(m) ]
return result
print(len(permute("ABCDEF",10,3))) # 12.9 seconds
在任何情况下,生成一个包含 5800 万个值的列表都不会在一秒钟内起作用。
list(range(58792500)) # 1.9 seconds
[编辑] 这里是迭代函数的进一步优化版本,如评论和其他改进中所述
from itertools import product
def permute2(letters,size,maxConsec):
result = map("".join,product(*[letters]*maxConsec))
if size == maxConsec: return list(result)
xLetters = { x:[c for c in letters if c != x] for x in letters }
xSuffix = { c*maxConsec for c in letters }
for _ in range(size-maxConsec):
result = [ r+c for r in result
for c in (xLetters[r[-1]] if r[-maxConsec:] in xSuffix else letters) ]
return result
print(len(permute2("ABCDEF",10,3))) # 8.9 seconds
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.