Найдите энное возникновение подстроки в строке

107
задан Todd Gamblin 10 December 2009 в 21:51
поделиться

8 ответов

Я думаю, что итеративный подход Марка был бы обычным способом.

Вот альтернатива с разделением строк, которое часто может быть полезно для поиска связанных процессов:

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')
58
ответ дан 24 November 2019 в 03:40
поделиться

Для особого случая, где Вы ищете 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 ответ и упрощено для случая отдельного символа.

0
ответ дан 24 November 2019 в 03:40
поделиться
>>> 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
1
ответ дан 24 November 2019 в 03:40
поделиться

Вот еще 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
2
ответ дан 24 November 2019 в 03:40
поделиться

Я бы, наверное, сделал что-то вроде этого, используя функцию 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)

Это функциональный способ решить эту проблему, но я не знаю, делает ли это его более питоническим.

6
ответ дан 24 November 2019 в 03:40
поделиться

Понимая, что регулярное выражение - не всегда лучшее решение, я бы, вероятно, использовал его здесь:

>>> 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
19
ответ дан 24 November 2019 в 03:40
поделиться

Вот более питоническая версия простого итеративного решения:

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 подходов:

  1. Простой лучше, чем сложный.
  2. Плоский лучше, чем вложенный .
  3. Важность считывания.
66
ответ дан 24 November 2019 в 03:40
поделиться

Вот еще один подход с использованием 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() 
1
ответ дан 24 November 2019 в 03:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: