Bagaimana cara saya pindah dari aliran pemikiran "for-loop"?

79

Ini adalah pertanyaan yang agak konseptual, tetapi saya berharap saya bisa mendapatkan saran yang bagus tentang ini. Banyak pemrograman yang saya lakukan adalah dengan array ( NumPy ); Saya sering harus mencocokkan item dalam dua atau lebih array yang ukurannya berbeda dan hal pertama yang saya tuju adalah for-loop atau bahkan lebih buruk, nested for-loop. Saya ingin menghindari for-loop sebanyak mungkin, karena mereka lambat (setidaknya dengan Python).

Saya tahu bahwa untuk banyak hal dengan NumPy ada perintah-perintah yang sudah ditentukan sebelumnya yang perlu saya teliti, tetapi apakah Anda (sebagai programmer yang lebih berpengalaman) memiliki proses pemikiran umum yang muncul di benak Anda ketika Anda harus mengulangi sesuatu?

Jadi saya sering memiliki sesuatu seperti ini, yang mengerikan dan saya ingin menghindarinya:

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]

Saya tahu ada beberapa cara berbeda untuk mencapai hal ini secara khusus, tetapi saya tertarik pada metode berpikir umum, jika ada.

lobak
sumber
10
Anda sedang mencari pemrograman fungsional : ekspresi lambda, fungsi tingkat tinggi, menghasilkan ekspresi dll. Google itu.
Kilian Foth
42
I want to avoid for-loops as much as possible because they are slow (at least in Python).Sepertinya Anda menyelesaikan masalah yang salah di sini. Jika Anda perlu mengulangi sesuatu, Anda perlu mengulangi sesuatu; Anda akan mendapatkan hit kinerja yang sama tidak peduli konstruk Python mana yang Anda gunakan. Jika kode Anda lambat, itu bukan karena Anda memiliki forloop; itu karena Anda melakukan pekerjaan yang tidak perlu atau melakukan pekerjaan di sisi Python yang bisa dilakukan di sisi C. Dalam contoh Anda, Anda melakukan pekerjaan ekstra; Anda bisa melakukannya dengan satu putaran, bukan dua.
Doval
24
@Doval Sayangnya tidak - di NumPy . Python untuk loop yang melakukan penambahan elementwise dapat dengan mudah beberapa kali (!) Lebih lambat dari operator NumPy yang di-vektor (yang tidak hanya ditulis dalam C tetapi menggunakan instruksi SSE dan trik-trik lain) untuk ukuran array yang realistis.
46
Beberapa komentar di atas sepertinya salah paham dengan pertanyaan itu. Saat memprogram dalam NumPy, Anda mendapatkan hasil terbaik jika Anda dapat membuat vektor perhitungan Anda - yaitu, ganti loop eksplisit dalam Python dengan operasi seluruh-array di NumPy. Ini secara konseptual sangat berbeda dari pemrograman biasa dengan Python, dan membutuhkan waktu untuk belajar. Jadi saya pikir masuk akal bagi OP untuk meminta saran tentang bagaimana cara belajar melakukannya.
Gareth Rees
3
@PetereterB: Ya, itu benar. "Vektorisasi" tidak sama dengan "memilih algoritma terbaik". Mereka adalah dua sumber kesulitan yang berbeda dalam menghasilkan implementasi yang efisien dan jadi yang terbaik untuk memikirkannya satu per satu.
Gareth Rees

Jawaban:

89

Ini adalah kesulitan konseptual yang umum ketika belajar menggunakan NumPy secara efektif. Biasanya, pemrosesan data dalam Python paling baik diungkapkan dalam hal iterator , untuk menjaga penggunaan memori rendah, untuk memaksimalkan peluang paralelisme dengan sistem I / O, dan untuk menyediakan penggunaan kembali dan kombinasi bagian-bagian algoritma.

Tapi NumPy membalikkan semua itu: pendekatan terbaik adalah mengekspresikan algoritme sebagai urutan operasi seluruh-array , untuk meminimalkan jumlah waktu yang dihabiskan dalam interpreter Python lambat dan memaksimalkan jumlah waktu yang dihabiskan dalam rutinitas NumPy yang dikompilasi dengan cepat.

Inilah pendekatan umum yang saya ambil:

  1. Simpan versi asli dari fungsi (yang Anda yakini benar) sehingga Anda dapat mengujinya terhadap versi yang ditingkatkan baik untuk kebenaran dan kecepatan.

  2. Bekerja dari dalam ke luar: yaitu, mulailah dengan loop paling dalam dan lihat apakah dapat di-vektor; maka ketika Anda sudah melakukannya, pindah satu tingkat dan lanjutkan.

  3. Luangkan banyak waktu membaca dokumentasi NumPy . Ada banyak fungsi dan operasi di sana dan mereka tidak selalu diberi nama dengan cemerlang, jadi ada baiknya mengenal mereka. Khususnya, jika Anda mendapati diri Anda berpikir, "seandainya ada fungsi yang melakukan ini-dan-itu," maka ada baiknya menghabiskan sepuluh menit mencarinya. Biasanya ada di suatu tempat.

Tidak ada pengganti untuk latihan, jadi saya akan memberi Anda beberapa contoh masalah. Tujuan untuk setiap masalah adalah untuk menulis ulang fungsi sehingga sepenuhnya vektor : yaitu, sehingga terdiri dari urutan operasi NumPy pada seluruh array, tanpa loop Python asli (tidak ada foratau whilepernyataan, tanpa iterator atau pemahaman).

Masalah 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

Masalah 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

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

Spoiler di bawah ini. Anda akan mendapatkan banyak hasil terbaik jika Anda sendiri sebelum melihat solusi saya!

jawaban 1

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

Jawaban 2

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

Jawaban 3

np.where (x == hilang, nilai, x)

Gareth Rees
sumber
Tunggu, apakah ada kesalahan ketik pada jawaban terakhir atau apakah NumPy mengubah cara Python menginterpretasikan kode?
Izkata
1
@Izkata Ini tidak mengubah apa pun, tetapi operasi logis yang diterapkan pada array didefinisikan untuk mengembalikan array boolean.
sapi
@sapi Ah, saya merindukan apa yang terjadi di doctests, mengira itu jelaslist
Izkata
Mungkin harus ada cara untuk menanamkan APL?
Saya suka bagaimana Anda memberi pekerjaan rumah.
Koray Tugay
8

Untuk membuat segalanya lebih cepat, Anda harus membaca tentang struktur data Anda dan menggunakan yang sesuai.

Untuk ukuran non-sepele dari array kecil dan array besar (misalkan kecil = 100 elemen dan besar = 10.000 elemen) salah satu cara untuk melakukannya adalah dengan mengurutkan array kecil, kemudian beralih di atas array besar dan gunakan pencarian biner untuk menemukan elemen yang cocok dalam array kecil.

Ini akan membuat kompleksitas waktu maksimum, O (N log N) (dan untuk array kecil kecil dan array besar sangat dekat dengan O (N)) di mana solusi loop bersarang Anda adalah O (N ^ 2)

Namun. struktur data apa yang paling efisien sangat tergantung pada masalah yang sebenarnya.

Pieter B
sumber
-3

Anda dapat menggunakan kamus untuk mengoptimalkan kinerja secara signifikan

Ini adalah contoh lain:

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
Jakub Bares
sumber
2
Ini jelas masih menggunakan aliran pemikiran "for-loop".
8bittree