web-dev-qa-db-ja.com

「for-loop」の考え方から離れるにはどうすればよいですか?

これはやや概念的な質問ですが、これについていくつかの良いアドバイスが得られることを望んでいました。私が行うプログラミングの多くは( NumPy )配列を使用しています。多くの場合、サイズが異なる2つ以上の配列の項目を一致させる必要があります。最初に行くのはforループ、さらに悪いことにネストされたforループです。 forループは遅いので(少なくともPythonでは)、できる限り避けたいです。

私はNumPyで多くのことを研究する必要がある定義済みのコマンドがあることを知っていますが、あなたは(より経験豊富なプログラマーとして)頭に浮かぶ一般的な思考プロセスを持っていますか?何かを繰り返す必要があるときは?

だから私はしばしばこのようなものを持っています、それはひどいのでそれを避けたいです:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

特にこれを実現する方法はいくつかありますが、もしあれば、一般的な考え方に興味があります。

80
turnip

NumPy を効果的に使用することを学ぶ場合、これは一般的な概念上の困難です。通常、Pythonでのデータ処理は、iteratorsの観点で最もよく表現され、メモリ使用量を低く保ち、機会を最大化します。 I/Oシステムとの並列処理、およびアルゴリズムの一部の再利用と組み合わせを提供します。

しかし、NumPyはこれらすべてを裏返します。最適なアプローチは、アルゴリズムを配列全体の操作のシーケンスとして表現し、費やす時間を最小限に抑えることです。遅いPython=インタプリタで、高速にコンパイルされたNumPyルーチンで費やされる時間を最大化します。

ここに私が取る一般的なアプローチがあります:

  1. 関数の元のバージョン(正しいと確信しているもの)を保持して、正確性と速度の両方について、改善されたバージョンに対してテストできるようにします。

  2. 内側から外側に向かって作業します。つまり、最も内側のループから始めて、ベクトル化できるかどうかを確認します。その後、それを終えたら、1つ上のレベルに移動して続行します。

  3. NumPyのドキュメント をよく読んでください。そこには多くの関数と操作があり、それらは常に見事に名前が付けられているわけではないので、それらを知る価値があるでしょう。特に、「こういうことをする機能があったら」と思ったら、10分かけて探してみる価値は十分あります。通常はどこかにあります。

練習に代わるものはないので、いくつか問題の例を挙げましょう。各問題の目標は、完全にベクトル化されるように関数を書き換えることです:つまり、全体でNumPy操作のシーケンスで構成されます配列、ネイティブPythonループなし(forまたはwhileステートメントなし、イテレータまたは内包なし)).

問題1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

問題2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

問題3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

下のネタバレ。私のソリューションを見る前に自分で試してみると、最高の結果が得られます!

回答1

np.sum(x)* np.sum(y)

回答2

np.sum(np.searchsorted(np.sort(x)、y))

回答3

np.where(x ==行方不明、値、x)

90
Gareth Rees

物事をより速くするためには、データ構造を読み、適切なものを使用する必要があります。

小さな配列と大きな配列(たとえば、small = 100要素とbig = 10.000要素)の重要なサイズの場合、小さな配列を並べ替えてから、大きな配列を反復処理し、バイナリ検索を使用して一致する要素を見つけます小さな配列で。

これは、最大の時間の複雑さO(N log N)になります(そして、小さな小さな配列と非常に大きな大きな配列の場合、ネストされたループソリューションがあるO(N))に近くなりますO(N ^ 2)

しかしながら。どのデータ構造が最も効率的かは、実際の問題に大きく依存します。

8
Pieter B