web-dev-qa-db-ja.com

文字列スライスの時間の複雑さ

Python文字列をスライスするときの時間はどれくらいですか?Python文字列は不変であることを考えると、O(1)またはO(n)スライスの実装方法によって異なります。

(潜在的に大きい)文字列のすべてのサフィックスを反復する関数を記述する必要があります。文字列全体のタプルと文字の読み取りを開始するためのインデックスとしてサフィックスを表すことにより、文字列のスライスを回避できましたが、それは醜いです。代わりに、私は単純に次のように自分の関数を書きます:

_def do_something_on_all_suffixes(big_string):
    for i in range(len(big_string)):
        suffix = big_string[i:]
        some_constant_time_operation(suffix)
_

...時間の複雑さはO(n)またはO(n2)になります。ここで、nlen(big_string)ですか?

23
Mark Amery

短い答え:strスライス、一般的にはコピー。つまり、文字列のnサフィックスごとにスライスを実行する関数は、O(n2)の機能を果たしています。つまり、 bytessを使用して元のバイトデータのゼロコピービューを取得する を使用してmemoryviewのようなオブジェクトを操作できる場合は、コピーを回避できます。機能させる方法については、以下のゼロコピースライスを実行する方法を参照してください。

長い答え:(C)Python strデータのサブセットのビューを参照してスライスしないでください。 strスライスには、正確に3つの操作モードがあります。

  1. 完全なスライス。 _mystr[:]_:まったく同じstrへの参照を返します(共有データだけでなく、同じ実際のオブジェクト、_mystr is mystr[:]_ strは不変なので、そうすること)
  2. 長さゼロのスライスと(実装に依存する)キャッシュされた長さ1のスライス。空の文字列はシングルトン(_mystr[1:1] is mystr[2:2] is ''_)であり、長さが1の低い序数の文字列もキャッシュされたシングルトンです(CPython 3.5.0では、latin-1で表現できるすべての文字、つまりrange(256)、キャッシュされます)
  3. 他のすべてのスライス:スライスされたstrは作成時にコピーされ、その後、元のstrとは無関係になります。

#3が一般的なルールである理由は、大きなstrがメモリの一部のビューによってメモリに保持される問題を回避するためです。 1 GBのファイルがある場合は、それを読み込んでスライスします(そうです、シークできると無駄になります。これは説明のためです)。

_with open(myfile) as f:
    data = f.read()[-1024:]
_

その後、最後の1 KBを表示するビューをサポートするために、メモリに1 GBのデータが保持されますが、これは重大な無駄です。スライスは通常小さめなので、ビューを作成する代わりにスライスをコピーする方がほとんど常に高速です。また、strの方が簡単な場合もあります。サイズを知る必要がありますが、データへのオフセットも追跡する必要はありません。

ゼロコピースライスを行う方法

Pythonでビューベースのスライスを実行する方法はありあり、Python 2では、strstrはPython 2で バッファプロトコル をサポートしているため、バイトのようであるため)。Py2strおよびPy3 bytes(およびbytearray、_array.array_、numpy配列、_mmap.mmap_ sなどの他の多くのデータ型)を作成できます。 a memoryviewつまり、元のオブジェクトのゼロコピービュー であり、データをコピーせずにスライスできます。したがって、Py2に使用(またはエンコード)できる場合str/Py3 bytes、そしてあなたの関数は任意のbytesのようなオブジェクトで動作することができます、そしてあなたはそうすることができます:

_def do_something_on_all_suffixes(big_string):
    # In Py3, may need to encode as latin-1 or the like
    remaining_suffix = memoryview(big_string)
    # Rather than explicit loop, just replace view with one shorter view
    # on each loop
    while remaining_suffix:  # Stop when we've sliced to empty view
        some_constant_time_operation(remaining_suffix)
        remaining_suffix = remaining_suffix[1:]
_

memoryviewsのスライスは、新しいビューオブジェクトを作成します(それらは、表示するデータの量とは無関係に固定サイズで超軽量です)。データはないため、_some_constant_time_operation_はコピーを格納できます必要に応じて、後でスライスしても変更されません。 Py2 str/Py3 bytesとして適切なコピーが必要な場合は、.tobytes()を呼び出して、未加工のbytes objを取得するか、(Py3ではそれのみ)表示)、それをバッファからコピーするstrに直接デコードします。例: str(remaining_suffix[10:20], 'latin-1')

33
ShadowRanger

それはすべて、スライスの大きさに依存します。以下の2つのベンチマークをまとめました。 1つ目は文字列全体をスライスし、2つ目は少しだけスライスします。 このツール を使用した曲線近似により、

# s[1:-1]
y = 0.09 x^2 + 10.66 x - 3.25

# s[1:1000]
y = -0.15 x + 17.13706461

最初のものは、4MBまでの文字列のスライスに対して非常に線形に見えます。これは、2番目の文字列の作成にかかる時間を本当に測定していると思います。 2番目はかなり一定ですが、非常に高速であるため、おそらくそれほど安定していません。

enter image description hereenter image description here

import time

def go(n):
    start = time.time()
    s = "abcd" * n
    for j in xrange(50000):

        #benchmark one
        a = s[1:-1]

        #benchmark two
        a = s[1:1000]

    end = time.time()
    return (end - start) * 1000

for n in range(1000, 100000, 5000):
    print n/1000.0, go(n)
4
jozxyqk