在字符串中查找子字符串的第 n 个匹配项

这看起来应该非常简单,但我是 Python 的新手,希望以最 Python 的方式来完成。

我想找到与字符串中子字符串的非 h 出现相对应的索引。

一定有什么东西等同于我想要做的

mystring.find("substring", 2nd)

如何在 Python 中实现这一点?

274297 次浏览

我可能会这样做,使用带有 index 参数的 find 函数:

def find_nth(s, x, n):
i = -1
for _ in range(n):
i = s.find(x, i + len(x))
if i == -1:
break
return i


print find_nth('bananabanana', 'an', 3)

我想它不是特别的 Python,但是它很简单,你可以用递归代替:

def find_nth(s, x, n, i = 0):
i = s.find(x, i)
if n == 1 or i == -1:
return i
else:
return find_nth(s, x, n - 1, i + len(x))


print find_nth('bananabanana', 'an', 3)

这是一种功能性的解决方法,但我不知道这是否会使它更加 Python 化。

我认为马克的迭代方法是通常的方法。

下面是字符串分解的一种替代方法,它通常对查找相关进程很有用:

def findnth(haystack, needle, n):
parts= haystack.split(needle, n+1)
if len(parts)<=n+1:
return -1
return len(haystack)-len(parts[-1])-len(needle)

这里有一个快速的(有点脏,因为你不得不选择一些不能与针相匹配的谷壳)俏皮话:

'foo bar bar bar'.replace('bar', 'XXX', 1).find('bar')

了解正则表达式并不总是最好的解决方案,我可能会在这里使用一个:

>>> import re
>>> s = "ababdfegtduab"
>>> [m.start() for m in re.finditer(r"ab",s)]
[0, 2, 11]
>>> [m.start() for m in re.finditer(r"ab",s)][2] #index 2 is third occurrence
11

下面是使用 re.finditer 的另一种方法。
不同之处在于,这只是在必要的情况下进行大海捞针

from re import finditer
from itertools import dropwhile
needle='an'
haystack='bananabanana'
n=2
next(dropwhile(lambda x: x[0]<n, enumerate(re.finditer(needle,haystack))))[1].start()

下面是直接的迭代解决方案的一个更加 Python 化的版本:

def find_nth(haystack, needle, n):
start = haystack.find(needle)
while start >= 0 and n > 1:
start = haystack.find(needle, start+len(needle))
n -= 1
return start

例如:

>>> find_nth("foofoofoofoo", "foofoo", 2)
6

如果你想找到 needle的第 n 次 重叠出现,你可以按 1而不是 len(needle)递增,像这样:

def find_nth_overlapping(haystack, needle, n):
start = haystack.find(needle)
while start >= 0 and n > 1:
start = haystack.find(needle, start+1)
n -= 1
return start

例如:

>>> find_nth_overlapping("foofoofoofoo", "foofoo", 2)
3

这比 Mark 的版本更容易阅读,而且不需要分割版本或导入正则表达式模块的额外内存。它还遵守 蟒蛇之禅中的一些规则,不同于 re的各种方法:

  1. 简单比复杂好。
  2. 平坦比嵌套好。
  3. 可读性很重要。
>>> s="abcdefabcdefababcdef"
>>> j=0
>>> for n,i in enumerate(s):
...   if s[n:n+2] =="ab":
...     print n,i
...     j=j+1
...     if j==2: print "2nd occurence at index position: ",n
...
0 a
6 a
2nd occurence at index position:  6
12 a
14 a

下面是另一个 re + itertools版本,当搜索 strRegexpObject时应该可以工作。我坦率地承认,这可能是过度设计,但出于某种原因,它娱乐了我。

import itertools
import re


def find_nth(haystack, needle, n = 1):
"""
Find the starting index of the nth occurrence of ``needle`` in \
``haystack``.


If ``needle`` is a ``str``, this will perform an exact substring
match; if it is a ``RegexpObject``, this will perform a regex
search.


If ``needle`` doesn't appear in ``haystack``, return ``-1``. If
``needle`` doesn't appear in ``haystack`` ``n`` times,
return ``-1``.


Arguments
---------
* ``needle`` the substring (or a ``RegexpObject``) to find
* ``haystack`` is a ``str``
* an ``int`` indicating which occurrence to find; defaults to ``1``


>>> find_nth("foo", "o", 1)
1
>>> find_nth("foo", "o", 2)
2
>>> find_nth("foo", "o", 3)
-1
>>> find_nth("foo", "b")
-1
>>> import re
>>> either_o = re.compile("[oO]")
>>> find_nth("foo", either_o, 1)
1
>>> find_nth("FOO", either_o, 1)
1
"""
if (hasattr(needle, 'finditer')):
matches = needle.finditer(haystack)
else:
matches = re.finditer(re.escape(needle), haystack)
start_here = itertools.dropwhile(lambda x: x[0] < n, enumerate(matches, 1))
try:
return next(start_here)[1].start()
except StopIteration:
return -1

