2010-10-16 43 views
8
# I have 3 lists: 
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
# I want to create another that is L1 minus L2's memebers and L3's memebers, so: 
L4 = (L1 - L2) - L3 # Of course this isn't going to work 

Tôi tự hỏi, cách "chính xác" để thực hiện việc này là gì. Tôi có thể làm điều đó theo nhiều cách khác nhau, nhưng hướng dẫn về phong cách của Python cho biết chỉ nên có 1 cách chính xác để làm mỗi thứ. Tôi chưa bao giờ biết đây là cái gì.Python - xóa các mục khỏi danh sách

+3

Không có cách nào chính xác để thực hiện việc này cho đến khi bạn quyết định xem bạn có quan tâm hay không quan tâm đến các bản sao và đặt hàng. Có lẽ một số loại danh sách hiểu hoặc thiết lập công việc tùy thuộc vào những gì bạn quan tâm. – istruble

+1

Ngoài ra, có thể giả định rằng tất cả các mục trong danh sách sẽ có thể băm tất cả thời gian không? Nếu không, hoặc đôi khi không, điều đó rất có ý nghĩa. – martineau

+1

Tại sao bạn không sử dụng bộ để bắt đầu? Sau đó, "số học" của bạn sẽ hoạt động. – poke

Trả lời

10

Dưới đây là một số cố gắng:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ] # parens for clarity 

tmpset = set(L2 + L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Bây giờ tôi đã có một chút thời gian để suy nghĩ, Tôi nhận ra rằng điều L2 + L3 tạo ra một danh sách tạm thời ngay lập tức bị vứt bỏ. Vì vậy, một cách tốt hơn là:

tmpset = set(L2) 
tmpset.update(L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Cập nhật: tôi thấy một số tuyên bố ngông cuồng bị ném xung quanh về hiệu suất, và tôi muốn khẳng định rằng giải pháp của tôi là đã càng nhanh càng tốt. Tạo các kết quả trung gian, cho dù chúng là các danh sách trung gian hoặc các trình vòng lặp trung gian mà phải được gọi lại nhiều lần, sẽ chậm hơn, luôn luôn, đơn giản hơn là chỉ cho các số L2L3 để thiết lập lặp lại trực tiếp như tôi đã làm ở đây.

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]' 
10000 loops, best of 3: 39.7 usec per loop 

Tất cả các lựa chọn thay thế khác (tôi có thể nghĩ) sẽ nhất thiết phải chậm hơn. Làm các vòng chính mình, ví dụ, thay vì để các nhà xây dựng set() làm cho họ, cho biết thêm chi phí:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]' 
10000 loops, best of 3: 46.4 usec per loop 

Sử dụng vòng lặp, sẽ tất cả các callbacks nhà nước tiết kiệm và họ đòi hỏi, sẽ rõ ràng thậm chí còn đắt hơn:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \ 
    'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop 

vì vậy, tôi tin rằng câu trả lời tôi đã đêm qua vẫn còn xa và đi (đối với giá trị của "xa xôi" lớn hơn xung quanh 5μsec, rõ ràng) là tốt nhất, trừ trường hợp người hỏi sẽ có bản sao trong L1 và muốn chúng được loại bỏ một lần cho mỗi lần trùng lặp xuất hiện trong một trong các danh sách khác .

+0

Nó có thể có khả năng eke ra một số hiệu suất hơn bằng cách xây dựng một tập hợp đông lạnh từ một chuỗi hai vòng lặp danh sách. – intuited

+0

Không, các bộ đông lạnh có tốc độ giống với tốc độ bình thường, nhưng thường đòi hỏi nhiều chi phí hơn để tạo vì bạn phải tạo kết quả trung gian hoặc lặp lại nếu, ở đây, bạn đang tạo chúng từ một số lần lặp đầu vào. –

0

Giả sử danh sách cá nhân của bạn sẽ không chứa bản sao .... Sử dụng SetDifference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
print(list(set(L1) - set(L2) - set(L3))) 
+2

Điều này sẽ mất trật tự. –

+1

Vâng, sự khác biệt chính giữa danh sách và tập hợp ... – mepcotterell

+1

Nếu đơn đặt hàng/trùng lặp KHÔNG phải là vấn đề, đây là tùy chọn rõ ràng nhất, IMO –

0

