문자열에서 n 번째 부분 문자열 찾기


118

이것은 매우 사소한 것처럼 보이지만 저는 Python을 처음 접했고 가장 Pythonic 방식으로하고 싶습니다.

문자열 내에서 하위 문자열의 n 번째 발생에 해당하는 인덱스를 찾고 싶습니다.

내가하고 싶은 것과 동등한 것이 있어야합니다.

mystring.find("substring", 2nd)

파이썬에서 어떻게 이것을 달성 할 수 있습니까?


7
n 번째 문자열을 찾으십니까? 나는 그것이 n 번째 발생의 색인을 의미한다고 가정합니까?
Mark Byers

2
네, n 번째 발생의 인덱스
prestomation

9
일치하는 항목이 겹치는 경우 어떻게해야합니까? find_nth ( 'aaaa', 'aa', 2)는 1 또는 2를 반환해야합니까?
Mark Byers

예! 문자열에서 하위 문자열의 n 번째 발생을 찾고 하위 문자열의 n 번째 발생에서 문자열을 분할 할 무언가가 있어야합니다.
리멘

답변:


69

Mark의 반복적 인 접근 방식은 일반적인 방법이라고 생각합니다.

다음은 관련 프로세스를 찾는 데 유용 할 수있는 문자열 분할의 대안입니다.

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')

7
첫 번째 제안은 관심있는 일치 항목이 거의 시작될 때 큰 문자열에 대해 매우 비효율적입니다. 항상 전체 문자열을 봅니다. 영리하지만 파이썬을 처음 접하고 좋은 방법을 배우고 싶은 사람에게는 이것을 권장하지 않습니다.
Mark Byers

3
고마워요. 나는 그것이 세상에서 가장 즉시 읽을 수있는 것이라고 생각하지 않지만, 아래에있는 대부분의 다른 것보다 훨씬 나쁘지는 않다
prestomation

1
한 줄에 +1하면 지금 도움이 될 것입니다. 나는에 상응하는 것을 생각하고 .rfind('XXX')있었지만 'XXX'어쨌든 나중에 입력에 나타나면 붕괴 될 것 입니다.
Nikhil Chelliah 2010-07-07

이 함수는 n = 0, 1, 2, 3, ...라고 가정합니다. n = 1, 2, 3, 4, ...라고 가정하면 좋을 것입니다.
Happy

75

다음은 간단한 반복 솔루션의 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

의 n 번째 겹치는 항목 을 찾으려면 다음과 같이 대신 needle증가 할 수 있습니다 .1len(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.

  1. 단순한 것이 복잡한 것보다 낫습니다.
  2. 플랫이 중첩보다 낫습니다.
  3. 가독성이 중요합니다.

이것은 문자열로 할 수 있습니까? find_nth (df.mystring.str, ( 'x'), 2)처럼 'x'의 두 번째 인스턴스의 위치를 ​​찾으려면?
Arthur D. Howland

36

문자열에서 두 번째 하위 문자열을 찾습니다.

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)

일반적으로 n 번째 요소를 찾기 위해 확장 할 수 있습니까?
ifly6

이 최고의 응답 IMHO, 나는 특별한 경우를위한 작은 추가를 만들어 여기서 n = 0
월 Wilmans

간결하게 게시물을 편집하고 싶지 않았습니다. 하지만 n = 0은 특별한 경우로 취급해야한다는 점에 동의합니다.
Sriram Murali

n하위 문자열의 발생 횟수보다 적은 경우를 처리하도록 조정해야합니다 . (이 경우 반환 값은 모든 발생 위치를 주기적으로 순환합니다).
coldfix

29

정규식이 항상 최선의 해결책은 아니라는 것을 이해하고 여기에서 사용할 것입니다.

>>> 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

4
물론 여기서 위험은 검색 할 문자열에 정규식이 원하지 않는 작업을 수행하게하는 특수 문자가 포함된다는 것입니다. re.escape를 사용하면이 문제를 해결할 수 있습니다.
Mark Byers

1
이것은 영리하지만 정말 Pythonic입니까? 부분 문자열의 n 번째 항목을 찾는 것만으로도 과잉처럼 보이며 읽기가 쉽지 않습니다. 또한 말씀하신 것처럼이 모든 re를 가져와야합니다
Todd Gamblin

대괄호를 사용하면 전체 목록을 생성하도록 Python에 지시합니다. 둥근 대괄호는 첫 번째 요소 만 반복하므로 더 효과적입니다.(m.start() for m in re.finditer(r"ab",s))[2]
emu

1
@emu 아니요, 게시 한 내용이 작동하지 않습니다. 당신은 발전기의 색인을 가져올 수 없습니다.
Mark Amery

@MarkAmery 죄송합니다! 이 코드를 게시 한 이유가 놀랍습니다. 그래도 다음 itertools.islice함수를 사용하면 비슷하고 추한 솔루션이 가능 합니다.next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
emu

