Я думаю, что итеративный подход Марка был бы обычным способом.
Вот альтернатива с разделением строк, которое часто может быть полезно для поиска связанных процессов:
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')
Для особого случая, где Вы ищете n'th происшествие символа (т.е. подстрока длины 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 ответ и упрощено для случая отдельного символа.
>>> 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
версия, которая должна работать при поиске либо 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
Я бы, наверное, сделал что-то вроде этого, используя функцию 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)
Это функциональный способ решить эту проблему, но я не знаю, делает ли это его более питоническим.
Понимая, что регулярное выражение - не всегда лучшее решение, я бы, вероятно, использовал его здесь:
>>> 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
Вот более питоническая версия простого итеративного решения:
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-е перекрывающееся вхождение иглы
, вы можете увеличить на 1
вместо len (игла)
, например:
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
Это легче читать, чем версию Марка, и она не требует дополнительной памяти для версии разделения или импорта модуля регулярных выражений. Он также придерживается некоторых правил Zen of python , в отличие от различных re
подходов:
Вот еще один подход с использованием 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()