Thực hiện các thao tác đó trong Danh sách có thể cản trở hiệu suất của chương trình của bạn rất sớm. Điều gì xảy ra là với mỗi loại bỏ, danh sách hoạt động làm một malloc & tươi di chuyển các yếu tố xung quanh. Điều này có thể tốn kém nếu bạn có một danh sách rất lớn hay cách khác. Vì vậy, tôi sẽ đề xuất điều này -

Tôi giả sử danh sách của bạn có các yếu tố độc đáo. Nếu không, bạn cần phải duy trì một danh sách trong dict của bạn có giá trị trùng lặp. Dù sao cho dữ liệu được cung cấp của bạn, ở đây nó là-

PHƯƠNG PHÁP 1

d = dict() 
for x in L1: d[x] = True 

# Check if L2 data is in 'd' 
for x in L2: 
    if x in d: 
     d[x] = False 

for x in L3: 
    if x in d: 
     d[x] = False 

# Finally retrieve all keys with value as True. 
final_list = [x for x in d if d[x]] 

PHƯƠNG PHÁP 2 Nếu tất cả những gì trông giống như quá nhiều mã. Sau đó, bạn có thể thử sử dụng set. Nhưng theo cách này danh sách của bạn sẽ mất tất cả các phần tử trùng lặp.

final_set = set.difference(set(L1),set(L2),set(L3)) 
final_list = list(final_set) 
+0

Việc hiểu danh sách không thực hiện loại bỏ các hoạt động đắt tiền. – aaronasterling

+0

#aaron yes Tôi biết. Tôi đã đề cập đến giải pháp được đăng bởi Santiago. –

+1

Hey, về cơ bản bạn đang sử dụng từ điển làm bộ. Họ có một loại dữ liệu hoàn toàn khác cho rằng: http://docs.python.org/library/stdtypes.html#types-set – intuited

0

này có thể ít pythonesque hơn câu trả lời list-hiểu, nhưng có một cái nhìn đơn giản hơn với nó:

l1 = [ ... ] 
l2 = [ ... ] 

diff = list(l1) # this copies the list 
for element in l2: 
    diff.remove(element) 

Ưu điểm ở đây là chúng ta giữ gìn trật tự của danh sách, và nếu có các phần tử trùng lặp, chúng tôi chỉ xóa một phần tử cho mỗi lần xuất hiện trong l2.

+1

Đó là cực kỳ tốn kém và ngược lại, nhiều hơn phức tạp để xem xét hơn là hiểu đơn giản. – aaronasterling

+0

Có vẻ như vấn đề về hương vị. Tôi thích danh sách hiểu biết rất nhiều, tôi thực sự có xu hướng lạm dụng chúng, nhưng tôi không nghĩ rằng "n cho n trong L nếu n không trong ..." là tốt đẹp trong mắt. Bằng cách này hay cách khác, đó là, tôi sẽ thừa nhận, tốn kém tính toán. – slezica

6

cập nhật ::: bài đăng chứa tham chiếu đến các cáo buộc sai về hiệu suất kém của các tập so với hàng chục người. Tôi duy trì rằng nó vẫn còn hợp lý để sử dụng một frozenset trong trường hợp này, mặc dù không cần phải băm các thiết lập chính nó, chỉ vì nó đúng ngữ nghĩa hơn. Mặc dù, trong thực tế, tôi có thể không bận tâm việc gõ thêm 6 ký tự. Tôi không cảm thấy có động lực để đi qua và chỉnh sửa bài đăng, vì vậy, chỉ cần được thông báo rằng liên kết "cáo buộc" liên kết đến một số thử nghiệm không chính xác. Các chi tiết gory được băm ra trong các ý kiến. ::: cập nhật

Các đoạn thứ hai của mã posted bởi Brandon Craig Rhodes là khá tốt, nhưng khi ông không trả lời đề nghị của tôi về việc sử dụng một frozenset (tốt, không phải khi tôi bắt đầu viết những dòng này, dù sao) , Tôi sẽ tiếp tục và tự đăng nó lên.

Toàn bộ cơ sở của cam kết trong tầm tay là kiểm tra xem mỗi chuỗi giá trị (L1) có trong bộ giá trị khác không; tập hợp các giá trị đó là nội dung của L2L3. Việc sử dụng từ "set" trong câu đó là: mặc dù L2L3list s, chúng tôi không thực sự quan tâm đến các thuộc tính giống như danh sách của chúng, như thứ tự giá trị của chúng hoặc số lượng của chúng chứa. Chúng tôi chỉ quan tâm đến số đặt (có một lần nữa) các giá trị mà chúng chứa chung.

