Làm thế nào để loại bỏ các mục từ một danh sách trong khi lặp?


934

Tôi đang lặp lại một danh sách các bộ dữ liệu trong Python và đang cố gắng loại bỏ chúng nếu chúng đáp ứng các tiêu chí nhất định.

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

Tôi nên sử dụng những code_to_remove_tupgì thay thế? Tôi không thể tìm ra cách để loại bỏ các mặt hàng trong thời trang này.


Hầu hết các câu trả lời trên trang này không thực sự giải thích lý do tại sao loại bỏ các yếu tố trong khi lặp qua danh sách tạo ra kết quả lạ, nhưng câu trả lời được chấp nhận trong câu hỏi này có lẽ là một bản sao tốt hơn cho những người mới bắt đầu gặp vấn đề này lần đầu tiên.
ggorlen

Câu trả lời:


827

Bạn có thể sử dụng mức độ hiểu danh sách để tạo danh sách mới chỉ chứa các yếu tố bạn không muốn xóa:

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

Hoặc, bằng cách gán cho lát somelist[:], bạn có thể thay đổi danh sách hiện có để chỉ chứa các mục bạn muốn:

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

Cách tiếp cận này có thể hữu ích nếu có các tài liệu tham khảo khác somelistcần phản ánh các thay đổi.

Thay vì hiểu, bạn cũng có thể sử dụng itertools. Trong Python 2:

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

Hoặc trong Python 3:

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

Để rõ ràng và cho những người tìm thấy việc sử dụng [:]ký hiệu hackish hoặc mờ, đây là một sự thay thế rõ ràng hơn. Về mặt lý thuyết, nó nên thực hiện tương tự đối với không gian và thời gian so với các lớp lót ở trên.

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

Nó cũng hoạt động trong các ngôn ngữ khác có thể không có khả năng thay thế các mục của danh sách Python, với các sửa đổi tối thiểu. Chẳng hạn, không phải tất cả các ngôn ngữ đều chuyển các danh sách trống Falsethành Python như. Bạn có thể thay thế while somelist:cho một cái gì đó rõ ràng hơn như while len(somelist) > 0:.


4
Bạn có thể làm cho nó nhanh hơn nếu bạn biết chỉ một vài người sẽ bị xóa, tức là chỉ xóa những cái đó và để những cái khác thay vì viết lại chúng?
highBandWidth

20
Điều gì xảy ra nếu danh sách của tôi rất lớn và không đủ khả năng để tạo một bản sao?
jpcgt

15
@jpcgt Bạn nên sử dụng somelist[:] = (x for x in somelist if determine(x))điều này sẽ tạo trình tạo có thể không tạo bất kỳ bản sao không cần thiết.
Rostislav Kondratenko

8
@RostislavKondratenko: list_ass_slice()chức năng thực hiện somelist[:]=các cuộc gọi PySequence_Fast()nội bộ. Hàm này luôn trả về một danh sách tức là giải pháp của @Alex Martelli đã sử dụng danh sách thay vì trình tạo có thể hiệu quả hơn
jfs

6
Bạn có quan tâm để giải thích sự khác biệt giữa việc gán mức độ hiểu danh sách cho danh sách và danh sách nhân bản không? Danh sách ban đầu sẽ không somelistbị thay đổi trong cả hai phương pháp chứ?
Bowen Liu

589

Các câu trả lời gợi ý cách hiểu danh sách là CÒN đúng - ngoại trừ việc họ xây dựng một danh sách hoàn toàn mới và sau đó đặt cùng tên với danh sách cũ, họ KHÔNG sửa đổi danh sách cũ. Điều đó khác với những gì bạn đang làm bằng cách xóa có chọn lọc, như trong đề xuất của @ Lennart - nó nhanh hơn, nhưng nếu danh sách của bạn được truy cập qua nhiều tham chiếu thì thực tế là bạn chỉ cần nối lại một trong các tham chiếu và KHÔNG thay đổi đối tượng danh sách chính nó có thể dẫn đến các lỗi tinh vi, tai hại.

May mắn thay, thật dễ dàng để có được cả tốc độ hiểu danh sách VÀ ngữ nghĩa cần thiết của sự thay đổi tại chỗ - chỉ cần mã:

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

Lưu ý sự khác biệt tinh tế với các câu trả lời khác: câu trả lời này KHÔNG được gán cho một tên mã - nó gán cho một lát danh sách chỉ là toàn bộ danh sách, do đó thay thế nội dung danh sách trong cùng một đối tượng danh sách Python , thay vì chỉ nối lại một tham chiếu (từ đối tượng danh sách trước đến đối tượng danh sách mới) như các câu trả lời khác.


1
Làm thế nào để tôi thực hiện cùng một bài tập cắt lát với một dict? Trong Python 2.6?
PaulMcG