这将在 string 中找到子字符串的第二个匹配项。

def find_2nd(string, substring):
return string.find(substring, string.find(substring) + 1)

编辑: 我没有考虑太多关于性能的问题,但是快速递归可以帮助我们找到第 n 次出现的情况:

def find_nth(string, substring, n):
if (n == 1):
return string.find(substring)
else:
return string.find(substring, find_nth(string, substring, n - 1) + 1)

替换一个班轮是伟大的,但只有工程,因为 XX 和酒吧有相同的长度

一个好的、普遍的定义是:

def findN(s,sub,N,replaceString="XXX"):
return s.replace(sub,replaceString,N-1).find(sub) - (len(replaceString)-len(sub))*(N-1)

我提供一些基准测试结果,比较迄今为止提出的最突出的方法,即@bobince 的 findnth()(基于 str.split())与@tgamblin 的或@Mark Byers 的 find_nth()(基于 str.find())。我还将与 C 扩展(_find_nth.so)进行比较,看看我们能走多快。这里是 find_nth.py:

def findnth(haystack, needle, n):
parts= haystack.split(needle, n+1)
if len(parts)<=n+1:
return -1
return len(haystack)-len(parts[-1])-len(needle)


def find_nth(s, x, n=0, overlap=False):
l = 1 if overlap else len(x)
i = -l
for c in xrange(n + 1):
i = s.find(x, i + l)
if i < 0:
break
return i

当然,如果字符串很大,那么性能最为重要,因此假设我们希望在一个名为“ bigfile”的1.3 GB 文件中找到1000001st newline (’n’)。为了节省内存,我们希望处理文件的 mmap.mmap对象表示形式:

In [1]: import _find_nth, find_nth, mmap


In [2]: f = open('bigfile', 'r')


In [3]: mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

由于 mmap.mmap对象不支持 split(),因此 findnth()已经存在第一个问题。所以我们实际上必须把整个文件复制到内存中:

In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s

哎哟! 幸运的是 s仍然适合我的 Macbook Air 的4GB 内存,所以让我们基准 findnth():

In [5]: %timeit find_nth.findnth(s, '\n', 1000000)
1 loops, best of 3: 29.9 s per loop

显然,这是一个糟糕的表现。让我们看看基于 str.find()的方法是如何做到的:

In [6]: %timeit find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 774 ms per loop

好多了!显然,findnth()的问题在于它被迫在 split()期间复制字符串,这已经是我们在 s = mm[:]之后第二次复制1.3 GB 的数据了。下面是 find_nth()的第二个优点: 我们可以直接在 mm上使用它,这样就需要 文件的副本:

In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop

mms之间似乎存在一个小的性能损失,但是这说明与 findnth总共47秒相比,find_nth()可以在1.2秒内得到一个答案。

我没有发现基于 str.find()的方法明显比基于 str.split()的方法差的情况,所以在这一点上,我认为@tgamblin 或@Mark Byers 的答案应该被接受,而不是@bobince 的。

在我的测试中,上面的 find_nth()版本是我能想到的最快的纯 Python 解决方案(非常类似于@Mark Byers 的版本)。让我们看看使用 C 扩展模块能做得多好。以下是 _find_nthmodule.c:

#include <Python.h>
#include <string.h>


off_t _find_nth(const char *buf, size_t l, char c, int n) {
off_t i;
for (i = 0; i < l; ++i) {
if (buf[i] == c && n-- == 0) {
return i;
}
}
return -1;
}


off_t _find_nth2(const char *buf, size_t l, char c, int n) {
const char *b = buf - 1;
do {
b = memchr(b + 1, c, l);
if (!b) return -1;
} while (n--);
return b - buf;
}


