これはかなり些細なことのように思えますが、私はPythonに不慣れで、最もPython的な方法でそれを実行したいと考えています。
文字列内のn番目の部分文字列に対応するインデックスを検索したい。
私がやりたいことと同等のものがあるはずです
mystring.find("substring", 2nd)
Pythonでこれをどのように実現できますか?
これはかなり些細なことのように思えますが、私はPythonに不慣れで、最もPython的な方法でそれを実行したいと考えています。
文字列内のn番目の部分文字列に対応するインデックスを検索したい。
私がやりたいことと同等のものがあるはずです
mystring.find("substring", 2nd)
Pythonでこれをどのように実現できますか?
回答:
マークの反復的なアプローチが通常の方法だと思います。
文字列分割を使用した代替方法を次に示します。これは、関連するプロセスを見つけるのに役立つことがよくあります。
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')
.rfind('XXX')
、'XXX'
とにかく入力に後で現れるとそれはばらばらになります。
これは、単純な反復ソリューションのより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
インクリメントできます。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
。
これにより、string内で2番目に出現するsubstringが検出されます。
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
、部分文字列の出現回数よりも少ない場合に対処するように調整する必要があります。(この場合、戻り値はすべての出現位置を定期的に循環します)。
正規表現が常に最良の解決策であるとは限らないことを理解して、私はおそらくここでそれを使用します:
>>> 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
(m.start() for m in re.finditer(r"ab",s))[2]
itertools.islice
関数を使用して同様の醜い解決策が可能です:next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
@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.3 GBのファイルで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.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
、私のMacbook Airの4 GBのメモリに収まるので、ベンチマークを行ってみましょう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 GBのデータを後でコピーした2回目s = mm[:]
です。ここに2番目の利点があります:ファイルのコピーが必要ないようfind_nth()
にmm
直接使用できます。
In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop
そこには小さなパフォーマンスペナルティ動作しているように見えるmm
対s
が、これはそれが示すfind_nth()
に比べて、私たちに1.2秒で答えを得ることができるfindnth
47秒のの合計。
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.h
memchr()
_find_nth()
memchr()
結論として、findnth()
(に基づくstr.split()
)での実装は、(a)必要なコピーのために大きな文字列に対してひどく実行され、(b)mmap.mmap
オブジェクトに対してまったく機能しないため、本当に悪い考えです。find_nth()
(に基づくstr.find()
)での実装は、すべての状況で優先される必要があります(したがって、この質問への回答として受け入れられます)。
C拡張機能は、純粋なPythonコードよりも4倍近い速度で実行され、専用のPythonライブラリ関数の場合があることを示しているため、まだ改善の余地はかなりあります。
インデックスパラメータを取る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になるかどうかはわかりません。
for _ in xrange(n):
代わりに使用できますwhile n: ... n-=1
return find_nth(s, x, n - 1, i + 1)
する必要がありますreturn find_nth(s, x, n - 1, i + len(x))
。大したことではありませんが、計算時間を節約できます。
次に、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
>>> 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
# 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
find_nth('aaa', 'a', 0)
戻る1
はずですが戻り0
ます。あなたのような何かを必要とi = s.find(substr, i) + 1
してから返しますi - 1
。
ループと再帰を使用しないソリューション。
コンパイル方法で必要なパターンを使用し、変数'n'に目的の出現を入力すると、最後のステートメントで、指定された文字列のパターンのn番目の出現の開始インデックスが出力されます。ここでfinditer、つまりiteratorの結果はリストに変換され、n番目のインデックスに直接アクセスしています。
import re
n=2
sampleString="this is history"
pattern=re.compile("is")
matches=pattern.finditer(sampleString)
print(list(matches)[n].span()[0])
ライナーの交換は素晴らしいですが、XXとバーが同じ長さであるため機能します
良い一般的な定義は次のとおりです。
def findN(s,sub,N,replaceString="XXX"):
return s.replace(sub,replaceString,N-1).find(sub) - (len(replaceString)-len(sub))*(N-1)
これが文字列の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
文字の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の回答から導き出され、単一の文字の場合に簡略化されています。
どうですか:
c = os.getcwd().split('\\')
print '\\'.join(c[0:-2])