11
@Paul: Vì các dicts không có thứ tự, các lát cắt là vô nghĩa đối với các dicts. Nếu bạn muốn thay thế nội dung của dict abằng nội dung của dict b, hãy sử dụng a.clear(); a.update(b).
Sven Marnach

1
Tại sao có thể 'nối lại' một trong các tham chiếu bằng cách thay thế những gì biến tham chiếu gây ra lỗi? Có vẻ như đó chỉ là một vấn đề tiềm năng trong các ứng dụng đa luồng, không phải là luồng đơn.
Derek Dahmer

59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Điều này gán lại xcho kết quả của việc hiểu danh sách, nhưng yvẫn đề cập đến danh sách ban đầu['foo','bar','baz'] . Nếu bạn mong đợi xytham khảo cùng một danh sách, bạn có thể đã giới thiệu các lỗi. Bạn ngăn chặn điều này bằng cách gán cho một lát của toàn bộ danh sách, như Alex hiển thị và tôi hiển thị ở đây : x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Danh sách được sửa đổi tại chỗ. đảm bảo rằng tất cả các tham chiếu đến danh sách (cả xyở đây) đều đề cập đến danh sách mới.
Steven T. Snyder

thực tế, sử dụng filterchức năng cũng tạo ra một danh sách mới, không sửa đổi các yếu tố tại chỗ ... chỉolist[:] = [i for i in olist if not dislike(i)]
John Strood

303

Bạn cần lấy một bản sao của danh sách và lặp lại nó trước, hoặc việc lặp lại sẽ thất bại với những gì có thể là kết quả bất ngờ.

Ví dụ (tùy thuộc vào loại danh sách):

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

Một ví dụ:

>>> 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 Bởi vì cái thứ hai lặp lại trên một bản sao của danh sách. Vì vậy, khi bạn sửa đổi danh sách ban đầu, bạn không sửa đổi bản sao mà bạn lặp đi lặp lại.
Lennart Regebro

3
Điều gì tốt hơn khi làm somelist [:] so với danh sách (somelist)?
Mariusz Jamro 4/2/2015

3
list(somelist)sẽ chuyển đổi một lần lặp thành một danh sách. somelist[:]tạo một bản sao của một đối tượng hỗ trợ cắt. Vì vậy, họ không nhất thiết phải làm điều tương tự. Trong trường hợp này tôi muốn tạo một bản sao của somelistđối tượng, vì vậy tôi sử dụng[:]
Lennart Regebro

33
Lưu ý cho bất cứ ai đọc cái này, đây là RẤT chậm cho danh sách. remove()phải đi qua danh sách WHOLE cho mỗi lần lặp, vì vậy nó sẽ mất mãi mãi.
vitirus 11/2/2015

7
Thời gian lớn không quan trọng khi xử lý danh sách chỉ có một tá mặt hàng. Thông thường rõ ràng và đơn giản để các lập trình viên trong tương lai hiểu là có giá trị hơn nhiều so với hiệu suất.
Steve

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

Bạn cần quay ngược lại nếu không nó giống như cưa nhánh cây mà bạn đang ngồi :-)

Người dùng Python 2: thay thế rangebằng xrangeđể tránh tạo danh sách mã hóa cứng


13
Trong các phiên bản gần đây của Python, bạn có thể thực hiện việc này thậm chí còn gọn gàng hơn bằng cách sử dụng reversed()nội dung dựng sẵn
ncoghlan

16
Reverseed () không tạo ra một danh sách mới, nó tạo ra một trình vòng lặp ngược qua chuỗi được cung cấp. Giống như liệt kê (), bạn phải gói nó trong danh sách () để thực sự lấy danh sách ra khỏi nó. Bạn có thể nghĩ đến việc sắp xếp (), mà không tạo ra một danh sách mới mỗi lần (nó phải, vì vậy nó có thể sắp xếp nó).
ncoghlan

1
@Mauris vì enumeratetrả về một iterator và reversedmong đợi một chuỗi. Tôi đoán bạn có thể làm reversed(list(enumerate(somelist)))nếu bạn không ngại tạo thêm một danh sách trong bộ nhớ.
drevicko

2
Đây là O (N * M) cho mảng, sẽ rất chậm nếu bạn xóa nhiều mục khỏi danh sách lớn. Vì vậy, không nên.
Sam Watkins

2
@SamWatkins Vâng, câu trả lời này là khi bạn loại bỏ một vài yếu tố khỏi một mảng rất lớn. Sử dụng bộ nhớ ít hơn, nhưng nó có thể mchậm hơn nhiều lần.
Navin

52

Hướng dẫn chính thức về Python 2 4.2. "cho Tuyên bố"

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

Phần tài liệu này cho thấy rõ rằng:

  • bạn cần tạo một bản sao của danh sách lặp để sửa đổi nó
  • một cách để làm điều đó là với ký hiệu lát [:]

