Yineleme sırasında bir listeden öğeler nasıl kaldırılır?


934

Python'daki tuples listesini tekrar ediyorum ve belirli kriterleri karşılamaları durumunda bunları kaldırmaya çalışıyorum.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

Bunun yerine ne kullanmalıyım code_to_remove_tup? Öğeyi bu şekilde nasıl kaldıracağımı anlayamıyorum.


Bu sayfadaki cevapların çoğu, bir liste üzerinde yineleme yaparken öğelerin kaldırılmasının neden garip sonuçlar ürettiğini açıklamıyor, ancak bu sorudaki kabul edilen cevap, bu sorunla ilk kez karşılaşan yeni başlayanlar için muhtemelen daha iyi bir dupe.
ggorlen

Yanıtlar:


827

Yalnızca kaldırmak istemediğiniz öğeleri içeren yeni bir liste oluşturmak için bir liste kavrama özelliğini kullanabilirsiniz:

somelist = [x for x in somelist if not determine(x)]

Veya dilime atayarak somelist[:], varolan listeyi yalnızca istediğiniz öğeleri içerecek şekilde değiştirebilirsiniz:

somelist[:] = [x for x in somelist if not determine(x)]

Bu yaklaşım, başka referanslar da varsa yararlı olabilir. somelist , değişiklikleri yansıtması gereken .

Anlama yerine, kullanabilirsiniz itertools. Python 2'de:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

Veya Python 3'te:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

Anlaşılır olması ve [:]notasyonu hackish veya fuzzy kullananlar için , burada daha açık bir alternatif. Teorik olarak, uzay ve zaman açısından yukarıdaki tek astarlardan aynı şeyi yapmalıdır.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

Ayrıca , Python listelerinin değiştirilme yeteneğine sahip olmayan diğer dillerde de minimum değişikliklerle çalışır. Örneğin, FalsePython'un yaptığı gibi tüm diller boş listeler a'ya yayınlamaz. while somelist:Daha açık bir şeyin yerine geçebilirsiniz while len(somelist) > 0:.


4
Sadece birkaç tanesinin silineceğini, yani yalnızca bunları silip diğerlerini yerinde yeniden yazmak yerine yerinde bırakacağını biliyorsanız daha hızlı yapabilir misiniz?
highBandWidth

20
Listem çok büyükse ve bir kopyasını alamazsa ne olur?
jpcgt

15
@jpcgt somelist[:] = (x for x in somelist if determine(x))Gereksiz kopya oluşturmayabilecek bir jeneratör oluşturacaksınız.
Rostislav Kondratenko

8
@RostislavKondratenko: dahili olarak çağrıları list_ass_slice()uygulayan işlev . Bu işlev her zaman bir liste döndürür, yani @Alex Martelli'nin jeneratör yerine zaten bir liste kullanan çözümü büyük olasılıkla daha verimlidirsomelist[:]=PySequence_Fast()
jfs

6
Liste kavrayışını listeye ve liste klonuna atamak arasındaki farkları açıklamak ister misiniz lütfen? Orijinal liste somelisther iki yöntemde de değiştirilemez mi?
Bowen Liu

589

Liste kavrayışlarını gösteren cevaplar ALMOST doğrudur - tamamen yeni bir liste oluşturmaları ve daha sonra eski listeyle aynı adı vermeleri dışında, eski listeyi yerinde DEĞİŞTİRMEMEKTEDİR. Bu, Lennart'ın önerisinde olduğu gibi, seçici kaldırma ile yapacağınızdan farklıdır - daha hızlıdır, ancak listenize birden çok referansla erişiliyorsa, referanslardan birini tekrar dinlediğiniz ve liste nesnesini DEĞİŞTİRMİYORSANIZ kendisi ince, feci hatalara yol açabilir.

Neyse ki, hem liste kavrayış hızını hem de yerinde değişiklik gerekli semantiğini elde etmek son derece kolaydır - sadece kod:

somelist[:] = [tup for tup in somelist if determine(tup)]

Diğer cevaplarla ince farkı not edin: bu bir barename atamıyor - sadece bir liste haline gelen bir liste dilimine atar, böylece liste içeriğini sadece bir referansı tekrar yerine değil , aynı Python liste nesnesi içinde değiştirir (önceki liste nesnesinden yeni liste nesnesine) diğer yanıtlar gibi.


1
Bir dikte ile aynı dilimlenmiş ödevi nasıl yaparım? Python 2.6'da mı?
PaulMcG