/* mmap_object is private in mmapmodule.c - replicate beginning here */
typedef struct {
PyObject_HEAD
char *data;
size_t size;
} mmap_object;


typedef struct {
const char *s;
size_t l;
char c;
int n;
} params;


int parse_args(PyObject *args, params *P) {
PyObject *obj;
const char *x;


if (!PyArg_ParseTuple(args, "Osi", &obj, &x, &P->n)) {
return 1;
}
PyTypeObject *type = Py_TYPE(obj);


if (type == &PyString_Type) {
P->s = PyString_AS_STRING(obj);
P->l = PyString_GET_SIZE(obj);
} else if (!strcmp(type->tp_name, "mmap.mmap")) {
mmap_object *m_obj = (mmap_object*) obj;
P->s = m_obj->data;
P->l = m_obj->size;
} else {
PyErr_SetString(PyExc_TypeError, "Cannot obtain char * from argument 0");
return 1;
}
P->c = x[0];
return 0;
}


static PyObject* py_find_nth(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}


static PyObject* py_find_nth2(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth2(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}


static PyMethodDef methods[] = {
{"find_nth", py_find_nth, METH_VARARGS, ""},
{"find_nth2", py_find_nth2, METH_VARARGS, ""},
{0}
};


PyMODINIT_FUNC init_find_nth(void) {
Py_InitModule("_find_nth", methods);
}

这是 setup.py文件:

from distutils.core import setup, Extension
module = Extension('_find_nth', sources=['_find_nthmodule.c'])
setup(ext_modules=[module])

像往常一样用 python setup.py install安装。C 代码在这方面有一个优势,因为它仅限于查找单个字符,但是让我们看看它的速度有多快:

In [8]: %timeit _find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 218 ms per loop


In [9]: %timeit _find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 216 ms per loop


In [10]: %timeit _find_nth.find_nth2(mm, '\n', 1000000)
1 loops, best of 3: 307 ms per loop


In [11]: %timeit _find_nth.find_nth2(s, '\n', 1000000)
1 loops, best of 3: 304 ms per loop

显然更快了。有趣的是,在 C 级别上,内存中的情况和映射的情况没有什么不同。同样有趣的是,基于 string.hmemchr()库函数的 _find_nth2()_find_nth()的简单实现中失败了: memchr()中的额外“优化”显然会适得其反..。

总之,findnth()中的实现(基于 str.split())实际上是一个坏主意,因为(a)由于需要复制,它对较大的字符串执行得很糟糕,(b) 它对 mmap.mmap对象根本不起作用。find_nth()中的实现(基于 str.find())在所有情况下都应该是首选的(因此是这个问题的公认答案)。

由于 C 扩展运行速度几乎比纯 Python 代码快4倍,因此仍有很大的改进空间,这表明可能需要专用的 Python 库函数。

提供另一个“棘手”的解决方案,使用 splitjoin

在你的例子中,我们可以使用

len("substring".join([s for s in ori.split("substring")[:2]]))

最简单的方法?

text = "This is a test from a test ok"


firstTest = text.find('test')


print text.find('test', firstTest + 1)

这个怎么样:

c = os.getcwd().split('\\')
print '\\'.join(c[0:-2])

这就是你真正想要的答案:

def Find(String,ToFind,Occurence = 1):
index = 0
count = 0
while index <= len(String):
try:
if String[index:index + len(ToFind)] == ToFind:
count += 1
if count == Occurence:
return index
break
index += 1
except IndexError:
return False
break
return False

这将为您提供与 yourstring匹配的起始索引数组:

import re
indices = [s.start() for s in re.finditer(':', yourstring)]

那么你的第 N 个条目是:

n = 2
nth_entry = indices[n-1]

当然,您必须小心使用索引界限。你可以像下面这样得到 yourstring的数量:

num_instances = len(indices)

建立在 模特13的答案之上,但是没有 re模块的依赖关系。

def iter_find(haystack, needle):
return [i for i in range(0, len(haystack)) if haystack[i:].startswith(needle)]

我有点希望这是一个内置的字符串方法。

>>> iter_find("http://stackoverflow.com/questions/1883980/", '/')
[5, 6, 24, 34, 42]
# return -1 if nth substr (0-indexed) d.n.e, else return index
def find_nth(s, substr, n):
i = 0
while n >= 0:
n -= 1
i = s.find(substr, i + 1)
return i

不使用循环和递归的解决方案。

在编译方法中使用所需的模式并输入所需的 出现在变量 中,最后一个语句将打印 中模式第 n 次出现的起始索引 这里将转换 finditer 的结果,即迭代器 列出并直接访问第 n 个索引。

import re
n=2
sampleString="this is history"
pattern=re.compile("is")
matches=pattern.finditer(sampleString)
print(list(matches)[n].span()[0])

以下是在字符串 a中查找 n第一次出现 b的解决方案:

from functools import reduce




def findNth(a, b, n):
return reduce(lambda x, y: -1 if y > x + 1 else a.find(b, x + 1), range(n), -1)

它是纯 Python 和迭代的。对于太大的0或 n,返回 -1。它是一行程序,可以直接使用。这里有一个例子:

>>> reduce(lambda x, y: -1 if y > x + 1 else 'bibarbobaobaotang'.find('b', x + 1), range(4), -1)
7

在特殊情况下,你搜索一个字符的非 h 出现(即长度为1的子字符串) ,下面的函数通过构建给定字符的所有出现位置的列表来工作:

def find_char_nth(string, char, n):
"""Find the n'th occurence of a character within a string."""
return [i for i, c in enumerate(string) if c == char][n-1]

如果给定字符的出现次数少于 n,它将给出 IndexError: list index out of range

这是从@Zv _ oDD 的 回答派生出来的,对于单个字符的情况进行了简化。

Def:

def get_first_N_words(mytext, mylen = 3):
mylist = list(mytext.split())
if len(mylist)>=mylen: return ' '.join(mylist[:mylen])

使用方法:

get_first_N_words('  One Two Three Four ' , 3)

产出:

'One Two Three'

当提供的出现次数的输入值高于实际出现次数时,应避免出现故障或不正确的输出。例如,在字符串‘ overflow’中,如果您要检查第3次出现的‘ o’(它只有2次出现) ,那么下面的代码将返回一个警告或消息,指示出现的值已经超过。

输入的发生次数已超过实际发生次数。

def check_nth_occurrence (string, substr, n):


## Count the Occurrence of a substr
cnt = 0
for i in string:
if i ==substr:
cnt = cnt + 1
else:
pass


## Check if the Occurrence input has exceeded the actual count of Occurrence


if n > cnt:
print (f' Input Occurrence entered has exceeded the actual count of Occurrence')
return


## Get the Index value for first Occurrence of the substr


index = string.find(substr)


## Get the Index value for nth Occurrence of Index
while index >= 0 and n > 1:
index = string.find(substr, index+ 1)
n -= 1
return index

这里有一个简单而有趣的方法:

def index_of_nth(text, substring, n) -> int:
index = 0
for _ in range(n):
index = text.index(substring, index) + 1
return index - 1

以防有人想从后面找到第 N 个:

def find_nth_reverse(haystack: str, needle: str, n: int) -> int:
end = haystack.rfind(needle)


while end >= 0 and n > 1:
end = haystack.rfind(needle, 0, end - len(needle))
n -= 1


return end

我使用了 findnth ()函数,但遇到了一些问题,所以我重写了该函数的一个更快的版本(没有拆分列表) :

def findnth(haystack, needle, n):
if not needle in haystack or haystack.count(needle) < n:
return -1


last_index = 0
cumulative_last_index = 0
for i in range(0, n):
last_index = haystack[cumulative_last_index:].find(needle)
cumulative_last_index += last_index
        

# if not last element, then jump over it
if i < n-1:
cumulative_last_index += len(needle)


return cumulative_last_index

对于那些具有基本编程知识的人来说,一个简单的解决方案是:

# Function to find the nth occurrence of a substring in a text
def findnth(text, substring, n):


# variable to store current index in loop
count = -1


# n count
occurance = 0


# loop through string
for letter in text:
    

# increment count
count += 1
    

# if current letter in loop matches substring target
if letter == substring:
        

# increment occurance
occurance += 1
        

# if this is the nth time the substring is found
if occurance == n:
            

# return its index
return count
        

# otherwise indicate there is no match
return "No match"


# example of how to call function
print(findnth('C$100$150xx', "$", 2))

我是这样解决的。

def second_index(text: str, symbol: str) -> [int, None]:
"""
returns the second index of a symbol in a given text
"""
first = text.find(symbol)
result = text.find(symbol,first+1)
if result > 0: return result