Nếu bạn cần sửa đổi trình tự bạn đang lặp đi lặp lại trong khi bên trong vòng lặp (ví dụ để sao chép các mục đã chọn), trước tiên bạn nên tạo một bản sao. Lặp lại một chuỗi không hoàn toàn tạo ra một bản sao. Ký hiệu lát cắt làm cho điều này đặc biệt thuận tiện:

>>> 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']

Tài liệu Python 2 7.3. "Tuyên bố cho"

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

Phần tài liệu này nói một lần nữa rằng bạn phải tạo một bản sao và đưa ra một ví dụ loại bỏ thực tế:

Lưu ý: Có một sự tinh tế khi trình tự đang được sửa đổi bởi vòng lặp (điều này chỉ có thể xảy ra đối với các chuỗi có thể thay đổi, tức là danh sách). Một bộ đếm nội bộ được sử dụng để theo dõi mục nào được sử dụng tiếp theo và điều này được tăng lên trên mỗi lần lặp. Khi bộ đếm này đã đạt đến độ dài của chuỗi, vòng lặp chấm dứt. Điều này có nghĩa là nếu bộ xóa mục hiện tại (hoặc trước đó) khỏi chuỗi, mục tiếp theo sẽ bị bỏ qua (vì nó lấy chỉ mục của mục hiện tại đã được xử lý). Tương tự, nếu bộ chèn một mục trong chuỗi trước mục hiện tại, mục hiện tại sẽ được xử lý lại vào lần tiếp theo thông qua vòng lặp. Điều này có thể dẫn đến các lỗi khó chịu có thể tránh được bằng cách tạo một bản sao tạm thời bằng cách sử dụng một lát của toàn bộ chuỗi, ví dụ:

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

Tuy nhiên, tôi không đồng ý với cách triển khai này, vì .remove()phải lặp lại toàn bộ danh sách để tìm giá trị.

Cách giải quyết tốt nhất

Hoặc:

  • bắt đầu một mảng mới từ đầu và .append()trở lại vào cuối: https://stackoverflow.com/a/1207460/895245

    Lần này hiệu quả, nhưng ít không gian hiệu quả hơn vì nó giữ một bản sao của mảng xung quanh trong quá trình lặp.

  • sử dụng delvới chỉ mục: https://stackoverflow.com/a/1207485/895245

    Điều này là không gian hiệu quả hơn vì nó phân phối bản sao mảng, nhưng nó ít hiệu quả hơn về thời gian vì danh sách CPython được thực hiện với mảng động .

    Điều này có nghĩa là loại bỏ mục yêu cầu dịch chuyển tất cả các mục sau trở lại một, đó là O (N).

Nói chung, bạn chỉ muốn .append()chọn tùy chọn nhanh hơn theo mặc định trừ khi bộ nhớ là mối quan tâm lớn.

Python có thể làm điều này tốt hơn không?

Có vẻ như API Python đặc biệt này có thể được cải thiện. So sánh nó, ví dụ, với:

  • Java ListIterator :: xóa tài liệu nào "Cuộc gọi này chỉ có thể được thực hiện một lần cho mỗi cuộc gọi đến lần tiếp theo hoặc trước đó"
  • C ++ std::vector::erasetrả về một interator hợp lệ cho phần tử sau khi loại bỏ

cả hai điều này làm cho rõ ràng rằng bạn không thể sửa đổi một danh sách được lặp đi lặp lại ngoại trừ với chính trình lặp đó và cung cấp cho bạn những cách hiệu quả để làm điều đó mà không cần sao chép danh sách.

Có lẽ lý do cơ bản là các danh sách Python được coi là được hỗ trợ mảng động và do đó, bất kỳ loại loại bỏ nào cũng sẽ không hiệu quả về mặt thời gian, trong khi Java có hệ thống phân cấp giao diện đẹp hơn với cả hai ArrayListLinkedListtriển khai ListIterator.

Dường như không có loại danh sách được liên kết rõ ràng trong Python stdlib: Danh sách liên kết Python


48

Cách tiếp cận tốt nhất của bạn cho một ví dụ như vậy sẽ là một sự hiểu biết danh sách

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

Trong trường hợp bạn đang làm một cái gì đó phức tạp hơn là gọi một determinehàm, tôi thích xây dựng một danh sách mới và chỉ cần thêm vào nó khi tôi đi. Ví dụ

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

Sao chép danh sách bằng cách sử dụng removecó thể làm cho mã của bạn trông gọn gàng hơn một chút, như được mô tả trong một trong những câu trả lời dưới đây. Bạn chắc chắn không nên làm điều này cho các danh sách cực lớn, vì điều này liên quan đến việc sao chép toàn bộ danh sách trước tiên và cũng thực hiện một O(n) removethao tác cho từng phần tử bị xóa, biến đây thành một O(n^2)thuật toán.

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

37

Đối với những người thích lập trình chức năng:

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

hoặc là

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