11
@ Paul: Dikteler sıralanmamış olduğundan, dilimler dikteler için anlamsızdır. Senin Talep dict içeriğini değiştirmek için ise adict içeriğine göre b, kullanmak a.clear(); a.update(b).
Sven Marnach

1
Değişkenin neden olduğu hataları hatalara neden olarak değiştirerek neden referanslardan birini 'yeniden' doldurabilirsiniz? Öyle görünüyor ki, sadece çok iş parçacıklı uygulamalarda potansiyel bir sorun var, tek iş parçacıklı değil.
Derek Dahmer

59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Bu x, liste kavrayışının sonucuna yeniden atar , ancak yyine de orijinal listeyi ifade eder ['foo','bar','baz']. Aynı listeye başvurmayı bekliyorsanız xve yhataları tanıtmış olabilirsiniz. Alex gösterdiği gibi, tüm listenin bir dilim atayarak Bunu önlemek ve burada gösterir: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Liste yerinde değiştirildi. listeye (her ikisi için tüm referansların sağlanması xve yburada) yeni listeye bakın.
Steven T. Snyder

aslında, filterişlevi kullanmak da yeni bir liste oluşturur, yerinde öğeleri değiştirmez ... sadeceolist[:] = [i for i in olist if not dislike(i)]
John Strood

302

Listenin bir kopyasını almanız ve önce listeyi tekrarlamanız gerekir, aksi takdirde yineleme beklenmedik sonuçlarla başarısız olur.

Örneğin (hangi liste türüne bağlı olarak):

for tup in somelist[:]:
    etc....

Bir örnek:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

13
@Zen Çünkü ikincisi listenin bir kopyası üzerinde yineleme yapıyor. Bu nedenle, orijinal listeyi değiştirdiğinizde, yinelediğiniz kopyayı değiştirmezsiniz.
Lennart Regebro

3
Somelist [:] 'i listeye (somelist) kıyasla daha iyi yapan nedir?
Mariusz Jamro

3
list(somelist)yinelenebilir bir listeye dönüştürür. somelist[:]dilimlemeyi destekleyen bir nesnenin kopyasını oluşturur. Yani aynı şeyi yapmaları gerekmiyor. Bu durumda somelistnesnenin bir kopyasını yapmak istiyorum , bu yüzden kullanıyorum[:]
Lennart Regebro

33
Bunu okuyan herkese dikkat edin, bu listeler için ÇOK yavaş. remove()her yineleme için BÜTÜN listesini gözden geçirmek zorundadır, bu yüzden sonsuza dek sürecektir.
vitiral

7
Büyük O zamanı, yalnızca bir düzine öğenin listesiyle uğraşırken önemli değildir. Gelecekteki programcıların anlaması genellikle açık ve basittir, performanstan çok daha değerlidir.
Steve

127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Geri gitmeniz gerekiyor, aksi takdirde oturduğunuz ağaç dalını kesmek gibi bir şey :-)

Python 2 kullanıcıları: yerine rangegöre xrangekaçınmak için kodlanmış listesi oluşturma


13
Python'un son sürümlerinde, reversed()yerleşik
ncoghlan

16
reversed () yeni bir liste oluşturmaz, sağlanan sekans üzerinde ters bir yineleyici oluşturur. Enumerate () gibi, listeden gerçekten çıkarmak için list () öğesine sarmanız gerekir. Sen düşünme olabilir sıralanmış (), hangi yapar (bunu sıralayabilir, böylece o zorundadır) Yeni bir listesine her zaman yaratmak.
ncoghlan

1
@Mauris çünkü enumeratebir yineleyici döndürür ve reversedbir dizi bekler. Sanırım reversed(list(enumerate(somelist)))hafızada fazladan bir liste oluşturmayı düşünmezsen yapabilirsin.
drevicko

2
Bu, diziler için O (N * M) 'dir, çok sayıda öğeyi büyük bir listeden kaldırırsanız çok yavaştır. Bu yüzden tavsiye edilmez.
Sam Watkins

2
@SamWatkins Evet, bu cevap çok geniş bir diziden birkaç öğeyi kaldırdığınızda içindir. Daha az bellek kullanımı, ancak mdaha yavaş olabilir.
Navin

52

Resmi Python 2 eğitimi 4.2. "İfadeler için"

https://docs.python.org/2/tutorial/controlflow.html#for-statements

Dokümanların bu kısmı şunu açıkça ortaya koyuyor:

  • değiştirmek için yinelenen listenin bir kopyasını oluşturmanız gerekir
  • Bunu yapmanın bir yolu dilim gösterimi ile [:]

