Bir dizedeki alt dizenin n. Oluşumunu bulun


118

Bu oldukça önemsiz gibi görünüyor, ancak Python'da yeniyim ve bunu en Pythonic yolla yapmak istiyorum.

Bir dizge içindeki bir alt dizenin n'ci oluşumuna karşılık gelen dizini bulmak istiyorum.

Yapmak istediğim şeye eşdeğer bir şey olmalı ki

mystring.find("substring", 2nd)

Bunu Python'da nasıl başarabilirsin?


7
Dizenin n'inci oluşumunu buldunuz mu? Sanırım n'inci oluşum dizini anlamına mı geliyor?
Mark Byers

2
Evet, n'inci
oluşum

9
Çakışan eşleşmeler varsa ne olmalı? Find_nth ('aaaa', 'aa', 2) 1 veya 2 döndürmeli mi?
Mark Byers

Evet! Bir dizedeki bir alt dizenin n'inci oluşumunu bulmak ve dizeyi bir alt dizenin n'ci oluşumunda ayırmak için bir şeyler olması gerekir.
Reman

Yanıtlar:


69

Mark'ın yinelemeli yaklaşımı her zamanki gibi olurdu.

Aşağıda, ilgili süreçleri bulmak için genellikle yararlı olabilecek bir dize bölme alternatifi verilmiştir:

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)

Ve işte hızlı (ve biraz kirli, çünkü iğneye uymayan bazı samanları seçmeniz gerekiyor) tek astar:

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

7
İlk öneri, ilgilendiğiniz maç başlangıca yakın olduğunda büyük diziler için çok verimsiz olacaktır. Her zaman dizenin tamamına bakar. Zekice ama Python'da yeni olan ve bunu yapmanın iyi bir yolunu öğrenmek isteyen birine bunu tavsiye etmem.
Mark Byers

3
Teşekkürler, tek astarını beğendim. Bunun dünyadaki en anlık okunabilir şey olduğunu sanmıyorum, ancak aşağıdaki diğerlerinden çok daha kötü değil
09:58

1
Tek satırlık +1, bu şu anda bana yardımcı olacaktır. Eşdeğeri yapmayı düşünüyordum .rfind('XXX'), ancak 'XXX'yine de girdide daha sonra görünürse, bu parçalanacaktı .
Nikhil Chelliah

Bu işlev, n = 0, 1, 2, 3, ... varsayar. N = 1, 2, 3, 4, ... olduğunu varsaymanız iyi olur.
Mutlu

75

İşte basit yinelemeli çözümün daha Pythonic versiyonu:

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

Misal:

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

N'inci çakışan oluşumunu bulmak needleistiyorsanız, 1bunun yerine şu şekilde artırabilirsiniz 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

Misal:

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

Bu, Mark'ın sürümünden daha kolay okunur ve bölünen sürümün fazladan belleğini veya normal ifade modülünü içe aktarmayı gerektirmez. Ayrıca çeşitli yaklaşımlardan farklı olarak, Python Zen'deki kurallardan birkaçına da bağlıdır re:

  1. Basit, karmaşıktan daha iyidir.
  2. Düz, iç içe olmaktan daha iyidir.
  3. Okunabilirlik önemlidir.

Bu bir dizede yapılabilir mi? Find_nth (df.mystring.str, ('x'), 2) gibi 'x'in 2. oluşumunun konumunu bulmak için mi?
Arthur D. Howland

36

Bu, dizedeki ikinci alt dizeyi bulacaktır.

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

Düzenleme: Performans hakkında fazla düşünmedim, ancak hızlı bir özyineleme, n'inci oluşumu bulmaya yardımcı olabilir:

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)

Bu genellikle n'inci elemanı bulmak için genişletilebilir mi?
ifly6

Bu en iyi cevap IMHO, n = 0 olan özel durum için küçük bir ekleme yaptım
Jan Wilmans 14'19

Kısa olması için gönderiyi düzenlemek istemedim. Yine de n = 0'ın özel bir durum olarak görülmesi gerektiğine katılıyorum.
Sriram Murali

Bu n, alt dizenin oluşumundan daha az sayıda olduğu durumu işlemek için ayarlanmalıdır . (Bu durumda, dönüş değeri tüm oluşum pozisyonları boyunca periyodik olarak döngü yapacaktır).
coldfix

29

Normal ifadenin her zaman en iyi çözüm olmadığını anladığımda, muhtemelen burada bir tane kullanırım:

>>> 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
Elbette buradaki risk, aranacak dizenin, normal ifadenin istemediğiniz bir şeyi yapmasına neden olacak özel karakterler içermesidir. Re.escape kullanmak bunu çözmelidir.
Mark Byers

