Soru Python'da nasıl, birden fazla 2d listesi üzerinde aynı anda, temiz bir şekilde nasıl yineleyebilirim?


Basit bir ızgara tabanlı oyun yapıyorum, örneğin, birkaç 2d listesi olabilir. Arazi için bir tane olabilir, bir diğeri nesneler için olabilir. Ne yazık ki, listeler üzerinde yinelemeye ihtiyacım olduğunda ve bir listedeki bir karenin içeriğine sahip olmak başka bir listenin parçasını etkiliyorsa, böyle bir şey yapmak zorundayım.

for i in range(len(alist)):
    for j in range(len(alist[i])):
        if alist[i][j].isWhatever:
            blist[i][j].doSomething()

Böyle bir şey yapmanın daha güzel bir yolu var mı?


22
2017-10-09 20:32


Menşei




Cevaplar:


Bir jeneratör metodu yazarak başlarım:

def grid_objects(alist, blist):
    for i in range(len(alist)):
        for j in range(len(alist[i])):
            yield(alist[i][j], blist[i][j])

Sonra listeler üzerinde yineleme yapmanız gerektiğinde kodunuz şöyle görünür:

for (a, b) in grid_objects(alist, blist):
    if a.is_whatever():
        b.do_something()

15
2017-10-09 20:51



Bu aynı şey değil, aradaki ikincisi alist [i] 'in yerini aldı, neden bu endeksi kaldırdınız? - Andrea Ambu
Kesinlikle bunu en çok beğendim. En iyi cevap olmayabilir, ancak bunu kullanmak oldukça muhtemel. - Eugene M
Bu Eugene'nin yazdığı şiir şekeri. Korkarım ki bu jeneratörler yeterince genişse, bu jeneratör çok yavaş olabilirdi. Sonuçta, her verim hala dört endeksli arama gerektirir. - DzinX
Ayrıca, range () çağrılarını xrange () olarak değiştirmek akıllıca olur. - tzot


Yukarıdaki çözümlerin performansıyla ilgilenen biri varsa, bunlar en hızlıdan en yavaşına kadar 4000x4000 ızgaralar içindir:

DÜZENLE: Brian'ın puanları eklendi izip değişiklik ve büyük miktarda kazandı!

John'un çözümü de çok hızlı, ancak endeksleri kullansa da (bunu görmek beni gerçekten şaşırttı!), Robert ve Brian'ın zip) yaratıcının ilk çözümünden daha yavaştır.

Hadi sunalım BrianBu iş parçacığının herhangi bir yerinde uygun biçimde gösterilmediği için kazanan işlevi

from itertools import izip
for a_row,b_row in izip(alist, blist):
    for a_item, b_item in izip(a_row,b_row):
        if a_item.isWhatever:
            b_item.doSomething()

32
2017-10-09 22:49



Brian'ın çözümünü olduğu gibi mi, yoksa itertools.izip ile mi denedin? Ayrıca önerimi de ölçmek ister misiniz? - tzot
Eklendi ve sonuçlar yine şaşırtıcı. Durumunuzda, beklenmedik bir şey eklemek gibi görünmüyor ( yuvalar) bir nesneye değişken çok yavaş! - DzinX
Oh, başlatma mevcut matris başlatma ile birleştirilebilir. Neden "neden olmasın" diye bilmiyorum yuvalar"bölüm; hücre nesnelerinin nasıl uygulandığını bilmiyoruz. BTW, a_item.isHesabı (yani parantez yok") çağırmayı unutmuşsunuzdur. - tzot
Eh, ben "normal" (yani sözlük) özellik yönetimi ile kukla nesneleri kullanılır. Paranteze gelince, asıl soruda hiçbiri yoktur :) - DzinX
İlginç. Orijinalin, her ne kadar nadiren olduğu durumlarda daha iyi performans göstereceğini düşünmüştüm, çünkü hiç bir zaman blistiğe erişme ihtiyacı duymuyordu, ama indeks araması çok daha önemliydi. % 0.5'de bile doğru olanı, izip yöntemi daha iyi oldu. - Brian


Onları sıkıştırabilirsin. yani:

for a_row,b_row in zip(alist, blist):
    for a_item, b_item in zip(a_row,b_row):
        if a_item.isWhatever:
            b_item.doSomething()