Döngünün içindeyken yinelediğiniz diziyi değiştirmeniz gerekirse (örneğin, seçilen öğeleri çoğaltmak için), önce bir kopya oluşturmanız önerilir. Bir sekans üzerinde yineleme yapmak dolaylı olarak bir kopya oluşturmaz. Dilim notasyonu bunu özellikle uygun hale getirir:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Python 2 belgeleri 7.3. "For ifadesi"

https://docs.python.org/2/reference/compound_stmts.html#for

Dokümanların bu kısmı bir kez daha bir kopya yapmanız gerektiğini söylüyor ve gerçek bir kaldırma örneği veriyor:

Not: Dizi döngü tarafından değiştirilirken bir incelik vardır (bu yalnızca değiştirilebilir diziler, yani listeler için olabilir). Bir sonraki sayaç hangi öğenin kullanılacağını takip etmek için kullanılır ve bu her yinelemede artırılır. Bu sayaç dizinin uzunluğuna ulaştığında, döngü sona erer. Bu, paket geçerli (veya önceki) bir öğeyi diziden silerse, sonraki öğenin atlanacağı anlamına gelir (daha önce işlenmiş olan geçerli öğenin dizinini alır). Benzer şekilde, paket geçerli öğeden önce sırayla bir öğe eklerse, geçerli öğe döngü içinde bir sonraki sefer yeniden ele alınır. Bu, tüm dizinin bir dilimini kullanarak geçici bir kopya yaparak önlenebilecek kötü hatalara yol açabilir, örn.

for x in a[:]:
    if x < 0: a.remove(x)

Ancak, bu uygulamaya katılmıyorum, çünkü değeri bulmak için tüm listeyi.remove() yinelemek zorunda.

En iyi geçici çözümler

Ya:

  • sıfırdan yeni bir dizi başlatmak .append()ve sonunda geri: https://stackoverflow.com/a/1207460/895245

    Bu zaman verimli, ancak daha az yer tasarrufu sağlar, çünkü yineleme sırasında dizinin bir kopyasını tutar.

  • delbir indeks ile kullanın : https://stackoverflow.com/a/1207485/895245

    Bu, dizi kopyasını dağıttığı için daha fazla yer tasarrufu sağlar, ancak CPython listeleri dinamik dizilerle uygulandığından daha az zaman tasarrufu sağlar .

    Bu, öğenin kaldırılmasının aşağıdaki tüm öğelerin birer birer kaydırılmasını gerektirdiği anlamına gelir, yani O (N).

Genellikle daha hızlı gitmek istersiniz .append()Bellek büyük bir endişe olmadığı sürece genellikle varsayılan olarak seçeneğe .

Python bunu daha iyi yapabilir mi?

Bu belirli Python API'sı geliştirilebilir gibi görünüyor. Örneğin, aşağıdakilerle karşılaştırın:

  • Java ListIterator :: hangi belgeleri kaldır "Bu çağrı sonraki veya önceki çağrı için yalnızca bir kez yapılabilir"
  • std::vector::eraseKaldırıldıktan sonra öğeye geçerli bir interator döndüren C ++

her ikisi de yineleyicinin kendisi dışında yinelenen bir listeyi değiştiremeyeceğinizi açıkça gösterir ve listeyi kopyalamadan bunu yapmanın etkili yollarını sunar.

Belki de altta yatan mantık, Python listelerinin dinamik dizi destekli olduğu varsayılır ve bu nedenle her türlü kaldırma her zaman zaman verimsiz olurken, Java'nın hem ArrayListve LinkedListuygulamaları ile daha güzel bir arayüz hiyerarşisi vardır ListIterator.

Python stdlib'de de açık bir bağlantılı liste türü yok gibi görünüyor: Python Bağlantılı Liste


48

Böyle bir örnek için en iyi yaklaşımınız bir liste kavraması olacaktır

somelist = [tup for tup in somelist if determine(tup)]

Bir determineişlevi çağırmaktan daha karmaşık bir şey yaptığınız durumlarda , yeni bir liste oluşturmayı ve gittikçe eklemeyi tercih ederim. Örneğin

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

Listeyi kullanarak kopyalamak, removeaşağıdaki yanıtlardan birinde açıklandığı gibi kodunuzun biraz daha temiz görünmesini sağlayabilir. Bunu son derece büyük listeler için kesinlikle yapmamalısınız, çünkü bu öncelikle tüm listeyi kopyalamayı ve ayrıca O(n) removekaldırılan her öğe için bir işlem gerçekleştirmeyi ve bunu bir O(n^2)algoritma haline getirmeyi içerir .

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