1. Danh sách hiểu và biểu thức trình tạo được mượn từ Haskell, một ngôn ngữ chức năng thuần túy; Chúng chính xác như chức năng filter, và nhiều Pythonic. 2. Nếu bạn cần lambdasử dụng maphoặc filter, danh sách comp hoặc genexpr luôn là lựa chọn tốt hơn; mapfiltercó thể nhanh hơn một chút khi hàm biến đổi / biến vị ngữ là một Python tích hợp sẵn được triển khai trong C và iterable không phải là nhỏ, nhưng chúng luôn chậm hơn khi bạn cần một lambdalistcomp / genexpr có thể tránh được.
ShadowRanger

13

Tôi cần phải làm điều này với một danh sách lớn và việc sao chép danh sách có vẻ tốn kém, đặc biệt là trong trường hợp của tôi, số lần xóa sẽ ít so với các mục còn lại. Tôi đã thực hiện phương pháp cấp thấp này.

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

Những gì tôi không biết là hiệu quả của một vài lần xóa so với sao chép một danh sách lớn. Hãy bình luận nếu bạn có bất kỳ cái nhìn sâu sắc.


Trong trường hợp của tôi, tôi cần chuyển các yếu tố 'không mong muốn' đó sang một danh sách khác. Bạn có nhận xét nào mới về giải pháp này không? Tôi cũng nghĩ rằng tốt hơn là sử dụng một số xóa thay vì sao chép danh sách.
gustavigsascoh

Đây là câu trả lời đúng nếu hiệu suất là một vấn đề (mặc dù giống như @Alexey). Điều đó nói rằng, việc lựa chọn listlàm cấu trúc dữ liệu ở vị trí đầu tiên cần được xem xét cẩn thận vì việc xóa khỏi giữa danh sách sẽ mất thời gian tuyến tính theo độ dài của danh sách. Nếu bạn không thực sự cần truy cập ngẫu nhiên vào mục tuần tự thứ k, có thể xem xét OrderedDict?
tối đa

@GVelascoh tại sao không tạo newlist = [], và sau đó newlist.append(array[i])chỉ trước del array[i]?
tối đa

2
Lưu ý rằng đây có thể là thời gian không hiệu quả: nếu list()là danh sách được liên kết, truy cập ngẫu nhiên rất tốn kém, nếu list()là một mảng, việc xóa rất tốn kém vì chúng yêu cầu di chuyển tất cả các yếu tố sau về phía trước. Một trình vòng lặp tốt có thể làm cho mọi thứ tốt cho việc thực hiện danh sách được liên kết. Điều này tuy nhiên có thể là không gian hiệu quả.
Ciro Santilli 冠状 病毒 审查 事件

10

Cũng có thể là thông minh khi chỉ tạo một danh sách mới nếu mục danh sách hiện tại đáp ứng các tiêu chí mong muốn.

vì thế:

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

và để tránh phải mã lại toàn bộ dự án với tên danh sách mới:

originalList[:] = newList

lưu ý, từ tài liệu Python:

copy.copy (x) Trả về một bản sao nông của x.

copy.deepcopy (x) Trả lại một bản sao sâu của x.


3
Điều này cho biết thêm không có thông tin mới không có trong câu trả lời được chấp nhận năm trước.
Mark Amery

2
Thật đơn giản và chỉ là một cách khác để xem xét vấn đề @MarkAmery. Nó ít cô đọng hơn cho những người không thích cú pháp mã hóa nén.
ntk4

9

Câu trả lời này ban đầu được viết để trả lời cho một câu hỏi đã được đánh dấu là trùng lặp: Xóa tọa độ khỏi danh sách trên python

Có hai vấn đề trong mã của bạn:

1) Khi sử dụng remove (), bạn cố xóa các số nguyên trong khi bạn cần xóa một tuple.

2) Vòng lặp for sẽ bỏ qua các mục trong danh sách của bạn.

Hãy xem những gì xảy ra khi chúng tôi thực thi mã của bạn:

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

Vấn đề đầu tiên là bạn chuyển cả 'a' và 'b' để xóa (), nhưng remove () chỉ chấp nhận một đối số duy nhất. Vậy làm thế nào chúng ta có thể khiến remove () hoạt động đúng với danh sách của bạn? Chúng tôi cần tìm ra từng yếu tố trong danh sách của bạn. Trong trường hợp này, mỗi người là một tuple. Để thấy điều này, hãy truy cập một yếu tố của danh sách (lập chỉ mục bắt đầu từ 0):

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

Aha! Mỗi phần tử của L1 thực sự là một tuple. Vì vậy, đó là những gì chúng ta cần phải vượt qua để loại bỏ (). Các bộ dữ liệu trong python rất dễ dàng, chúng chỉ đơn giản được tạo bằng cách đặt các giá trị trong ngoặc đơn. "A, b" không phải là một tuple, nhưng "(a, b)" là một tuple. Vì vậy, chúng tôi sửa đổi mã của bạn và chạy lại nó:

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