Nếu bộ giá trị đó được lưu trữ dưới dạng danh sách, bạn phải xem từng phần tử một danh sách, kiểm tra từng phần tử. Nó tương đối tốn thời gian, và đó là ngữ nghĩa xấu: một lần nữa, nó là một "bộ" các giá trị, không phải là một danh sách. Vì vậy, Python có các loại thiết lập gọn gàng giữ một loạt các giá trị duy nhất, và có thể nhanh chóng cho bạn biết nếu một số giá trị là trong họ hay không. Điều này hoạt động khá giống với cách mà các loại dict của python hoạt động khi bạn đang tìm kiếm một khóa.

Sự khác biệt giữa bộfrozensets là các bộ có thể thay đổi, có nghĩa là chúng có thể được sửa đổi sau khi tạo. Tài liệu trên cả hai loại là here.

Vì tập hợp chúng ta cần tạo, liên kết của các giá trị được lưu trữ trong L2L3, sẽ không bị sửa đổi khi được tạo, phù hợp ngữ nghĩa để sử dụng loại dữ liệu không thay đổi. Điều này cũng có một số lợi ích hiệu suất. Vâng, nó có ý nghĩa rằng nó sẽ có một số lợi thế; nếu không, tại sao Python có frozenset làm nội trang dựng sẵn?

cập nhật ...

Brandon đã trả lời câu hỏi này: lợi thế thực sự của bộ đông lạnh là tính bất biến của họ làm cho nó có thể cho họ được hashable, cho phép họ được phím từ điển hoặc các thành viên của bộ khác .

Tôi đã chạy một số thử nghiệm định thời không chính thức so sánh tốc độ tạo và tra cứu các tập hợp có thể thay đổi và có thể thay đổi tương đối lớn (3000 phần tử); không có nhiều khác biệt. Điều này mâu thuẫn với liên kết trên, nhưng hỗ trợ những gì Brandon nói về chúng giống hệt nhau nhưng đối với khía cạnh của sự biến đổi.

... cập nhật

Bây giờ, vì frozensets là không thay đổi, họ không có một phương pháp cập nhật. Brandon đã sử dụng phương pháp set.update để tránh tạo và sau đó loại bỏ danh sách tạm thời trên đường để thiết lập tạo; Tôi sẽ đi theo một cách tiếp cận khác.

items = (item for lst in (L2, L3) for item in lst) 

generator expression Điều này làm cho items một iterator qua, liên tục, nội dung của L2L3. Không chỉ vậy, nhưng nó làm nó mà không tạo ra một danh sách toàn bộ các đối tượng trung gian. Sử dụng các biểu thức lồng nhau for trong các trình tạo là một chút khó hiểu, nhưng tôi quản lý để giữ cho nó được sắp xếp bằng cách nhớ rằng chúng lồng nhau theo thứ tự mà chúng sẽ nếu bạn viết thực tế cho các vòng lặp, ví dụ:

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

Đó generator function tương đương với khái niệm máy phát điện mà chúng ta gán cho items. Vâng, ngoại trừ đó là một định nghĩa hàm parametrized thay vì gán trực tiếp cho một biến.

Dù sao, đủ tiêu hóa. Thỏa thuận lớn với máy phát điện là họ không thực sự làm bất cứ điều gì. Vâng, ít nhất là không phải ngay lập tức: họ chỉ cần thiết lập công việc để được thực hiện sau, khi biểu thức máy phát điện là lặp lại. Đây chính thức được gọi là lười biếng. Chúng tôi sẽ làm điều đó (tốt, tôi, dù sao) bằng cách đi qua items đến chức năng frozenset, lặp lại trên nó và trả về một frozenset lạnh giá lạnh.

unwanted = frozenset(items) 

Bạn thực sự có thể kết hợp hai dòng cuối cùng, bằng cách đặt các biểu hiện phát ngay trong các cuộc gọi đến frozenset:

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

lừa cú pháp gọn gàng này hoạt động miễn là iterator tạo ra bởi sự biểu hiện máy phát điện là tham số duy nhất cho hàm bạn đang gọi. Nếu không, bạn phải viết nó trong bộ ngoặc đơn riêng biệt thông thường của nó, giống như bạn đang truyền một bộ tuple làm đối số cho hàm.

