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


79

これはかなり概念的な質問ですが、これについては良いアドバイスが得られることを望んでいました。私が行うプログラミングの多くは(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]

特にこれを達成するための複数の異なる方法があることを知っていますが、もしあれば、一般的な考え方に興味があります。


10
関数型プログラミングを探しています:ラムダ式、高階関数、式の生成など。
キリアンフォス14

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).ここで間違った問題を解決しているように聞こえます。何かを繰り返す必要がある場合は、何かを繰り返す必要があります。どのPythonコンストラクトを使用しても、同様のパフォーマンスヒットが発生します。コードが遅い場合、forループがあるからではありません。不要な作業を行っているか、C側で実行できるPython側で作業を行っているためです。あなたの例では、余分な仕事をしています。2つではなく1つのループで実行できます。
ドーバル14

24
残念ながらない@Doval - numpyの中で。要素ごとの加算を実行するループ用のPythonは、現実的な配列サイズのために、ベクトル化されたNumPy演算子(Cで書かれているだけでなく、SSE命令やその他のトリックを使用)よりも数倍簡単に遅くなります。

46
上記のコメントの一部は、質問を誤解しているようです。NumPyでプログラミングする場合、計算ベクトル化できれば最高の結果が得られます。つまり、Pythonの明示的なループをNumPyの配列全体の操作に置き換えます。これは、Pythonでの通常のプログラミングとは概念的に非常に異なり、学習に時間がかかります。だから私は、OPがそれを行う方法を学ぶ方法についてアドバイスを求めるのは合理的だと思う。
ガレス・リース

3
@PieterB:はい、そうです。「ベクトル化」は「最適なアルゴリズムの選択」と同じではありません。それらは、効率的な実装を考え出すのに困難な2つの別個の原因であるため、一度に1つずつ検討するのが最善です。
ガレス・リース

回答:


89

これは、NumPyを効果的に使用することを学ぶときの一般的な概念上の困難です。通常、Pythonでのデータ処理は、メモリー使用量を低く抑え、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 == missing、value、x)


待って、最後の答えにタイプミスがありますか、それともNumPyはPythonがコードを解釈する方法を変更しますか?
イズカタ14

1
@Izkataそれ自体は何も変更しませんが、配列に適用される論理演算はブール配列を返すように定義されています。
サピ14

ああ@sapi、私はそれらが普通だと思っていた、のdoctestで何が起こっているか逃したlist
Izkata

たぶんAPLを埋め込む方法があるはずですか?

宿題をする方法が大好きです。
コライトゥゲイ

8

処理を高速化するには、データ構造を調べて適切なものを使用する必要があります。

小さな配列と大きな配列の自明でないサイズ(例えば、small = 100要素とbig = 10.000要素)の場合、それを行う1つの方法は、小さな配列をソートしてから、big-arrayを反復処理し、バイナリ検索を使用して一致する要素を見つけることです小さな配列で。

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

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


-3

辞書を使用してパフォーマンスを大幅に最適化できます

これは別の例です:

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w

2
これは、明らかに「for-loop」の考え方をまだ使用していることです。
8ビットツリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.