Mã này chạy mà không có bất kỳ lỗi nào, nhưng hãy xem danh sách mà nó xuất ra:

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

Tại sao (1, -2) vẫn còn trong danh sách của bạn? Hóa ra việc sửa đổi danh sách trong khi sử dụng một vòng lặp để lặp lại nó là một ý tưởng rất tồi mà không được chăm sóc đặc biệt. Lý do (1, -2) vẫn còn trong danh sách là vị trí của từng mục trong danh sách đã thay đổi giữa các lần lặp của vòng lặp for. Hãy xem điều gì xảy ra nếu chúng ta cung cấp mã trên một danh sách dài hơn:

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

Như bạn có thể suy ra từ kết quả đó, mỗi khi câu lệnh điều kiện đánh giá là đúng và một mục danh sách bị loại bỏ, lần lặp tiếp theo của vòng lặp sẽ bỏ qua việc đánh giá mục tiếp theo trong danh sách vì các giá trị của nó hiện nằm ở các chỉ số khác nhau.

Giải pháp trực quan nhất là sao chép danh sách, sau đó lặp lại danh sách ban đầu và chỉ sửa đổi bản sao. Bạn có thể thử làm như vậy:

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)

Tuy nhiên, đầu ra sẽ giống hệt như trước:

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

Điều này là do khi chúng ta tạo L2, python không thực sự tạo ra một đối tượng mới. Thay vào đó, nó chỉ tham chiếu L2 đến cùng một đối tượng như L1. Chúng tôi có thể xác minh điều này với 'là' khác với chỉ "bằng" (==).

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

Chúng ta có thể tạo một bản sao thực sự bằng cách sử dụng copy.copy (). Sau đó, mọi thứ hoạt động như mong đợi:

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

Cuối cùng, có một giải pháp sạch hơn là phải tạo một bản sao hoàn toàn mới của L1. Hàm đảo ngược ():

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

Thật không may, tôi không thể mô tả đầy đủ cách thức hoạt động của Reverse (). Nó trả về một đối tượng 'listreverseiterator' khi một danh sách được truyền cho nó. Đối với các mục đích thực tế, bạn có thể nghĩ về nó như tạo ra một bản sao đảo ngược của đối số của nó. Đây là giải pháp tôi khuyên dùng.


4

Nếu bạn muốn làm bất cứ điều gì khác trong quá trình lặp lại, có thể tốt hơn để có được cả chỉ mục (đảm bảo bạn có thể tham chiếu nó, ví dụ nếu bạn có một danh sách các ký tự) và nội dung mục danh sách thực tế.

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]

enumeratecung cấp cho bạn quyền truy cập vào mục và chỉ mục cùng một lúc. reversedlà để các chỉ số mà bạn sẽ xóa sau này không thay đổi về bạn.


Tại sao việc lấy chỉ mục lại có liên quan nhiều hơn trong trường hợp bạn có một danh sách các dấu hiệu hơn trong trường hợp của bất kỳ loại danh sách nào khác? Điều này không có ý nghĩa như tôi có thể nói.
Đánh dấu Amery


4

Hầu hết các câu trả lời ở đây muốn bạn tạo một bản sao của danh sách. Tôi đã có một trường hợp sử dụng trong đó danh sách khá dài (110K mặt hàng) và thông minh hơn là tiếp tục giảm danh sách thay thế.

Trước hết bạn sẽ cần thay thế vòng lặp foreach bằng vòng lặp while ,

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

Giá trị của ikhông bị thay đổi trong khối if vì bạn sẽ muốn nhận giá trị của mục mới TỪ CHỈ CÙNG, khi mục cũ bị xóa.


3

Bạn có thể thử quay vòng ngược lại để đối với some_list bạn sẽ làm một cái gì đó như:

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)

Bằng cách này, chỉ mục được căn chỉnh và không bị ảnh hưởng bởi các cập nhật danh sách (bất kể bạn có bật phần tử cong hay không).


Vòng lặp reversed(list(enumerate(some_list)))sẽ đơn giản hơn so với chỉ số điện toán cho chính bạn.
Mark Amery

@MarkAmery đừng nghĩ rằng bạn có thể thay đổi danh sách theo cách này.
Queequeg

3

Một giải pháp khả thi, hữu ích nếu bạn muốn không chỉ loại bỏ một số thứ, mà còn làm một cái gì đó với tất cả các yếu tố trong một vòng lặp:

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

Bạn thực sự chỉ nên sử dụng hiểu. Chúng dễ hiểu hơn nhiều.
Beefster 15/03/18

Điều gì sẽ xảy ra nếu tôi muốn loại bỏ badmọi thứ, làm một cái gì đó với nó và cũng làm một cái gì đó với goodnhững thứ trong một vòng lặp?
Alexey