Bununla birlikte, nadiren aslında b_item'i kullanırsanız (örn. A_item.isWhen genellikle False), öğelerin üzerine sıkıştırma ve yineleme yükü orijinal yönteminizden daha yüksek olabilir. Bunun bellek etkisini azaltmak için zip yerine itertools.izip kullanabilirsiniz, ancak her zaman b_item'e ihtiyacınız olmadıkça, muhtemelen biraz daha yavaş olacaktır.

Alternatif olarak, bunun yerine bir 3B listesi kullanmayı düşünün, böylece hücre i için arazi, j l [i] [j] [0] 'da, l [i] [j] [1] gibi nesnelerde vs. [i] [j] .terrain, [i] [j] .object vb. yapabilir

[Düzenle] DzinX'in zamanlamaları Aslında, b_item için ekstra kontrolün etkisinin, endeks tarafından yeniden gözden geçirme performansının cezasının yanında, gerçekten önemli olmadığını, dolayısıyla yukarıdakilerin (izip kullanarak) en hızlı olduğu görülüyor.

Şimdi, 3d yaklaşımı için hızlı bir test verdim ve hala daha hızlı görünüyor, bu nedenle verilerinizi bu formda depolayabiliyorsanız, hem daha kolay hem de daha hızlı erişilebiliyordu. İşte onu kullanmanın bir örneği:

# Initialise 3d list:
alist = [ [[A(a_args), B(b_args)] for i in xrange(WIDTH)] for j in xrange(HEIGHT)]

# Process it:
for row in xlist:
    for a,b in row:
        if a.isWhatever(): 
            b.doSomething()

İşte, 1000x1000 dizisini kullanan 10 döngüye ilişkin zamanlamalarım, çeşitli oranlarda olanlarım, gerçek olan şu şeylerdir:

            ( Chance isWhatever is True )
Method      100%     50%      10%      1%

3d          3.422    2.151    1.067    0.824
izip        3.647    2.383    1.282    0.985
original    5.422    3.426    1.891    1.534

10
2017-10-09 20:39



Buradaki en hızlı çözüm budur, ancak yalnızca itertools.izip dosyasına zip değiştirirseniz (aşağıdan aşağıya benim yerime bakın). - DzinX


Rakamların ızgaralarıyla çalışırken ve gerçekten iyi bir performans istiyorsanız, kullanmayı düşünmelisiniz. numpy. Kullanımı şaşırtıcı derecede kolaydır ve ızgaralar üzerindeki döngüler yerine ızgaralarla işlemler açısından düşünmenizi sağlar. Performans, işlemlerin daha sonra optimize edilmiş SSE koduyla tüm ızgaralar üzerinde çalıştırılmasından kaynaklanır.

Örneğin, yazdığım kodların, yaylarla bağlanmış yüklü parçacıkların kaba kuvvet sayısal simülasyonunu yapan bir kod yazmasıdır. Bu kod, 31 düğümde 100 düğüm ve 99 kenarlı bir 3d sistem için bir zaman çizelgesi hesaplar. Bu, gelebileceğim en iyi python kodundan 10 kat daha hızlıdır.

from numpy import array, sqrt, float32, newaxis
def evolve(points, velocities, edges, timestep=0.01, charge=0.1, mass=1., edgelen=0.5, dampen=0.95):
    """Evolve a n body system of electrostatically repulsive nodes connected by
       springs by one timestep."""
    velocities *= dampen

    # calculate matrix of distance vectors between all points and their lengths squared
    dists = array([[p2 - p1 for p2 in points] for p1 in points])
    l_2 = (dists*dists).sum(axis=2)

    # make the diagonal 1's to avoid division by zero
    for i in xrange(points.shape[0]):
        l_2[i,i] = 1

    l_2_inv = 1/l_2
    l_3_inv = l_2_inv*sqrt(l_2_inv)

    # repulsive force: distance vectors divided by length cubed, summed and multiplied by scale
    scale = timestep*charge*charge/mass
    velocities -= scale*(l_3_inv[:,:,newaxis].repeat(points.shape[1], axis=2)*dists).sum(axis=1)

    # calculate spring contributions for each point
    for idx, (point, outedges) in enumerate(izip(points, edges)):
        edgevecs = point - points.take(outedges, axis=0)
        edgevec_lens = sqrt((edgevecs*edgevecs).sum(axis=1))
        scale = timestep/mass
        velocities[idx] += (edgevecs*((((edgelen*scale)/edgevec_lens - scale))[:,newaxis].repeat(points.shape[1],axis=1))).sum(axis=0)

    # move points to new positions
    points += velocities*timestep

