Lọc một danh sách dựa trên danh sách các booleans


127

Tôi có một danh sách các giá trị mà tôi cần lọc theo các giá trị trong danh sách booleans:

list_a = [1, 2, 4, 6]
filter = [True, False, True, False]

Tôi tạo một danh sách được lọc mới với dòng sau:

filtered_list = [i for indx,i in enumerate(list_a) if filter[indx] == True]

kết quả là:

print filtered_list
[1,4]

Dòng này hoạt động nhưng có vẻ (với tôi) hơi quá mức và tôi đã tự hỏi liệu có cách nào đơn giản hơn để đạt được điều tương tự.


Lời khuyên

Tóm tắt hai lời khuyên tốt được đưa ra trong các câu trả lời dưới đây:

1- Không đặt tên danh sách filternhư tôi đã làm vì đây là chức năng tích hợp.

2- Đừng so sánh những thứ Truenhư tôi đã làm if filter[idx]==True..vì nó không cần thiết. Chỉ cần sử dụng if filter[idx]là đủ.


3
Chỉ cần FYI, đây là một tính toán nguyên thủy song song phổ biến được gọi là nén luồng . (Nó được gọi là 'nguyên thủy' không phải vì nó đơn giản, mà bởi vì nó được sử dụng như một khối xây dựng cho nhiều thuật toán song song khác)
BlueRaja - Danny Pflughoeft

2
Một số lưu ý phong cách: if filter[indx] == TrueĐừng không sử dụng ==nếu bạn muốn kiểm tra danh tính với True, sử dụng is. Dù sao trong trường hợp này, toàn bộ so sánh là vô ích, bạn chỉ có thể sử dụng if filter[indx]. Cuối cùng: không bao giờ sử dụng tên của một tên tích hợp làm tên biến / mô-đun (tôi đang đề cập đến tên filter). Sử dụng một cái gì đó như included, để ifđọc độc đáo ( if included[indx]).
Bakuriu

Câu trả lời:


184

Bạn đang tìm kiếm itertools.compress:

>>> from itertools import compress
>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> list(compress(list_a, fil))
[1, 4]

So sánh thời gian (py3.x):

>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> %timeit list(compress(list_a, fil))
100000 loops, best of 3: 2.58 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]  #winner
100000 loops, best of 3: 1.98 us per loop

>>> list_a = [1, 2, 4, 6]*100
>>> fil = [True, False, True, False]*100
>>> %timeit list(compress(list_a, fil))              #winner
10000 loops, best of 3: 24.3 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]
10000 loops, best of 3: 82 us per loop

>>> list_a = [1, 2, 4, 6]*10000
>>> fil = [True, False, True, False]*10000
>>> %timeit list(compress(list_a, fil))              #winner
1000 loops, best of 3: 1.66 ms per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v] 
100 loops, best of 3: 7.65 ms per loop

Đừng sử dụng filternhư một tên biến, nó là một hàm tích hợp.


@Mehdi Tôi thấy cách Matlab rất không trực quan, nhưng tôi cho rằng nó phụ thuộc vào những gì bạn đã quen.
Ian Goldby

Làm thế nào tôi có thể chọn [2, 6]?
Florent

Tôi hiểu rồi, list(compress(list_a, [not i for i in fill]))nên quay lại[2, 6]
Florent

42

Thích như vậy:

filtered_list = [i for (i, v) in zip(list_a, filter) if v]

Sử dụng ziplà cách pythonic để lặp lại trên nhiều chuỗi song song, mà không cần bất kỳ lập chỉ mục. Giả định này cả hai chuỗi có cùng độ dài (zip dừng sau khi hết thời gian ngắn nhất). Sử dụngitertools cho một trường hợp đơn giản như vậy là hơi quá mức ...

Một điều bạn làm trong ví dụ của bạn, bạn thực sự nên ngừng làm là so sánh mọi thứ với True, điều này thường không cần thiết. Thay vì if filter[idx]==True: ..., bạn chỉ có thể viết if filter[idx]: ....


40

Với numpy:

In [128]: list_a = np.array([1, 2, 4, 6])
In [129]: filter = np.array([True, False, True, False])
In [130]: list_a[filter]

Out[130]: array([1, 4])

hoặc xem câu trả lời của Alex Szatmary nếu list_a có thể là một mảng gọn gàng nhưng không lọc

Numpy thường giúp bạn tăng tốc độ lớn

In [133]: list_a = [1, 2, 4, 6]*10000
In [134]: fil = [True, False, True, False]*10000
In [135]: list_a_np = np.array(list_a)
In [136]: fil_np = np.array(fil)

In [139]: %timeit list(itertools.compress(list_a, fil))
1000 loops, best of 3: 625 us per loop

In [140]: %timeit list_a_np[fil_np]
10000 loops, best of 3: 173 us per loop

Điểm tốt, tôi thích sử dụng NumPyhơn listnơi có thể. Nhưng nếu bạn vẫn cần sử dụng list, bạn có (sử dụng NumPygiải pháp) tạo np.arraytừ cả hai danh sách, sử dụng lập chỉ mục boolean và cuối cùng chuyển đổi mảng trở lại danh sách với tolist()phương thức. Để chính xác, bạn nên đưa những đối tượng đó vào so sánh thời gian. Sau đó, sử dụng itertools.compresssẽ vẫn là giải pháp nhanh nhất.
Nerxis

17

Để làm điều này bằng cách sử dụng numpy, tức là, nếu bạn có một mảng a, thay vì list_a:

a = np.array([1, 2, 4, 6])
my_filter = np.array([True, False, True, False], dtype=bool)
a[my_filter]
> array([1, 4])

3
Nếu bạn biến my_filter thành một mảng boolean, bạn có thể sử dụng lập chỉ mục boolean trực tiếp mà không cần where.
Bas Swinckels


-1

Với python 3 bạn có thể sử dụng list_a[filter]để lấy Truegiá trị. Để có được Falsegiá trị sử dụnglist_a[~filter]

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.