37

Fonksiyonel programlamayı sevenler için:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

veya

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

1. Liste anlama ve üretici ifadeleri, saf işlevsel bir dil olan Haskell'den ödünç alınmıştır; onlar kadar işlevsel filterve daha Pythonic. 2. Eğer bir ihtiyaç varsa lambdakullanmak mapveya filterliste comp veya genexpr olduğunu hep daha iyi bir seçenek; mapve filterdönüştürme / yüklem işlevi C'de yerleşik bir Python olduğunda ve yinelenebilir önemsiz derecede küçük olmadığında biraz daha hızlı olabilir, ancak lambdalistcomp / genexpr'in önleyebileceği bir şeye ihtiyacınız olduğunda her zaman daha yavaştır .
ShadowRanger

13

Bunu büyük bir liste ile yapmam gerekiyordu ve listeyi çoğaltmak pahalı görünüyordu, özellikle de benim durumumda kalan öğelere kıyasla silme sayısı az olurdu. Bu düşük seviyeli yaklaşımı kullandım.

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

Ne bilmiyorum birkaç silme büyük bir listeyi kopyalama ile karşılaştırıldığında ne kadar verimli olduğunu. Herhangi bir görüşünüz varsa lütfen yorum yapın.


Benim durumumda bu 'istenmeyen' öğeleri başka bir listeye taşımam gerekiyor. Bu çözüm hakkında yeni bir yorumunuz var mı? Ayrıca, listeyi çoğaltmak yerine bazı silme işlemlerini kullanmanın daha iyi olduğunu düşünüyorum.
gustavovelascoh

Performans bir sorunsa bu doğru yanıttır (@Alexey ile aynı olsa da). Bununla birlikte list, bir listenin ortasından kaldırılması listenin uzunluğunda doğrusal zaman aldığından, ilk etapta bir veri yapısı olarak seçimin dikkatle değerlendirilmesi gerekir. K-th sıralı öğeye gerçekten rastgele erişime ihtiyacınız yoksa, belki düşünün OrderedDict?
en fazla

@GVelascoh neden yaratmadınız newlist = []ve newlist.append(array[i])hemen önce del array[i]?
en fazla

2
Bunun muhtemelen zaman verimsiz olduğuna dikkat edin: list()bağlı bir liste ise, rasgele erişim pahalıdır, eğer list()bir dizi ise, silmeler pahalıdır, çünkü aşağıdaki tüm öğeleri ileriye taşımak gerekir. İyi bir yineleyici bağlantılı liste uygulaması için işleri iyi yapabilir. Ancak bu alandan tasarruf sağlayabilir.
Ciro Santilli 法轮功 病毒 审查 8 事件 法轮功

10

Mevcut liste öğesi istenen kriterleri karşılıyorsa, yeni bir liste oluşturmak da akıllıca olabilir.

yani:

for item in originalList:
   if (item != badValue):
        newList.append(item)

ve projenin tamamını yeni listeler adıyla yeniden kodlamaktan kaçınmak için:

originalList[:] = newList

not, Python belgelerinden:

copy.copy (x) x'in sığ bir kopyasını döndürür.

copy.deepcopy (x) x'in derin bir kopyasını döndürür.


3
Bu, yıllar önce kabul edilen cevapta bulunmayan yeni bir bilgi eklemez.
Mark Amery

2
Bu basit ve @MarkAmery bir soruna bakmak için başka bir yol. Sıkıştırılmış kodlama sözdizimini sevmeyen insanlar için daha az yoğunlaşır.
ntk4

9

Bu yanıt başlangıçta yinelenen olarak işaretlenmiş bir soruya yanıt olarak yazılmıştır: Python'daki listeden koordinatları kaldırma

Kodunuzda iki sorun var:

1) remove () kullanırken, tamsayıları kaldırmayı denerken, bir tuple kaldırmanız gerekir.

2) for döngüsü listenizdeki öğeleri atlar.

Kodunuzu yürüttüğümüzde neler olacağını inceleyelim:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