4
2017-10-10 11:30





Hafif bir değişiklik olarak, numaralandırmayı kullanabilirsiniz:

for i, arow in enumerate(alist):
    for j, aval in enumerate(arow):
        if aval.isWhatever():
            blist[i][j].doSomething()

Veri yapılarınızı, Federico'nın önerdiği gibi yeniden düzenlemediğiniz sürece, çok daha basit bir şey elde edeceğinizi sanmıyorum. Böylece son satırı "aval.b.doSomething ()" gibi bir şeye dönüştürebilirsiniz.


3
2017-10-09 21:24





Jeneratör ifadeleri ve izip itibaren itertools modülü burada çok güzel yapacak:

from itertools import izip
for a, b in (pair for (aline, bline) in izip(alist, blist) 
             for pair in izip(aline, bline)):
    if a.isWhatever:
        b.doSomething()

İçindeki çizgi for Yukarıdaki ifade:

  • kombine ızgaralardan her satırı al alist ve blist ve onlardan bir tuple yapmak (aline, bline)
  • şimdi bu listeleri ile birleştirin izip tekrar ve her öğeyi onlardan al (pair).

Bu yöntemin iki avantajı vardır:

  • herhangi bir yerde kullanılan endeks yok
  • listelerini oluşturmak zorunda değilsiniz zip ve daha verimli jeneratörler ile izip yerine.

3
2017-10-09 21:12



Bu daha mı güzel? Bu daha okunabilir mi? - Andrea Ambu
İnanıyorum, evet. İndeksleri kaldırarak, burada neyi başarmaya çalıştığınız konusunda daha açık olursunuz (nesneleri manipüle etmek, pozisyonlarını değil). - DzinX
Bu açıkça en iyi yaklaşımdır. - Robert Rossney
Nesnelerin üzerinde sayılardan daha fazla yinelenen temiz olduğunu düşünüyorum. Güzel bir yaklaşım. - Brian


Paralel olarak yinelediğiniz iki matristeki nesnelerin kavramsal olarak farklı sınıfların örnekleri olduğundan emin misiniz? İçeren bir nesne matrisi ile biten iki sınıfı birleştirmeye ne dersiniz? her ikisi de isWhatever () ve doSomething ()?


2
2017-10-09 20:39





İki 2D listesi oyununuzun ömrü boyunca sabit kalırsa ve Python'un alist [i] [j] ve blist [i] [j] nesne sınıflarına katılmaları için birden fazla mirasın tadını çıkaramazsınız (diğerlerinin önerdiği gibi). b her bir öğe bir Listeler oluşturulduktan sonra öğe şöyle:

for a_row, b_row  in itertools.izip(alist, blist):
    for a_item, b_item in itertools.izip(a_row, b_row):
        a_item.b_item= b_item

Sınıflarınızdaki gibi çeşitli optimizasyonlar uygulanabilir. __slots__ Tanımlanmış veya yukarıdaki başlatma kodu, kendi başlatma kodunuzla birleştirilebilir e.t.c. Bundan sonra, döngünüz şöyle olur:

for a_row in alist:
    for a_item in a_row:
        if a_item.isWhatever():
            a_item.b_item.doSomething()

Bu daha verimli olmalı.


1
2017-10-09 21:53



Bu kesinlikle beklenmedik bir cevap. Oldukça ilginç. - Eugene M


Eğer a.isWhatever Nadiren doğru bir "indeksi" oluşturabilirsin:

a_index = set((i,j) 
              for i,arow in enumerate(a) 
              for j,a in enumerate(arow) 
              if a.IsWhatever())

ve her seferinde yapılması gereken bir şey:

for (i,j) in a_index:
    b[i][j].doSomething()

Zaman içinde bir değişiklik olursa, ihtiyacınız olacak dizini güncel tutun. Bu yüzden kullandım bir set, böylece öğeler eklenebilir ve hızlı bir şekilde kaldırılabilir.


0
2017-10-11 07:49





for d1 in alist
   for d2 in d1
      if d2 = "whatever"
          do_my_thing()

-4
2017-10-09 20:35



bloke ettin - Toni Ruža