1
Bu zekice, ama gerçekten Pythonic mi? Bir alt dizenin n. Oluşumunu bulmak için fazla öldürme gibi görünüyor ve okunması tam olarak kolay değil. Ayrıca, sizin de dediğiniz gibi, bunun için tüm yeniden dosyaları içe aktarmanız gerekir
Todd Gamblin

Köşeli parantez kullandığınızda, Python'a tüm listeyi oluşturmasını söylersiniz. Yuvarlak parantezler yalnızca ilk öğeler boyunca yinelenir ve bu daha etkilidir:(m.start() for m in re.finditer(r"ab",s))[2]
emu

1
@emu Hayır, gönderdikleriniz işe yaramayacak; bir jeneratörün indeksini alamazsınız.
Mark Amery

@MarkAmery üzgünüm! Bu kodu neden gönderdiğime oldukça şaşırdım. Yine de, benzer ve çirkin bir çözüm şu itertools.isliceişlevi kullanarak mümkündür :next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
emu

17

Şimdiye kadar sunulan en önemli yaklaşımları, yani @ bobince's findnth()(dayalı str.split()) ile @ tgamblin's veya @Mark Byers ' find_nth()(dayalı str.find()) gibi bazı kıyaslama sonuçları sunuyorum . Ayrıca _find_nth.sone kadar hızlı gidebileceğimizi görmek için bir C uzantısı ( ) ile karşılaştıracağım . İşte 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

Elbette, dizge büyükse performans en çok önemlidir, bu nedenle 1000001. yeni satırı ('\ n') 'bigfile' adlı 1.3 GB'lik bir dosyada bulmak istediğimizi varsayalım. Hafızadan tasarruf etmek için mmap.mmap, dosyanın nesne temsili üzerinde çalışmak istiyoruz :

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)

İle ilk sorun zaten var findnth()olduğundan, mmap.mmapnesneler desteklemez split(). Yani aslında tüm dosyayı belleğe kopyalamalıyız:

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

Ah! Neyse ki shala Macbook Air'imin 4 GB belleğine sığıyor, öyleyse kıyaslama yapalım findnth():

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

Açıkça korkunç bir performans. Temel alınan yaklaşımın nasıl olduğunu görelim str.find():

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

Çok daha iyi! Açıkçası findnth()sorun, dizeyi kopyalamak zorunda kalmasıdır split(), bu da 1.3 GB'lık veriyi ikinci kez kopyaladık s = mm[:]. İşte ikinci avantajı find_nth(): Dosyanın sıfır kopyası gerekecek şekilde mmdoğrudan kullanabiliriz :

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

Buna mmkarşı işleyen küçük bir performans cezası var gibi görünüyor s, ancak bu find_nth()bize findnthtoplam 47 saniyeye kıyasla 1,2 saniyede bir cevap alabileceğimizi gösteriyor .

Temel yaklaşımın temelli yaklaşımdan str.find()önemli ölçüde daha kötü olduğu bir durum bulamadım str.split(), bu nedenle bu noktada @ bobince'nin yerine @ tgamblin'in veya @Mark Byers'ın yanıtının kabul edilmesi gerektiğini savunabilirim.

Testlerimde, find_nth()yukarıdaki sürüm bulabildiğim en hızlı saf Python çözümüydü (@ Mark Byers'ın sürümüne çok benzer). Bakalım bir C genişletme modülü ile ne kadar iyi yapabiliriz. İşte _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);
}

İşte setup.pydosya:

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

İle her zamanki gibi yükleyin python setup.py install. Tek karakter bulmakla sınırlı olduğu için burada C kodu bir avantaj sağlar, ancak bunun ne kadar hızlı olduğunu görelim:

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

Açıkça biraz daha hızlı. İlginç bir şekilde, bellek içi ve genişletilmiş kasalar arasında C düzeyinde bir fark yoktur. Ayrıca, 'nin kütüphane işlevine _find_nth2()dayanan , basit uygulamaya karşı kaybettiğini görmek ilginçtir : içindeki ek "optimizasyonlar" görünüşte geri tepiyor ...string.hmemchr()_find_nth()memchr()

Sonuç olarak, findnth()(dayalı str.split()) içindeki uygulama gerçekten kötü bir fikirdir, çünkü (a) gerekli kopyalama nedeniyle daha büyük dizeler için korkunç bir performans gösterir ve (b) mmap.mmapnesneler üzerinde hiç çalışmaz . İçinde uygulanması find_nth()(dayanarak str.find()) her koşulda tercih edilmelidir (ve dolayısıyla bu sorunun kabul cevabı).