17

지금까지 제시된 가장 눈에 띄는 접근 방식, 즉 @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.3GB 파일에서 1000001 번째 줄 바꿈 ( '\ 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)

객체가를 지원하지 않기 findnth()때문에 이미 첫 번째 문제가 있습니다. 따라서 실제로 전체 파일을 메모리에 복사해야합니다.mmap.mmapsplit()

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()이후에 1.3GB의 데이터를 복사 한 것은 이미 두 번째 인 동안 문자열을 복사해야한다는 것입니다 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

mms에서 작동하는 약간의 성능 저하가있는 것으로 보이지만 이는 의 총 47 초에 find_nth()비해 1.2 초 안에 답을 얻을 수 있음을 보여줍니다 findnth.

str.find()기반 접근 방식이 기반 접근 방식보다 훨씬 더 나쁜 경우를 발견하지 못 str.split()했으므로이 시점에서 @bobince 대신 @tgamblin 또는 @Mark Byers의 답변을 수락해야한다고 주장합니다.

내 테스트에서 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

분명히 꽤 더 빠릅니다. 흥미롭게도 인 메모리 케이스와 mmapped 케이스 사이의 C 레벨에는 차이가 없습니다. 의 라이브러리 기능을 _find_nth2()기반으로 하는 , 의 간단한 구현에 대해 잃는 것도 흥미 롭습니다 .의 추가 "최적화" 는 분명히 역효과를냅니다 ...string.hmemchr()_find_nth()memchr()

결론적으로 findnth()(기반 str.split()) 의 구현은 ( a) 필요한 복사로 인해 더 큰 문자열에 대해 끔찍하게 수행되고 (b) mmap.mmap객체에서 전혀 작동하지 않기 때문에 정말 나쁜 생각 입니다. find_nth()(기반 str.find()) 의 구현은 모든 상황에서 선호되어야합니다 (따라서이 질문에 대한 대답이 허용됨).

C 확장은 순수한 Python 코드보다 거의 4 배 더 빠르게 실행되어 전용 Python 라이브러리 함수에 대한 사례가있을 수 있으므로 개선의 여지가 여전히 많이 있습니다.


8

가장 간단한 방법?

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

firstTest = text.find('test')

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

나는 이것이 다른 솔루션에 비해 상당히 성능이 좋다고 상상할 수 있습니다.
Rotareti

7

색인 매개 변수를 사용하는 찾기 함수를 사용하여 다음과 같이 할 수 있습니다.

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하게 만드는지 모르겠습니다.


1
for _ in xrange(n):대신 사용할 수 있습니다.while n: ... n-=1
jfs

@JF Sebastian : 네, 좀 더 파이썬적인 것 같아요. 업데이트하겠습니다.
Mark Byers

BTW : xrange는 더 이상 Python 3에서 필요하지 않습니다 : diveintopython3.org/…
Mark Byers

1
return find_nth(s, x, n - 1, i + 1)이어야합니다 return find_nth(s, x, n - 1, i + len(x)). 큰 문제는 아니지만 계산 시간을 절약합니다.
Dan Loewenherz 2009

@dlo : 실제로 어떤 경우에는 다른 결과를 줄 수 있습니다 : find_nth ( 'aaaa', 'aa', 2). 내 것은 1, 당신의 것은 2를 준다. 나는 당신의 것이 실제로 포스터가 원하는 것이라고 생각한다. 코드를 업데이트하겠습니다. 댓글 주셔서 감사합니다.
Mark Byers

3

다음과 일치하는 시작 인덱스 배열을 제공합니다 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)

2

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() 

2

a 또는 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

2

바탕 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]

1
>>> 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

사용하는 또 다른 "까다로운"솔루션을 제공 split하고 join.

귀하의 예에서 우리는

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

1
# 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

설명이 필요합니다
Ctznkane525

find_nth('aaa', 'a', 0)반환 1해야하는 동안 반환 0합니다. 당신은 같은 것을 필요로 i = s.find(substr, i) + 1하고 수익을 i - 1.
a_guest

1

루프와 재귀를 사용하지 않는 솔루션입니다.

컴파일 방법에서 필요한 패턴을 사용하고 변수 '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])

1

문자의 n 번째 발생을 검색하는 특수한 경우 (즉, 길이가 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의 대답 에서 파생되었으며 단일 문자의 경우 단순화되었습니다.


이것은 아름답다.
Hafiz Hilman Mohammad Sofian

0

하나의 라이너 교체는 훌륭하지만 XX와 바의 길이가 같기 때문에 작동합니다.

좋고 일반적인 정의는 다음과 같습니다.

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

0

이것이 당신이 정말로 원하는 대답입니다.

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

0

다음은 문자열 n의 발생 을 찾는 솔루션입니다 .ba

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

0

데프 :

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'

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.