1
Trên thực tế, tôi nhận ra có một sự thông minh ở đây khi bạn tạo một bản sao của danh sách với một lát cắt mở ( alist[:]) Và vì bạn có thể đang làm một cái gì đó lạ mắt, nó thực sự có một trường hợp sử dụng. Sửa đổi tốt là tốt. Lấy upvote của tôi.
Beefster

2

Tôi cần phải làm một cái gì đó tương tự và trong trường hợp của tôi, vấn đề là bộ nhớ - tôi cần hợp nhất nhiều đối tượng dữ liệu trong một danh sách, sau khi thực hiện một số thứ với chúng, như một đối tượng mới và cần loại bỏ từng mục tôi đang hợp nhất tránh sao chép tất cả chúng và làm nổ tung bộ nhớ. Trong trường hợp của tôi, có các đối tượng trong từ điển thay vì danh sách hoạt động tố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:

Tôi đã viết một thư viện cho phép bạn làm điều này:

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'

Tốt nhất là sử dụng một phương pháp khác nếu có thể không yêu cầu sửa đổi lần lặp của bạn trong khi lặp qua nó, nhưng đối với một số thuật toán, nó có thể không đơn giản như vậy. Và vì vậy, nếu bạn chắc chắn rằng bạn thực sự muốn mẫu mã được mô tả trong câu hỏi ban đầu, điều đó là có thể.

Nên làm việc trên tất cả các chuỗi đột biến không chỉ danh sách.


Câu trả lời đầy đủ:

Chỉnh sửa: Ví dụ mã cuối cùng trong câu trả lời này đưa ra trường hợp sử dụng cho lý do tại sao đôi khi bạn có thể muốn sửa đổi danh sách tại chỗ thay vì sử dụng cách hiểu danh sách. Phần đầu tiên của câu trả lời đóng vai trò là hướng dẫn về cách một mảng có thể được sửa đổi tại chỗ.

Giải pháp tiếp theo từ câu trả lời này (cho một câu hỏi liên quan) từ người gửi. Điều này giải thích cách chỉ mục mảng được cập nhật trong khi lặp qua danh sách đã được sửa đổi. Giải pháp dưới đây được thiết kế để theo dõi chính xác chỉ số mảng ngay cả khi danh sách được sửa đổi.

Tải về fluidIter.pytừ đây https://github.com/alanbacon/FluidIterator , nó chỉ là một file duy nhất nên không cần cài đặt git. Không có trình cài đặt, do đó bạn sẽ cần đảm bảo rằng tệp nằm trong đường dẫn python của chính bạn. Mã này đã được viết cho python 3 và chưa được kiểm tra trên python 2.

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

Điều này sẽ tạo ra đầu ra sau đây:

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]

Ở trên chúng tôi đã sử dụng popphương thức trên đối tượng danh sách chất lỏng. Phương pháp iterable phổ biến khác cũng được thực hiện như del fluidL[i], .remove, .insert, .append, .extend. Danh sách cũng có thể được sửa đổi bằng cách sử dụng các lát ( sortreversecác phương thức không được thực hiện).

Điều kiện duy nhất là bạn chỉ phải sửa đổi danh sách tại chỗ, nếu tại bất kỳ thời điểm nào fluidLhoặc lđược gán lại cho một đối tượng danh sách khác, mã sẽ không hoạt động. fluidLĐối tượng ban đầu vẫn sẽ được sử dụng bởi vòng lặp for nhưng sẽ vượt ra ngoài phạm vi để chúng ta sửa đổi.

I E

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

Nếu chúng ta muốn truy cập giá trị chỉ mục hiện tại của danh sách, chúng ta không thể sử dụng phép liệt kê, vì điều này chỉ tính số lần vòng lặp for đã chạy. Thay vào đó chúng ta sẽ sử dụng đối tượng iterator trực tiếp.

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

Điều này sẽ xuất ra như sau:

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]

Các FluidIterablelớp học chỉ cung cấp một wrapper cho đối tượng danh sách ban đầu. Đối tượng ban đầu có thể được truy cập như một thuộc tính của đối tượng chất lỏng như vậy:

originalList = fluidArr.fixedIterable

Nhiều ví dụ / bài kiểm tra có thể được tìm thấy trong if __name__ is "__main__":phần ở dưới cùng của fluidIter.py. Đây là những giá trị xem xét bởi vì họ giải thích những gì xảy ra trong các tình huống khác nhau. Chẳng hạn như: Thay thế một phần lớn của danh sách bằng một lát cắt. Hoặc sử dụng (và sửa đổi) cùng một lần lặp trong các vòng lặp lồng nhau.

Như tôi đã nói để bắt đầu: đây là một giải pháp phức tạp sẽ làm tổn thương tính dễ đọc của mã của bạn và làm cho việc gỡ lỗi trở nên khó khăn hơn. Do đó, các giải pháp khác như hiểu danh sách được đề cập trong câu trả lời của David Raznick nên được xem xét trước tiên. Điều đó đang được nói, tôi đã tìm thấy thời gian mà lớp này hữu ích với tôi và dễ sử dụng hơn là theo dõi các chỉ số của các yếu tố cần xóa.


