Найти n-е вхождение подстроки в строку
Кажется, что это должно быть довольно тривиально, но я новичок в Python и хочу сделать это самым питоническим способом.
Я хочу найти n-е вхождение подстроки в строку.
Там должно быть что-то эквивалентное тому, что я хочу сделать, это
mystring.find("substring", 2nd)
Как вы можете добиться этого в Python?
Ответы
Ответ 1
Я думаю, что итеративный подход будет обычным способом.
Здесь альтернатива с разбиением строк, которая часто может быть полезна для процессов, связанных с поиском:
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')
Ответ 2
Здесь более Pythonic версия простого итеративного решения:
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
Если вы хотите найти n-ое перекрывающееся появление needle
, вы можете увеличить его на 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, и не требует дополнительной памяти для разделяющей версии или импорта модуля регулярных выражений. Он также придерживается нескольких правил в Zen of python, в отличие от различных подходов re
:
- Простой лучше, чем сложный.
- Плоский лучше, чем вложенный.
- Показатели удобочитаемости.
Ответ 3
Это найдет второе вхождение подстроки в строку.
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)
Ответ 4
Понимание того, что регулярное выражение не всегда является лучшим решением, я бы, вероятно, использовал его здесь:
>>> 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
Ответ 5
Я предлагаю некоторые результаты сравнительного анализа, сравнивающие наиболее известные подходы, представленные до сих пор, а именно @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
Конечно, производительность важна, если строка большая, поэтому предположим, что мы хотим найти 1000001st новую строку ('\n') в 1,3-Гбайт файле под названием "bigfile". Чтобы сохранить память, мы хотели бы работать с объектным представлением 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)
Уже существует первая проблема с findnth()
, так как объекты mmap.mmap
не поддерживают split()
. Поэтому нам действительно нужно скопировать весь файл в память:
In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s
Ой! К счастью, s
по-прежнему вписывается в 4 ГБ памяти моего Macbook Air, поэтому давайте проверим 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()
, которая уже второй раз скопировала 1,3 ГБ данных вокруг после s = mm[:]
. Здесь второе преимущество find_nth()
: мы можем использовать его непосредственно на mm
, так что требуются нулевые копии файла:
In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop
Похоже, что на mm
vs. s
наблюдается небольшое снижение производительности, но это показывает, что find_nth()
может получить ответ в 1,2 с по сравнению с findnth
всего 47 с.
Я не обнаружил случаев, когда подход на основе str.find()
был значительно хуже, чем подход на основе str.split()
, поэтому на этом этапе я бы сказал, что ответ @tgamblin или @Mark Byers должен быть принят вместо @bobince's.
В моем тестировании версия 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 между операциями in-memory и mmapped. Интересно также, что _find_nth2()
, основанный на библиотечной функции string.h
memchr()
, теряет прямолинейную реализацию в _find_nth()
: дополнительные "оптимизации" в memchr()
, по-видимому, обходятся...
В заключение, реализация в findnth()
(на основе str.split()
) на самом деле является плохой идеей, поскольку (а) она ужасно работает для больших строк из-за требуемого копирования и (б)
он вообще не работает с объектами mmap.mmap
. Реализация в find_nth()
(на основе str.find()
) должна быть предпочтительной при любых обстоятельствах (и, следовательно, быть принятым ответом на этот вопрос).
По-прежнему существует довольно много возможностей для улучшения, поскольку расширение C выполнялось почти в 4 раза быстрее, чем чистый код Python, что указывает на то, что может быть случай для выделенной библиотеки Python.
Ответ 6
Я бы, наверное, сделал что-то подобное, используя функцию 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)
Это не особенно Pythonic, я думаю, но это просто. Вы могли бы сделать это, используя рекурсию:
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)
Это функциональный способ его решения, но я не знаю, делает ли это более Pythonic.
Ответ 7
Простейший способ?
text = "This is a test from a test ok"
firstTest = text.find('test')
print text.find('test', firstTest + 1)
Ответ 8
Здесь другая версия re
+ itertools
, которая должна работать при поиске либо str
, либо RegexpObject
. Я буду свободно признавать, что это, вероятно, чрезмерно спроектировано, но по какой-то причине оно развлекало меня.
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
Ответ 9
Вот еще один подход, использующий 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()
Ответ 10
>>> 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
Ответ 11
Это даст вам массив начальных индексов для совпадений с 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)
Ответ 12
Настроить ответ на modle13, но без зависимости от модуля 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]
Ответ 13
# 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
Ответ 14
Замена одного вкладыша велик, но работает только потому, что XX и bar имеют одинаковый lentgh
Хороший и общий def будет:
def findN(s,sub,N,replaceString="XXX"):
return s.replace(sub,replaceString,N-1).find(sub) - (len(replaceString)-len(sub))*(N-1)
Ответ 15
Предоставление другого "сложного" решения, использующего split
и join
.
В вашем примере мы можем использовать
len("substring".join([s for s in ori.split("substring")[:2]]))
Ответ 16
Это ответ, который вы действительно хотите:
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
Ответ 17
Решение без использования петель и рекурсии.
Используйте требуемый шаблон в методе компиляции и введите желаемое вхождение в переменную 'n', и последний оператор выведет начальный индекс 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])
Ответ 18
Вот мое решение для поиска n
го вхождения b
в строку a
:
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
Ответ 19
Как насчет:
c = os.getcwd().split('\\')
print '\\'.join(c[0:-2])