我的问题如下,我想找到字符串中所有出现的子字符串。更具体地说,我想查找所有索引集s[0]...s[n]
,以便st
是st[s[0]], st[s[1], ... st[s[n]]
字符串匹配搜索到的子字符串。 “但为什么??”你问,好吧......因为它在这里on code jam。通过对所有可能的排列进行顺序比较很容易解决,但是对于大字符串来说它会变得很慢。所以我想到了正则表达式。
作为一个例子,对于字符串'abcoeubc'和子串'abc',它应该给我索引[(0,1,2),(0,1,7),(0,6,7)]或类似的东西。我真的不想要指数,只是计算子发生次数。我一直在尝试像
这样的东西import re
r = re.compile(r'a.*b.*c')
matches = [m for i in r.finditer('abcoeubc')]
但它并没有像我预期的那样表现。我也用前瞻性的表达方式尝试过,用r = re.compile(r'(?=a).*(?=b).*(?=c)')
之类的东西,但是也不行。我尝试使用正则表达式是错误的吗?
答案 0 :(得分:3)
你想要速度吗?
超级速度?
嗯...
from bisect import bisect_right
def count_ascending_permutations(sequence_indexes, i, max=float("inf")):
last = sequence_indexes[i]
end = bisect_right(last, max)
return sum(
count_ascending_permutations(sequence_indexes, i-1, item)
for item in last[:end]
) if i else end
def count_allpaths(target, sequence):
sequence_chars = {k: [] for k in sequence}
for i, character in enumerate(target):
if character in sequence_chars:
sequence_chars[character].append(i)
sequence_indexes = [sequence_chars[character] for character in sequence]
return count_ascending_permutations(sequence_indexes, len(sequence_indexes)-1)
<小时/> <小时/>
你无法使用正则表达式执行此操作,因为正则表达式不会找到所有可能的匹配项,只显示位置与您正在搜索的正则表达式匹配。
这是一个解决方案,我将更新解释:
from itertools import takewhile
def ascending_permutations(sequence_indexes, i, max=float("inf")):
last = takewhile(lambda item: item < max, sequence_indexes[i])
if i == 0:
for item in last:
yield [item]
for item in last:
for subitems in ascending_permutations(sequence_indexes, i-1, item):
subitems.append(item)
yield subitems
def allpaths(target, sequence):
sequence_indexes = []
for character in sequence:
sequence_indexes.append([i for i, c in enumerate(target) if c == character])
return ascending_permutations(sequence_indexes, len(sequence_indexes)-1)
list(allpaths("abcoeubcbc", "abc"))
#>>> [[0, 1, 2], [0, 1, 7], [0, 6, 7], [0, 1, 9], [0, 6, 9], [0, 8, 9]]
足够的编辑,解释!
如果您有字符abcoeubcbc
,并且您希望按顺序排列字符abc
,那么您正在查看
0123456780
abcoeubcbc
----------
abc
ab c
a bc
ab c
a bc
不是向前阅读,这是明显的解释,而是向后读。
查看每个c
的位置。它依次位于第2,7和0位。
当它处于位置0时,你可以忽略它之后的所有内容,因为那些都是无序的:
012
abc
---
abc
为b
做同样的事情。好吧,它只有一个位置,a
也一样,所以这很容易。到下一部分:
01234567
abcoeubc
--------
ab c
a bc
b
可以分为两个地方。在这两种情况下,a
都有一个广告位。
然后是最终位置:
0123456780
abcoeubcbc
----------
ab c
a bc
再次b
有两个职位,然后我们递归每个职位,a
只有一个职位。
在我解决了我的饥饿之后,更多关于这与下面的代码的关系!
我回来了,比我预期的要晚一点。我想,不要匆忙,因为OP似乎甚至没有注意到我......
首先我们应该看一下allpaths
。
def allpaths(target, sequence):
sequence_indexes = []
for character in sequence:
sequence_indexes.append([i for i, c in enumerate(target) if c == character])
return ascending_permutations(sequence_indexes, len(sequence_indexes)-1)
allpaths
是包装函数 - 除了为ascending_permutations
设置条件之外,它实际上并没有多少实现。这是必需的,因为正如您稍后将看到的,ascending_permutations
是递归的,我们只想运行此部分一次。
首先,
for character in sequence:
sequence_indexes.append([i for i, c in enumerate(target) if c == character])
为每个字符生成单词中每个出现的索引。这是一个“矩阵”,因为它是一个列表列表:
abcoeubcbc
----------
a|0 | → [[0, ],
b| 1 6 8 | → [1, 6, 8],
c| 2 7 9| → [2, 7, 9]]
----------
当前方法需要O(len(target) × len(sequence))
,可以使用字典将其优化为O(len(target) + len(sequence))
:
sequence_chars = {k: [] for k in sequence}
for i, character in enumerate(target):
if character in sequence_chars:
sequence_chars[character].append(i)
sequence_indexes = [sequence_chars[character] for character in sequence]
这很酷,可以解决。
然后它将此矩阵发送给ascending_permutations
,它完成实际工作。
ascending_permutations
从列表末尾开始向后工作。这可能听起来很奇怪,但它是一个有根据的结构。
假设您有阶乘的递归算法:
def fact(n):
if n == 1:
return n
return n * fact(n-1)
调用fact(3)
执行fib(3) == 3 * fib(2) == 3 * (2 * fib(1)) == 3 * (2 * (1))
我们可以看到,由于括号,我们在乘法时从1 → 2 → 3
向外工作。因为我们希望使用append
来构建我们的列表(快速到append
,慢到insert(0, item)
)我们想要这样做:
(((our_list).append(a's position)).append(b's position)).append(c's position)
因此我们可以看到最外面的范围是c
,而不是a
。因此,我们应该从a
开始。
我们也将len(sequence_indexes)-1
传递给ascending_permutations
,因为我们不想继续pop
项目并将其推回去;我们将进行递归递归,并且更容易跟踪我们认为“结束”的位置。 len(sequence_indexes)-1
是sequence_indexes
中最后一项的位置,在这种情况下,c
的索引很多。
所以,现在谈谈这个职能部门......
def ascending_permutations(sequence_indexes, i, max=float("inf")):
max
跟踪不同类型的结尾;当i
跟踪字母时,max
会跟踪要搜索到的最高索引:
max→
abcoeubcbc
----------
i a|0 | → [[0, ],
↓ b| 1 6 8 | → [1, 6, 8],
c| 2 7 9| → [2, 7, 9]]
----------
然后我们想要遍历我们的“活动”字母,这是最后一封信:
last = takewhile(lambda item: item < max, sequence_indexes[i])
请注意,我们使用takewhile
将数字裁剪为max
,因此没有索引超过我们之前计算过的字母。 max
从无限开始,所以在你选择一封信之前没有限制!
然后我们有一个结束条件:
if i == 0:
for item in last:
yield [item]
基本上这表示如果你只有一个字母,那么你的“路径”就是字母的索引。
最后,我们处于递归的核心。
对于我们的信件所在的每个索引,我们需要单独递归。对于c
,我们会递归到索引2
,7
和9
,例如。
for item in last:
然后我们需要获得“裁剪”金额的所有路径。
for subitems in ascending_permutations(sequence_indexes, i-1, item):
记住这一点:
01234567
abcoeubc
--------
ab c
a bc
?这就是递归的作用:它给出了c
的特定位置的子集(在本例中为7
),并将问题简化为该部分。
现在我们有了该子部分的列表,我们可以将我们的位置(在本例中为7
,记住)添加到结尾并将结果抛给“上游”。
subitems.append(item)
yield subitems
答案 1 :(得分:0)
所以这里是我如何在没有正则表达式的情况下解决它,但是对于大输入来说它变得非常慢。我会尝试检查替代实施......
import re
def simple_match( string, pattern ):
count = 0
# if there's only one character left, return the number of occurrences
if len(pattern)==1:
return len(re.findall( pattern, string ) )
# otherwise find all occurrences of the first char of the remaining string
pos = [i.start() for i in re.finditer( pattern[0], string )]
# and for each position
for i in pos:
# check if the next char still comes up in the remaining string
next2 = re.search( pattern[1], string[i:])
# if it doesn't it, there are no valid substrings remaining, so break
if next2 is None:
break
# else recur with the remaining string and pattern
count+=simple_match( string[(i+1):], pattern[1:] )
return count
可能效率较低,但不要真正理解为什么。