“Döngü” düşünce okulundan nasıl uzaklaşırım?


79

Bu oldukça kavramsal bir soru, ancak bu konuda iyi tavsiyeler alabileceğimi umuyordum. Yaptığım programlamanın çoğu ( NumPy ) dizileriyle; Sık sık farklı boyutlardaki iki veya daha fazla dizideki öğeleri eşleştirmem gerekir ve ilk gittiğim şey bir döngü için, hatta daha da kötüsü, iç içe için bir döngüdür. Ben mümkün olduğunca for-loop'lardan kaçınmak istiyorum çünkü yavaşlar (en azından Python'da).

NumPy ile bir çok şey için araştırmam gereken önceden tanımlanmış komutlar olduğunu biliyorum ;

Bu yüzden sık sık böyle bir şey var, ki bu korkunç ve bundan kaçınmak istiyorum:

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]

Özellikle bunu başarmanın çok farklı yolları olduğunu biliyorum, ancak varsa genel bir düşünme yöntemiyle ilgileniyorum.


10
İşlevsel programlama arıyorsunuz : lambda ifadeleri, üst düzey fonksiyonlar, ifadeler üreten vb. Google.
Kilian Foth

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).Buradaki yanlış sorunu çözüyor gibisin. Bir şeyi yinelemeye ihtiyacınız varsa, bir şeyi yinelemeniz gerekir; Hangi Python'u kullanırsanız kullanın benzer bir performans elde edersiniz. Eğer kodunuz yavaşsa, fordöngüleriniz olmadığı için değil ; çünkü gereksiz yere ya da Python tarafında C tarafında yapılabilecek işleri yapıyorsun. Örnekte ekstra iş yapıyorsunuz; Bunu iki yerine bir döngüle yapabilirdin.
Doval

24
@Doval Maalesef hayır - NumPy'de . Epsiyonel olarak ekleme işlemini gerçekleştiren döngü için bir Python, gerçekçi dizi büyüklükleri için vectorized NumPy operatöründen (sadece C ile yazılmış ancak SSE komutlarını ve diğer püf noktaları kullanan) kullanan birkaç kez (!) Daha yavaş olabilir.

46
Yukarıdaki yorumlardan bazıları soruyu yanlış anlıyor gibi görünüyor. NumPy'de programlama yaparken, hesaplamalarınızı vektörelleştirebilirseniz en iyi sonuçları elde edersiniz - yani Python'daki açık döngüleri NumPy'deki tüm dizi işlemleriyle değiştirin. Bu kavramsal olarak Python'daki sıradan programlardan çok farklı ve öğrenmesi zaman alıyor. Bu yüzden, OP'nin bunu yapmayı öğrenmeye ilişkin önerilerde bulunmasının makul olduğunu düşünüyorum.
Gareth Rees

3
@ PieterB: Evet, doğru. "Vektörleştirme", "en iyi algoritmayı seçmek" ile aynı değildir. Bunlar verimli uygulamalar ile ortaya çıkmakta iki ayrı zorluk kaynağıdır ve bu nedenle bunlar hakkında tek tek düşünmek en iyisidir.
Gareth Rees

Yanıtlar:


89

NumPy'yi etkili bir şekilde kullanmayı öğrenirken bu yaygın bir kavramsal zorluktur . Normal olarak, Python'daki veri işleme en iyisi yineleyiciler cinsinden ifade edilir, bellek kullanımını düşük tutmak, G / Ç sistemiyle paralellik fırsatlarını en üst düzeye çıkarmak ve algoritma parçalarının yeniden kullanımını ve kombinasyonunu sağlamak için kullanılır.

Ancak NumPy bunların hepsini tersine çevirir: En iyi yaklaşım, algoritmayı bir dizi dizi işlem dizisi olarak ifade etmek , yavaş Python yorumlayıcısında harcanan zamanı en aza indirgemek ve hızlı derlenmiş NumPy yordamlarında harcanan zamanı en üst düzeye çıkarmaktır.

İşte aldığım genel yaklaşım:

  1. Fonksiyonun orijinal versiyonunu saklayın (doğru olduğundan emin olun), hem doğruluk hem de hız açısından gelişmiş versiyonlarınızla test edebilirsiniz.

  2. İçten dışa doğru çalışın: yani en içteki döngü ile başlayın ve bakalım vektörleştirilebilir mi; o zaman bunu yaptıktan sonra bir seviye yüksel ve devam et.

  3. NumPy belgelerini okumak için çok zaman harcayın . Orada birçok işlev ve işlem var ve bunlar her zaman zekice bir isim değil, bu yüzden onları tanımaya değer. Özellikle, eğer “sadece böyle ve böyle bir işlevi yapan bir işlev varsa” diye düşünürseniz, o zaman on dakika harcamakta fayda var. Genellikle orada bir yerlerdedir.

Uygulamanın yerine geçecek bir şey yok, bu yüzden size bazı örnek problemler vereceğim. Her sorunun amacı, işlevi tam olarak vektörleştirilinceye kadar yeniden yazmaktır: yani, tüm Pyra dizileri olmadan (diziler forveya whileifadeler, yineleyiciler veya anlamalar olmadan) tüm dizilerdeki bir dizi NumPy işleminden oluşur .

1. sorun

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

Sorun 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

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

Aşağıda Spoiler. Çözümlerime bakmadan önce kendiniz giderseniz en iyi sonuçları alacaksınız!

cevap 1

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

Cevap 2

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

Cevap 3

np.where (x == eksik, değer, x)


Bekle, son cevapta bir yazım hatası var mı yoksa NumPy Python'un kodu nasıl yorumladığını değiştiriyor mu?
Izkata,

1
@ Izkata Kendi başına hiçbir şeyi değiştirmez, ancak dizilere uygulanan mantıksal işlemler, boolean dizileri döndürmek için tanımlanır.
sapi

@sapi Ah, dokteste neler olup bittiğini özledim, düz olduğunu düşünmüştümlist
Izkata

Belki de APL'yi gömmenin bir yolu olmalı?

Ödevlerini vermeyi çok seviyorum.
Koray Tugay

8

İşleri daha hızlı yapmak için, veri yapılarınızı okumak ve uygun olanları kullanmak zorundasınız.

Küçük olmayan dizilerin ve büyük dizilerin önemsiz olmayan boyutları için (küçük = 100 elemanlar ve büyük = 10.000 elemanlar diyelim) bunu yapmanın bir yolu küçük diziyi sıralamak, sonra büyük diziyi yinelemektir ve eşleşen elemanları bulmak için ikili bir arama kullanmaktır. Küçük dizide.

Bu, maksimum zaman karmaşıklığını, O (N log N) (ve küçük küçük diziler ve çok büyük büyük diziler için, yuvalanmış döngü çözümünüzün O (N ^ 2) olduğu O (N) 'ye yakın yapar.

Ancak. Hangi veri yapılarının en verimli olduğu gerçek probleme bağlıdır.


-3

Performansı önemli ölçüde optimize etmek için bir sözlük kullanabilirsiniz

Bu başka bir örnek:

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
Bu oldukça açık bir şekilde hala "for-loop" düşünce okulu kullanıyor.
8,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.