Chỉnh sửa: Như đã đề cập trong các bình luận, câu trả lời này không thực sự gây ra vấn đề mà phương pháp này cung cấp giải pháp. Tôi sẽ cố gắng giải quyết điều đó ở đây:

Việc hiểu danh sách cung cấp một cách để tạo ra một danh sách mới nhưng các cách tiếp cận này có xu hướng xem xét từng yếu tố một cách riêng lẻ hơn là toàn bộ trạng thái hiện tại của danh sách.

I E

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

Nhưng nếu kết quả của testFuncphụ thuộc vào các yếu tố đã được thêm vào newListthì sao? Hoặc các yếu tố vẫn còn trong oldListđó có thể được thêm vào tiếp theo? Vẫn có thể có một cách để sử dụng một sự hiểu biết danh sách nhưng nó sẽ bắt đầu mất đi sự thanh lịch của nó, và đối với tôi cảm thấy dễ dàng hơn để sửa đổi một danh sách tại chỗ.

Mã dưới đây là một ví dụ về thuật toán gặp phải vấn đề trên. Thuật toán sẽ giảm một danh sách để không có phần tử nào là bội số của bất kỳ phần tử nào khác.

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)

Đầu ra và danh sách giảm cuối cùng được hiển thị dưới đây

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]

Thật khó để biết liệu điều này có được thiết kế quá mức hay không bởi vì nó không rõ vấn đề mà nó đang cố gắng giải quyết; những gì loại bỏ các yếu tố sử dụng phương pháp này đạt được mà some_list[:] = [x for x in some_list if not some_condition(x)]không đạt được? Không có câu trả lời cho điều đó, tại sao mọi người nên tin rằng việc tải xuống và sử dụng thư viện 600 dòng của bạn hoàn chỉnh với lỗi chính tả và mã nhận xét là giải pháp tốt hơn cho vấn đề của họ so với một dòng? -1.
Mark Amery

@MarkAmery. Trường hợp sử dụng chính cho khi này là khi cố gắng xác định xem một mục nên được loại bỏ (hoặc thêm hoặc di chuyển) không chỉ dựa vào chính mục đó, mà dựa trên trạng thái của một mục khác trong danh sách hoặc trạng thái của danh sách dưới dạng toàn bộ. Ví dụ, nó không phải là có thể với comprehensions danh sách để viết một cái gì đó giống như some_list[:] = [x for x in some_list if not some_condition(y)]nơi ylà một yếu tố danh sách khác nhau từ x. Cũng không thể viết some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Cộng hưởng

2

Phương pháp hiệu quả nhất là hiểu danh sách, nhiều người chỉ ra trường hợp của họ, tất nhiên, đó cũng là một cách tốt để vượt iteratorqua filter.

Filternhận được một chức năng và một chuỗi. Filterlần lượt áp dụng hàm đã truyền cho từng phần tử và sau đó quyết định giữ lại hoặc loại bỏ phần tử tùy thuộc vào giá trị trả về của hàm là Truehay False.

Có một ví dụ (lấy tỷ lệ cược trong tuple):

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

Thận trọng: Bạn cũng không thể xử lý các trình vòng lặp. Lặp đi lặp lại đôi khi tốt hơn trình tự.


2

cho vòng lặp sẽ được lặp qua chỉ số ..

xem xét bạn có một danh sách,

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

bạn đã sử dụng biến danh sách được gọi lis. và bạn sử dụng tương tự để loại bỏ ..

biến của bạn

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

trong lần lặp thứ 5,

số 35 của bạn không phải là số nguyên tố nên bạn đã xóa nó khỏi danh sách.

lis.remove(y)

và sau đó giá trị tiếp theo (65) chuyển sang chỉ mục trước đó.

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

Vì vậy, lần lặp thứ 4 thực hiện con trỏ di chuyển lên 5 ..

đó là lý do tại sao vòng lặp của bạn không bao gồm 65 kể từ khi nó được chuyển sang chỉ mục trước đó.

vì vậy bạn không nên tham chiếu danh sách vào một biến khác vẫn tham chiếu gốc thay vì sao chép.

ite = lis #dont do it will reference instead copy

sao chép danh sách bằng list[::]

bây giờ bạn sẽ cho,

[5, 7, 13, 29]

Vấn đề là bạn đã xóa một giá trị khỏi danh sách trong khi lặp lại thì chỉ mục danh sách của bạn sẽ sụp đổ.

để bạn có thể thử hiểu thay thế.

hỗ trợ tất cả các lần lặp như like, list, tuple, dict, string, v.v.