C uzantısı, saf Python kodundan neredeyse 4 kat daha hızlı çalıştığı için, özel bir Python kütüphanesi işlevi için bir durum olabileceğine işaret ettiğinden, iyileştirme için hala epeyce alan var.


8

En basit yol?

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

firstTest = text.find('test')

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

Diğer çözümlere kıyasla bunun da oldukça başarılı olduğunu tahmin edebiliyorum.
Rotareti

7

İndeks parametresi alan bul işlevini kullanarak muhtemelen böyle bir şey yapardım:

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)

Sanırım özellikle Pythonic değil, ama basit. Bunun yerine özyineleme kullanarak yapabilirsiniz:

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)

Bunu çözmenin işlevsel bir yolu, ancak bu onu daha Pythonic yapar mı bilmiyorum.


1
for _ in xrange(n):yerine kullanılabilirwhile n: ... n-=1
jfs

@JF Sebastian: Evet, sanırım bu biraz daha Pythonic. Güncelleyeceğim.
Mark Byers

BTW: xrange artık Python 3'te gerekli değil: diveintopython3.org/…
Mark Byers

1
return find_nth(s, x, n - 1, i + 1)olmalıdır return find_nth(s, x, n - 1, i + len(x)). Çok önemli değil, ancak hesaplama süresinden tasarruf sağlıyor.
Dan Loewenherz

@dlo: Aslında bu bazı durumlarda farklı sonuçlar verebilir: find_nth ('aaaa', 'aa', 2). Benimki 1 verir, seninki 2 verir. Sanırım seninki aslında posterin istediği şey. Kodumu güncelleyeceğim. Yorum için teşekkürler.
Mark Byers

3

Bu size aşağıdakiler için eşleşmeler için bir başlangıç ​​indeksleri dizisi verecektir yourstring:

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

O zaman n'inci girişiniz şöyle olur:

n = 2
nth_entry = indices[n-1]

Elbette indeks sınırlarına dikkat etmelisiniz. Bunun yourstringgibi örneklerin sayısını alabilirsiniz :

num_instances = len(indices)

2

İşte re.finditer kullanan başka bir yaklaşım.
Aradaki fark, bunun yalnızca samanlığa gerektiği kadar bakmasıdır.

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

İşte a veya a ararken çalışması gereken başka bir re+ itertoolssürümü . Bunun muhtemelen fazla tasarlanmış olduğunu özgürce itiraf edeceğim, ancak nedense beni eğlendirdi.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

Bina modle13 'ın cevabı, ancak olmadan remodül bağımlılık.

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

Bunun bir yerleşik dize yöntemi olmasını diliyorum.

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

splitVe kullanan başka bir "zor" çözüm sunmak join.

Örneğinizde kullanabiliriz

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

bir açıklama gerekiyor
Ctznkane525

find_nth('aaa', 'a', 0)döndürür 1o dönmelidir iken 0. Gibi bir şeye ihtiyacın var i = s.find(substr, i) + 1ve sonra geri dön i - 1.
a_guest

1

Döngüler ve özyineleme kullanmadan çözüm.

Derleme yönteminde gerekli kalıbı kullanın ve 'n' değişkenine istenen oluşumu girin ve son ifade, verilen dizedeki modelin n. Oluşumunun başlangıç ​​dizinini yazdıracaktır. Burada bulucunun yani yineleyicinin sonucu listeye dönüştürülmekte ve doğrudan n'inci dizine erişilmektedir.

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

1

Bir karakterin n'inci oluşumunu aradığınız özel durum için (yani uzunluk 1'in alt dizesi), aşağıdaki işlev verilen karakterin tüm oluşum konumlarının bir listesini oluşturarak çalışır:

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]

nVerilen karakterin oluşumundan daha azı varsa, verecektir IndexError: list index out of range.

Bu Zv_oDD en @ türetilmiştir cevap ve tek karakterin durumu için basitleştirilmiş.



0

Bir astarı değiştirmek harika ancak yalnızca XX ve çubuk aynı lentgh'e sahip olduğu için çalışıyor

İyi ve genel bir tanım şudur:

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

0

Gerçekten istediğin cevap bu:

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

İşte dizedeki noluşumunu bulmak için benim çözümüm :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)

Saf Python ve yinelemelidir. 0 veya nbu çok büyükse -1 döndürür. Tek astarlıdır ve doğrudan kullanılabilir. İşte bir örnek:

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

0

def:

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

Kullanmak:

get_first_N_words('  One Two Three Four ' , 3)

Çıktı:

'One Two Three'

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.