İlk sorun, kaldırılacak () için hem 'a' hem de 'b' iletiyorsunuz, ancak remove () yalnızca tek bir argümanı kabul ediyor. Öyleyse listenizle düzgün bir şekilde çalışmak için remove () yöntemini nasıl alabiliriz? Listenizdeki her bir öğenin ne olduğunu bulmamız gerekiyor. Bu durumda, her biri bir demettir. Bunu görmek için listenin bir öğesine erişelim (indeksleme 0'dan başlar):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Aha! L1'in her elemanı aslında bir demettir. Kaldırmak için geçmemiz gereken şey budur (). Python'daki tuples çok kolaydır, sadece değerleri parantez içine alarak yapılırlar. "a, b" bir demet değil, "(a, b)" bir demettir. Kodunuzu değiştirip tekrar çalıştırıyoruz:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Bu kod hatasız çalışır, ancak çıktığı listeye bakalım:

L1 is now: [(1, 2), (5, 6), (1, -2)]

(1, -2) neden hala listenizde? Üzerinde yineleme yapmak için bir döngü kullanırken listeyi değiştirmek, özel bir bakım gerektirmeden çok kötü bir fikirdir. (1, -2) öğesinin listede kalmasının nedeni, listedeki her öğenin konumlarının for döngüsünün yinelemeleri arasında değişmesidir. Yukarıdaki kodu daha uzun bir liste halinde beslersek ne olduğuna bakalım:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Bu sonuçtan çıkarım yapabileceğiniz gibi, koşullu ifadenin her doğru olarak değerlendirildiği ve bir liste öğesinin kaldırıldığı her seferinde, döngünün bir sonraki yinelemesi, değerleri artık farklı endekslerde yer aldığından listedeki bir sonraki öğenin değerlendirmesini atlayacaktır.

En sezgisel çözüm listeyi kopyalamak, daha sonra orijinal listeyi tekrarlamak ve yalnızca kopyayı değiştirmek. Bunu böyle deneyebilirsiniz:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Ancak, çıktı aşağıdakilerle aynı olacaktır:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Bunun nedeni, L2'yi oluşturduğumuzda, python'un aslında yeni bir nesne oluşturmadığıdır. Bunun yerine, yalnızca L2'yi L1 ile aynı nesneye göndermiştir. Bunu sadece "eşittir" (==) 'den farklı olan' is 'ile doğrulayabiliriz.

>>> L2=L1
>>> L1 is L2
True

Copy.copy () kullanarak gerçek bir kopya oluşturabiliriz. Sonra her şey beklendiği gibi çalışır:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Son olarak, tamamen yeni bir L1 kopyası yapmaktan daha temiz bir çözüm var. Reversed () işlevi:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Ne yazık ki, tersine () nasıl çalıştığını yeterince tanımlayamıyorum. Bir liste iletildiğinde bir 'listreverseiterator' nesnesi döndürür. Pratik amaçlar için, argümanının ters kopyasını oluşturmak olarak düşünebilirsiniz. Önerdiğim çözüm bu.


4

Yineleme sırasında başka bir şey yapmak isterseniz, hem dizine (referans verebilmenizi garanti eden, örneğin bir dikte listeniz varsa) hem de gerçek liste öğesi içeriğini almak iyi olabilir.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerateöğeye ve dizine bir kerede erişmenizi sağlar. reversedböylece daha sonra sileceğiniz endekslerin sizin üzerinizde değişmemesi.


Neden bir liste listeniz varsa, dizini başka herhangi bir liste yerine neden daha alakalı hale getiriyorsunuz? Bu benim anlayabildiğim kadarıyla mantıklı değil.
Mark Amery


4

Buradaki cevapların çoğu listenin bir kopyasını oluşturmanızı istiyor. Listenin oldukça uzun olduğu bir kullanım durumum vardı (110K öğe) ve bunun yerine listeyi azaltmaya devam etmek daha akıllıydı.

Her şeyden önce foreach döngüsünü while döngüsüyle değiştirmeniz gerekir ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

iİf bloğundaki değer değiştirilmez, çünkü eski öğe silindikten sonra SAME INDEX adlı yeni öğenin değerini almak isteyeceksiniz.


3

Bazı döngüler için şuna benzer bir işlem yapmanız için tersine döngüyü deneyebilirsiniz:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

Bu şekilde dizin hizalanır ve liste güncellemelerinden etkilenmez (öğeyi açıp açmamaya bakılmaksızın).


Döngü, reversed(list(enumerate(some_list)))dizinleri kendiniz hesaplamaktan daha kolay olacaktır.
Mark Amery

@MarkAmery listeyi bu şekilde değiştirebileceğinizi düşünmüyor.
Queequeg

3

Sadece bazı şeyleri kaldırmak değil, aynı zamanda tüm öğelerle tek bir döngüde bir şey yapmak istiyorsanız kullanışlı bir çözüm:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

Gerçekten sadece kavrayışları kullanmalısınız. Anlamaları çok daha kolay.
Beefster

Bir badşeyi kaldırmak , onunla bir şey yapmak ve aynı zamanda bir şeyle bir şey yapmak istersem ne olur good?
Alexey

1
Aslında, açık bir dilim ( alist[:]) ile listenin bir kopyasını yaptığınız için burada bir akıllılık olduğunu fark ettim ve süslü bir şey yapıyor olabileceğiniz için, aslında bir kullanım durumu var. İyi revizyon iyidir. Benim oyumu al.
18'de Beefster

2

Benzer bir şey yapmam gerekiyordu ve benim durumumda problem hafızaydı - Onlarla bir şeyler yaptıktan sonra yeni bir nesne olarak bir liste içinde birden çok veri kümesi nesnesini birleştirmem gerekiyordu ve birleştirdiğim her girişten kurtulmam gerekiyordu hepsini çoğaltmaktan ve belleği patlatmaktan kaçının. Benim durumumda bir liste yerine sözlükte nesneleri olması iyi çalıştı:

`

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

`


2

TLDR:

Bunu yapmanıza izin veren bir kütüphane yazdım:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

Mümkünse, yinelemede yinelemenizi değiştirmeyi gerektirmeyen başka bir yöntem kullanmak en iyisidir, ancak bazı algoritmalar için bu doğru olmayabilir. Bu nedenle, orijinal soruda açıklanan kod desenini gerçekten istediğinizden eminseniz, bu mümkündür.

Sadece listeler değil tüm değiştirilebilir diziler üzerinde çalışmalıdır.


Tam cevap:

Düzenleme: Bu yanıttaki son kod örneği, neden bazen bir liste kavrama kullanmak yerine bir listeyi değiştirmek isteyebileceğinize ilişkin bir kullanım durumu verir . Cevapların ilk bölümü öğretici olarak görev yapmaktadır nasıl bir dizi yerde değiştirilebilir.

Solüsyon üzerinde izler bu cevaptan (ilgili bir soru için) gönderenden gelir. Bu, değiştirilmiş bir liste üzerinden yineleme yapılırken dizi dizininin nasıl güncellendiğini açıklar. Aşağıdaki çözüm, liste değiştirilse bile dizi dizinini doğru şekilde izlemek için tasarlanmıştır.

İndir fluidIter.pydan burada https://github.com/alanbacon/FluidIterator gerek budala yüklemeye böylece, sadece tek bir dosyadır. Yükleyici yoktur, bu nedenle dosyanın kendi python yolunda olduğundan emin olmanız gerekir. Kod python 3 için yazılmıştır ve python 2 üzerinde test edilmemiştir.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Bu aşağıdaki çıktıyı üretecektir:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Yukarıda pop, akışkan listesi nesnesindeki yöntemi kullandık . Diğer yaygın iterable yöntemler de bu olarak uygulanır del fluidL[i], .remove, .insert, .append, .extend. Liste, dilimler ( sortvereverse yöntemler uygulanmaz).

Tek koşul, listeyi yalnızca herhangi bir noktada fluidLveya lfarklı bir liste nesnesine yeniden atanmışsa kodun çalışmazsa değiştirmesi gerektiğidir. Orijinal fluidLnesne for döngüsü tarafından kullanılmaya devam eder, ancak değiştirmemiz için kapsam dışı kalır.

yani

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Listenin geçerli dizin değerine erişmek istiyorsak, numaralandırma kullanamayız, çünkü bu sadece for döngüsünün kaç kez çalıştığını sayar. Bunun yerine, iterator nesnesini doğrudan kullanacağız.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Bu aşağıdakileri çıkarır:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

FluidIterableSınıf sadece orijinal liste nesnesi için sarıcı sağlar. Orijinal nesneye, aşağıdaki gibi sıvı nesnenin bir özelliği olarak erişilebilir:

originalList = fluidArr.fixedIterable

Daha fazla örnek / test if __name__ is "__main__":, altındaki bölümden bulunabilir fluidIter.py. Bunlara bakmaya değer çünkü çeşitli durumlarda neler olduğunu açıklıyorlar. Örneğin: Listenin büyük bölümlerini bir dilim kullanarak değiştirme. Ya da döngüler için iç içe yerleştirilenlerde aynı yinelenebilir öğenin kullanılması (ve değiştirilmesi)

Başlamak için belirttiğim gibi: Bu, kodunuzun okunabilirliğine zarar verecek ve hata ayıklamayı zorlaştıracak karmaşık bir çözümdür. Böyle David Raznick en belirtilen liste comprehensions Bu nedenle diğer çözümler cevap ilk düşünülmelidir. Bununla birlikte, bu sınıfın benim için yararlı olduğu ve silmeye ihtiyaç duyan öğelerin indekslerini takip etmekten daha kolay olduğu zamanlar buldum.


Düzenleme: Yorumlarda belirtildiği gibi, bu cevap gerçekten bu yaklaşımın bir çözüm sağladığı bir sorun sunmuyor. Bunu burada ele almaya çalışacağım:

Liste kavrayışları yeni bir liste oluşturmak için bir yol sağlar, ancak bu yaklaşımlar listenin bir bütün olarak mevcut durumu yerine her bir öğeye ayrı ayrı bakma eğilimindedir.

yani

newList = [i for i in oldList if testFunc(i)]

Peki ya sonuç zaten testFunceklenmiş olan öğelere bağlıysa newList? Ya da hala içinde bulunan elemanlar oldListeklenebilir mi? Bir liste kavrayışını kullanmanın bir yolu olabilir, ancak zarafetini kaybetmeye başlayacak ve benim için bir listeyi yerinde değiştirmek daha kolay geliyor.

Aşağıdaki kod, yukarıdaki sorundan muzdarip bir algoritma örneğidir. Algoritma bir listeyi azaltacaktır, böylece hiçbir öğe diğer öğelerin katı değildir.

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

Çıktı ve son indirgenmiş liste aşağıda gösterilmiştir

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

Bunun aşırı tasarlanmış olup olmadığını söylemek zor, çünkü hangi sorunu çözmeye çalıştığı belli değil; Bu yaklaşımı kullanarak elemanların kaldırılması, elde some_list[:] = [x for x in some_list if not some_condition(x)]edilemeyen ne elde eder? Buna bir cevap vermeden, neden 600 satırlık kitaplığınızı yazım hataları ve yorumlanmış kodlarla birlikte indirmenin ve kullanmanın, sorunlarına tek satırdan daha iyi bir çözüm olduğuna inanmalı? -1.
Mark Amery

@MarkAmery. Bunun bir öğenin yalnızca öğenin kendisine değil, listedeki başka bir öğenin durumuna veya listenin durumuna bütün. Örneğin, liste kavrayışlarında farklı bir liste öğesinin some_list[:] = [x for x in some_list if not some_condition(y)]nerede yolduğu gibi bir şey yazmak mümkün değildir x. Yazmak da mümkün değildi some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Rezonans

2

En etkili yöntem liste anlama, birçok kişi tabii ki, aynı zamanda bir almak için iyi bir yoldur, kendi halinde göstermek olduğunu iteratorthrough filter.

Filterbir fonksiyon ve bir dizi alır. Filteriletilen işlevi sırayla her öğeye uygular ve sonra işlev döndürme değerinin Trueveya değerine bağlı olarak öğeyi tutmaya veya atmaya karar verir False.

Bir örnek var (tupledaki oranları alın):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Dikkat: Yineleyicileri de kullanamazsınız. Yineleyiciler bazen dizilerden daha iyidir.


2

for döngüsü dizin üzerinden yinelenen olacaktır ..

bir listeniz olduğunu düşünün,

[5, 7, 13, 29, 65, 91]

list değişkenini kullandınız lis. ve bunu kaldırmak için kullanıyorsunuz ..

senin değişkenin

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

5. iterasyon sırasında,

senin numarası 35 , bir listeden kaldırıldı böylece bir asal değildi.

lis.remove(y)

ve sonraki değer (65) önceki dizine geçer.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

4. iterasyon işaretçisi 5. sıraya taşındı.

bu yüzden döngünüz önceki dizine taşındığından beri 65'i kapsamaz.

bu yüzden listeyi kopya yerine orijinali referans alan başka bir değişkene göndermemelisiniz.

ite = lis #dont do it will reference instead copy

listenin kopyasını kullanarak list[::]

şimdi vereceksin,

[5, 7, 13, 29]

Sorun, yineleme sırasında bir listeden bir değeri kaldırmanız, sonra liste dizininizin daralmasıdır.

bunun yerine kavrayışı deneyebilirsiniz.

liste, tuple, dict, string vb.Tüm yinelenebilirleri destekler


Bu, kodumun neden başarısız olduğunu anlamama yardımcı oldu.
Wahid Sadik

2

Yineleme sırasında bir listeden öğeleri silmek istiyorsanız, her silme işleminden sonra geçerli dizini ve bitiş dizinini değiştirebilmeniz için while döngüsü kullanın.

Misal:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1

1

Diğer yanıtlar, yinelediğiniz bir listeden silinmenin genellikle kötü bir fikir olduğu doğrudur. Ters yineleme tuzakları önler, ancak bunu yapan kodu takip etmek çok daha zordur, bu nedenle genellikle bir liste kavrama veyafilter .

Bununla birlikte, yinelemekte olduğunuz bir dizideki öğeleri kaldırmanın güvenli olduğu bir durum vardır: yineleme sırasında yalnızca bir öğeyi kaldırıyorsanız. Bu, a returnveya a kullanılarak sağlanabilir break. Örneğin:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

Bu, bir listenin bazı koşullarını karşılayan ilk öğesinde yan etkileri olan bazı işlemleri yaparken ve ardından hemen ardından öğeyi listeden kaldırdığınızda, listeyi anlamaktan daha kolay anlaşılır.


1

Sorununuzu çözmek için üç yaklaşım düşünebilirim. Örnek olarak, rastgele bir tuples listesi oluşturacağım somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Seçtiğim koşul sum of elements of a tuple = 15. Son listede sadece toplamı 15'e eşit olmayan tupllere sahip olacağız.

Seçtiğim rastgele seçilmiş bir örnek. Değiştirmek için çekinmeyin dizilerini listesi ve durumu Seçtiğim söyledi.

Yöntem 1.> Önerdiğiniz çerçeveyi kullanın (burada for döngüsü içindeki bir kodu doldurur). Ben delsöz konusu koşulu karşılayan bir demet silmek için ile küçük bir kod kullanın . Bununla birlikte, arka arkaya yerleştirilen iki tupl verilen koşulu karşılarsa, bu yöntem bir tuple (söz konusu koşulu karşılar) kaçırır.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Yöntem 2.> Verilen koşulun karşılanmadığı öğeler (tuples) içeren yeni bir liste oluşturun (bu, belirtilen koşulun karşılandığı liste öğelerini kaldırmakla aynı şeydir). Bunun kodu aşağıdadır:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Yöntem 3.> Verilen koşulun karşılandığı dizinleri bulun ve ardından bu dizinlere karşılık gelen kaldırma öğelerini (tuples) kullanın. Bunun kodu aşağıdadır.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Yöntem 1 ve yöntem 2, yöntem 3'ten daha hızlıdır . Yöntem2 ve yöntem3 yöntem1'den daha etkilidir. Ben Method2 tercih . Yukarıda belirtilen örnek için,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


0

Gerçekten büyük olma potansiyeli olan her şey için aşağıdakileri kullanıyorum.

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Bu her şeyden çok daha hızlı olmalı.


Ölçtüğüm kadarıyla, NumPy 20'den fazla öğenin listeleri için daha hızlı olmaya başlıyor ve 1000 öğeden daha büyük listeler için> 12x daha hızlı filtrelemeye ulaşıyor.
Georgy

0

Bazı durumlarda, bir listeye bir defada filtre uygulamaktan daha fazlasını yaptığınız durumlarda, yinelemede yinelemenin değişmesini istersiniz.

Listeyi önceden kopyalamanın yanlış olduğu, ters yinelemenin imkansız olduğu ve liste kavramasının da bir seçenek olmadığı bir örnek.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p

0

Yeni listeyi daha sonra kullanacaksanız, elem'i None olarak ayarlayabilir ve daha sonra bu işlemi daha sonraki döngüde değerlendirebilirsiniz.

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

Bu şekilde listeyi kopyalamanıza gerek kalmaz ve anlaşılması daha kolaydır.


-1

bir sayı listesi oluşturun ve 3 ile bölünebilen tüm no'ları kaldırmak istediğinizde,

list_number =[i for i in range(100)]

kullanarak list comprehension, bu yeni bir liste oluşturur ve yeni bellek alanı oluşturur

new_list =[i for i in list_number if i%3!=0]

lambda filterişlevini kullanarak , sonuçta ortaya çıkan yeni liste oluşturulur ve memeory alanı kullanılır

new_list = list(filter(lambda x:x%3!=0, list_number))

yeni liste için bellek alanı kullanmadan ve mevcut listeyi değiştirmeden

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
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.