Điều này giúp tôi hiểu tại sao mã của tôi bị lỗi.
Wahid Sadik

2

Nếu bạn muốn xóa các thành phần khỏi danh sách trong khi lặp, hãy sử dụng vòng lặp while để bạn có thể thay đổi chỉ mục hiện tại và chỉ mục kết thúc sau mỗi lần xóa.

Thí dụ:

i = 0
length = len(list1)

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

    i += 1

1

Các câu trả lời khác là chính xác rằng thường là một ý tưởng tồi để xóa khỏi danh sách mà bạn đang lặp lại. Lặp lại ngược lại để tránh những cạm bẫy, nhưng việc theo dõi mã đó khó khăn hơn nhiều, vì vậy, thông thường bạn nên sử dụng một cách hiểu danh sách hoặc filter.

Tuy nhiên, có một trường hợp an toàn khi xóa các phần tử khỏi chuỗi mà bạn đang lặp: nếu bạn chỉ xóa một mục trong khi bạn lặp lại. Điều này có thể được đảm bảo bằng cách sử dụng a returnhoặc a break. Ví dụ:

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

Điều này thường dễ hiểu hơn so với việc hiểu danh sách khi bạn thực hiện một số thao tác với tác dụng phụ trên mục đầu tiên trong danh sách đáp ứng một số điều kiện và sau đó xóa mục đó khỏi danh sách ngay sau đó.


1

Tôi có thể nghĩ ra ba cách tiếp cận để giải quyết vấn đề của bạn. Ví dụ, tôi sẽ tạo một danh sách các bộ dữ liệu ngẫu nhiên somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Điều kiện mà tôi chọn là sum of elements of a tuple = 15. Trong danh sách cuối cùng, chúng ta sẽ chỉ có những bộ dữ liệu có tổng không bằng 15.

Những gì tôi đã chọn là một ví dụ được chọn ngẫu nhiên. Cảm thấy tự do để thay đổi các danh sách của các bộ và các điều kiện mà tôi đã chọn.

Phương pháp 1.> Sử dụng khung mà bạn đã đề xuất (trong đó một mã điền vào mã bên trong vòng lặp for). Tôi sử dụng một mã nhỏ delđể xóa một tuple đáp ứng điều kiện đã nói. Tuy nhiên, phương pháp này sẽ bỏ lỡ một tuple (thỏa mãn điều kiện đã nói) nếu hai tuple được đặt liên tiếp đáp ứng điều kiện đã cho.

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

Phương pháp 2.> Xây dựng một danh sách mới có chứa các phần tử (bộ dữ liệu) trong đó điều kiện đã cho không được đáp ứng (đây là điều tương tự như loại bỏ các phần tử của danh sách nơi điều kiện đã cho được đáp ứng). Sau đây là mã cho rằng:

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

Phương pháp 3.> Tìm các chỉ mục nơi điều kiện đã cho được đáp ứng, sau đó sử dụng các phần tử loại bỏ (bộ dữ liệu) tương ứng với các chỉ mục đó. Sau đây là mã cho điều đó.

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

Phương pháp 1 và phương pháp 2 nhanh hơn phương pháp 3 . Phương thức 2 và phương thức 3 hiệu quả hơn phương thức1. Tôi thích phương thức2 . Ví dụ đã nói ở trên,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


0

Đối với bất cứ điều gì có tiềm năng thực sự lớn, tôi sử dụng như sau.

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)

Điều đó sẽ nhanh hơn đáng kể so với bất cứ điều gì khác.


Từ những gì tôi đo được, NumPy bắt đầu nhanh hơn cho danh sách hơn 20 phần tử và đạt mức lọc nhanh hơn 12 lần cho danh sách lớn gồm 1000 phần tử trở lên.
Georgy

0

Trong một số trường hợp, khi bạn thực hiện nhiều hơn chỉ đơn giản là lọc một danh sách một mục, bạn muốn phép lặp của mình thay đổi trong khi lặp.

Dưới đây là một ví dụ trong đó việc sao chép danh sách trước là không chính xác, việc lặp lại là không thể và việc hiểu danh sách cũng không phải là một lựa chọn.

""" 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

Nếu bạn sẽ sử dụng danh sách mới sau, bạn có thể chỉ cần đặt elem thành Không, sau đó đánh giá nó trong vòng lặp sau, như thế này

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

Theo cách này, bạn không cần sao chép danh sách và dễ hiểu hơn.


-1

đưa ra một danh sách các số và bạn muốn xóa tất cả số không chia hết cho 3,

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

bằng cách sử dụng list comprehension, điều này sẽ tạo ra một danh sách mới và tạo không gian bộ nhớ mới

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

bằng cách sử dụng lambda filterchức năng, điều này sẽ tạo ra danh sách mới kết quả và tiêu thụ không gian ghi nhớ

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

không tốn dung lượng bộ nhớ cho danh sách mới và sửa đổi danh sách hiện có

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.