Bây giờ, chúng tôi có thể tạo danh sách mới giống như cách mà Brandon đã làm, với list comprehension. Chúng sử dụng cú pháp tương tự như biểu thức trình tạo, và về cơ bản giống nhau, ngoại trừ chúng là eager thay vì lười biếng (một lần nữa, đây là các thuật ngữ kỹ thuật thực tế), vì vậy chúng có quyền làm việc lặp lại các mục và tạo một danh sách từ họ.

L4 = [item for item in L1 if item not in unwanted] 

Điều này tương đương với việc chuyển biểu thức máy phát đến list, ví dụ:

L4 = list(item for item in L1 if item not in unwanted) 

nhưng thành ngữ hơn.

Vì vậy, đây sẽ tạo ra danh sách L4, có chứa các yếu tố của L1 mà không ở một trong hai L2 hoặc L3, duy trì thứ tự mà họ đã được ban và số lượng với họ rằng ở đó.


Nếu bạn chỉ muốn biết giá trị trong L1 nhưng không phải trong L2 hoặc L3, nó dễ dàng hơn nhiều: bạn chỉ cần tạo mà thiết lập:

L1_unique_values = set(L1) - unwanted 

Bạn có thể tạo một danh sách ra của nó, as does st0le, nhưng điều đó có thể không thực sự là những gì bạn muốn. Nếu bạn thực sự làm muốn thiết các giá trị mà chỉ được tìm thấy trong L1, bạn có thể có một lý do rất tốt để giữ cho rằng thiết như một set, hoặc thực sự một frozenset:

L1_unique_values = frozenset(L1) - unwanted 

... Annnnd, bây giờ cho một cái gì đó hoàn toàn khác nhau:

from itertools import ifilterfalse, chain 
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1)) 
+0

+1 Rất nhiều thông tin. Việc bổ sung gần đây nhất (với itertools) là rất tốt đẹp. Tôi muốn nói rằng bạn đã kiếm được bằng tiến sĩ trong danh sách lọc dựa trên việc đưa vào một bộ danh sách. – aaronasterling

+0

@aaron: Đã mất nhiều năm nghiên cứu, nhưng nó đáng giá. – intuited

+0

Tôi có thiếu một cái gì đó hoặc là biểu thức máy phát điện của bạn chỉ 'itertools.chain'? Nếu có, chỉ cần sử dụng (bạn có thể giữ lời giải thích của máy phát điện và biểu hiện genrator mặc dù, mọi người cần phải tìm hiểu về họ). – delnan

0

Tôi nghĩ câu trả lời của trực giác là quá dài đối với một vấn đề đơn giản như vậy, và Python đã có một hàm dựng sẵn để chuỗi hai danh sách dưới dạng trình tạo.

Thủ tục như sau:

  1. Sử dụng itertools.chain để chuỗi L2 và L3 mà không cần tạo một bản sao bộ nhớ tốn
  2. Tạo một bộ từ đó (trong trường hợp này, một frozenset sẽ làm vì chúng tôi don 't thay đổi nó sau khi tạo)
  3. Sử dụng tính năng hiểu danh sách để lọc ra các phần tử nằm trong L1 và cũng trong L2 hoặc L3. Như thiết lập/frozenset tra cứu (x in someset) là O (1), điều này sẽ rất nhanh.

Và bây giờ mã:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 

from itertools import chain 
tmp = frozenset(chain(L2, L3)) 
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6] 

này nên là một trong những giải pháp bộ nhớ tốn đơn giản nhất và ít nhất nhanh nhất.

+0

Nó không phải là nhanh nhất; kiểm tra các bài kiểm tra trong bài viết của tôi. Đặt một trình vòng lặp ở giữa tập hợp và danh sách đã lặp lại chỉ làm chậm mọi thứ. –

+0

@Brandon Craig Rhodes: Ok, hãy nói "một trong những giải pháp nhanh nhất". Cảm ơn bạn đã đăng kết quả điểm chuẩn của mình. – AndiDog

+0

Thật vậy - các giải pháp của bạn chắc chắn là một trong những giải pháp nhanh nhất và chắc chắn của lớp O (* n * log * m *) mà vấn đề này xứng đáng. Tôi chỉ muốn đảm bảo rằng các lập trình viên nhận ra rằng các vòng lặp không phải là bụi pixie mà bằng cách nào đó nhanh hơn là lặp qua một container; mỗi mục được trả về bởi một trình lặp sẽ yêu cầu phạm vi của nó được kích hoạt lại và mã của nó được bắt đầu lại, vì vậy các lợi ích của chúng không được cung cấp miễn phí. –

Các vấn đề liên quan