Cách khó hiểu nhất để bật một phần tử ngẫu nhiên khỏi danh sách là gì?


88

Giả sử tôi có một danh sách xvới độ dài không xác định mà từ đó tôi muốn bật ngẫu nhiên một phần tử để danh sách không chứa phần tử sau đó. Cách đáng sợ nhất để làm điều này là gì?

Tôi có thể làm điều đó bằng cách sử dụng combincation khá vụng về pop, random.randintlen, và muốn thấy những giải pháp ngắn hơn hoặc đẹp hơn:

import random
x = [1,2,3,4,5,6]
x.pop(random.randint(0,len(x)-1))

Những gì tôi đang cố gắng đạt được là liên tục bật các phần tử ngẫu nhiên từ một danh sách. (tức là, bật ngẫu nhiên một phần tử và di chuyển nó vào từ điển, bật ngẫu nhiên phần tử khác và di chuyển nó sang từ điển khác, ...)

Lưu ý rằng tôi đang sử dụng Python 2.6 và không tìm thấy bất kỳ giải pháp nào thông qua chức năng tìm kiếm.


3
Tôi không phải là một Pythonista, nhưng điều đó chắc chắn trông khá ổn đối với tôi.
Matt Ball

một phân tích chi tiết về độ phức tạp của thời gian đã được tôi thực hiện, hãy xem câu trả lời của tôi ở đâu đó dưới đây. SHUFFLE KHÔNG HIỆU QUẢ! nhưng bạn vẫn có thể sử dụng nếu bạn cần thay đổi thứ tự các mặt hàng bằng cách nào đó. nếu pop (0) liên quan đến bạn, hãy sử dụng dequeue, được đề cập trong phân tích của tôi.
nikhil swami

O (2) độ phức tạp về thời gian cho câu trả lời đã viết. bọc nó trong một chức năng để sử dụng nhanh chóng. xin lưu ý rằng bất kỳ list.pop (n) nào khác ngoài list.pop (-1) lấy O (n).
nikhil swami

Câu trả lời:


94

Những gì bạn có vẻ không giống Pythonic ngay từ đầu. Bạn không nên xóa nội dung khỏi giữa danh sách, vì danh sách được triển khai dưới dạng mảng trong tất cả các triển khai Python mà tôi biết, vì vậy đây là một O(n)hoạt động.

Nếu bạn thực sự cần chức năng này như một phần của thuật toán, bạn nên kiểm tra cấu trúc dữ liệu như cấu trúc blisthỗ trợ xóa hiệu quả từ giữa.

Trong Python thuần túy, những gì bạn có thể làm nếu không cần quyền truy cập vào các phần tử còn lại chỉ là xáo trộn danh sách trước và sau đó lặp lại nó:

lst = [1,2,3]
random.shuffle(lst)
for x in lst:
  # ...

Nếu bạn thực sự cần phần còn lại (hơi có mùi mã, IMHO), ít nhất bạn có thể pop()từ cuối danh sách ngay bây giờ (nhanh!):

while lst:
  x = lst.pop()
  # do something with the element      

Nói chung, bạn thường có thể diễn đạt các chương trình của mình một cách thanh lịch hơn nếu bạn sử dụng một kiểu chức năng hơn, thay vì trạng thái thay đổi (như bạn làm với danh sách).


3
Vì vậy, một ý tưởng tốt hơn (nhanh hơn) sẽ là sử dụng random.shuffle(x)và sau đó x.pop()? Tôi không hiểu làm thế nào để làm điều này "chức năng"?
Henrik

1
@Henrik: Nếu bạn có hai bộ sưu tập (ví dụ: danh sách từ điển và danh sách các số ngẫu nhiên) và bạn muốn lặp lại chúng cùng một lúc, bạn có thể chọn zipchúng để nhận danh sách các cặp (dict, number). Bạn đã nói điều gì đó về nhiều từ điển mà bạn muốn liên kết mỗi từ điển với một số ngẫu nhiên. ziphoàn hảo cho việc này
Niklas B.

2
Tôi phải thêm một bài đăng khi tôi bỏ phiếu. Có những lúc bạn cần loại bỏ một mục giữa danh sách ... Tôi phải làm điều đó ngay bây giờ. Không có lựa chọn nào khác: Tôi có một danh sách đã đặt hàng, tôi phải loại bỏ một mục ở giữa. Thật tệ, nhưng lựa chọn khác duy nhất là thực hiện cấu trúc lại mã nặng cho một hoạt động hơi hiếm. Vấn đề là một trong việc triển khai [], NÊN hiệu quả cho các hoạt động như vậy, nhưng không.
Mark Gerolimatos

5
@NiklasB. OP đã sử dụng ngẫu nhiên làm ví dụ (thành thật mà nói, đáng lẽ ra, nó đã bị bỏ qua, nó đã làm mờ vấn đề). "Đừng làm vậy" là không đủ. Một câu trả lời tốt hơn sẽ là đề xuất một cấu trúc dữ liệu Python KHÔNG hỗ trợ các hoạt động như vậy trong khi cung cấp tốc độ truy cập HIỆU QUẢ (rõ ràng là không tốt bằng danh sách Ar ... er ...). Trong python 2, tôi không thể tìm thấy một. Nếu tôi làm, tôi sẽ trả lời với điều đó. Lưu ý rằng do lỗi trình duyệt, tôi không thể thêm nhận xét đó vào nhận xét ban đầu của mình, đáng lẽ tôi phải thêm nhận xét phụ. Cảm ơn bạn đã giữ tôi :) trung thực
Đánh dấu Gerolimatos

1
@MarkGerolimatos Không có cấu trúc dữ liệu nào có cả truy cập ngẫu nhiên hiệu quả và chèn / xóa trong thư viện chuẩn. Bạn có thể muốn sử dụng cái gì đó như pypi.python.org/pypi/blist tôi vẫn cho rằng trong rất nhiều trường hợp sử dụng này có thể tránh được
Niklas B.

49

Bạn sẽ không nhận được nhiều hơn thế, nhưng đây là một cải tiến nhỏ:

x.pop(random.randrange(len(x)))

Tài liệu về random.randrange():

random.randrange ([bắt đầu], dừng [, bước])
Trả về một phần tử được chọn ngẫu nhiên từ range(start, stop, step). Điều này tương đương với choice(range(start, stop, step)), nhưng không thực sự tạo một đối tượng phạm vi.


14

Để xóa một phần tử duy nhất ở chỉ mục ngẫu nhiên khỏi danh sách nếu thứ tự của các phần tử còn lại trong danh sách không quan trọng:

import random

L = [1,2,3,4,5,6]
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

Hoán đổi được sử dụng để tránh hành vi O (n) khi xóa khỏi giữa danh sách.


9

Đây là một giải pháp thay thế khác: tại sao bạn không xáo trộn danh sách trước , rồi bắt đầu xuất các phần tử của nó cho đến khi không còn phần tử nào nữa? như thế này:

import random

x = [1,2,3,4,5,6]
random.shuffle(x)

while x:
    p = x.pop()
    # do your stuff with p

3
@NiklasB. bởi vì chúng tôi đang xóa các phần tử khỏi danh sách. Nếu nó không phải là hoàn toàn cần thiết đến các yếu tố loại bỏ, vâng tôi đồng ý với bạn:[for p in x]
Óscar López

Bởi vì nó làm thay đổi danh sách và nếu bạn chỉ muốn chọn một nửa số phần tử bây giờ và nửa phần còn lại sau này, bạn sẽ có tập hợp còn lại sau đó.
Henrik

@Henrik: Được rồi, đó là lý do tôi hỏi bạn có cần danh sách còn lại không. Bạn đã không trả lời điều đó.
Niklas B.

2

Một cách để làm điều đó là:

x.remove(random.choice(x))

7
Điều này có thể trở thành vấn đề nếu các phần tử xảy ra nhiều lần.
Niklas B.

2
Thao tác này sẽ xóa phần tử ngoài cùng bên trái khi có các bản sao, gây ra kết quả không hoàn toàn ngẫu nhiên.
FogleBird

Với popbạn có thể trỏ tên vào phần tử bị loại bỏ, với điều này bạn không thể.
agf

Công bằng mà nói, tôi đồng ý rằng điều này không phải là ngẫu nhiên khi các yếu tố xảy ra nhiều hơn một lần.
Simeon Visser

1
Ngoài câu hỏi làm lệch phân phối của bạn, removeyêu cầu quét tuyến tính của danh sách. Điều đó thật sự kém hiệu quả so với việc tra cứu một chỉ mục.
aaronasterling

2

Trong khi không xuất hiện từ danh sách, tôi đã gặp câu hỏi này trên Google khi cố gắng lấy X mục ngẫu nhiên từ danh sách mà không có bản sao. Đây là những gì cuối cùng tôi đã sử dụng:

items = [1, 2, 3, 4, 5]
items_needed = 2
from random import shuffle
shuffle(items)
for item in items[:items_needed]:
    print(item)

Điều này có thể hơi kém hiệu quả vì bạn đang xáo trộn toàn bộ danh sách nhưng chỉ sử dụng một phần nhỏ của nó, nhưng tôi không phải là chuyên gia tối ưu hóa nên tôi có thể nhầm.


3
random.sample(items, items_needed)
jfs

2

Tôi biết đây là một câu hỏi cũ, nhưng chỉ vì mục đích tài liệu:

Nếu bạn (người đang tìm kiếm câu hỏi tương tự) đang làm những gì tôi nghĩ là bạn đang làm, đó là chọn k số mục ngẫu nhiên từ một danh sách (trong đó k <= len (danh sách của bạn)), nhưng hãy đảm bảo rằng mỗi mục không bao giờ được chọn thêm hơn một lần (= lấy mẫu mà không cần thay thế), bạn có thể sử dụng random.sample như @ jf-sebastian đề xuất. Nhưng nếu không biết thêm về trường hợp sử dụng, tôi không biết liệu đây có phải là thứ bạn cần hay không.


1

Câu trả lời này được cung cấp bởi @ niklas-b :

" Bạn có thể muốn sử dụng một cái gì đó như pypi.python.org/pypi/blist "

Để trích dẫn trang PYPI :

... một loại giống như danh sách với hiệu suất tiệm cận tốt hơn và hiệu suất tương tự trên các danh sách nhỏ

Blist là sự thay thế thả vào cho danh sách Python cung cấp hiệu suất tốt hơn khi sửa đổi danh sách lớn. Gói vỉ cũng cung cấp các loại danh sách sắp xếp, sắp xếp, danh sách đã sắp xếp, tập kết hợp dệt, sắp xếp và các loại btuple.

Người ta sẽ cho rằng hiệu suất bị giảm ở cuối truy cập ngẫu nhiên / chạy ngẫu nhiên , vì nó là cấu trúc dữ liệu "sao chép khi ghi". Điều này vi phạm nhiều giả định trường hợp sử dụng trên danh sách Python, vì vậy hãy sử dụng nó một cách cẩn thận .

TUY NHIÊN, nếu trường hợp sử dụng chính của bạn là làm điều gì đó kỳ lạ và không tự nhiên với một danh sách (như trong ví dụ bắt buộc được cung cấp bởi @OP hoặc vấn đề hàng đợi FIFO Python 2.6 của tôi), thì điều này sẽ phù hợp với hóa đơn .


1

mặc dù nhiều câu trả lời gợi ý sử dụng random.shuffle(x)x.pop()nó rất chậm trên dữ liệu lớn. và thời gian cần thiết trên danh sách các 10000phần tử 6 secondskhi tính năng xáo trộn được bật. khi xáo trộn bị tắt, tốc độ là0.2s

phương pháp nhanh nhất sau khi thử nghiệm tất cả các phương pháp đã cho ở trên hóa ra là do @jfs viết

import random

L = ['1',2,3,'4'...1000] #you can take mixed or pure list
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

để hỗ trợ cho tuyên bố của tôi, đây là biểu đồ phức tạp về thời gian từ nguồn này nhập mô tả hình ảnh ở đây


NẾU không có bản sao trong danh sách,

bạn cũng có thể đạt được mục đích của mình bằng cách sử dụng các bộ. một khi danh sách được tạo thành các bản sao đã đặt sẽ bị xóa. remove by valueremove randomchi phí O(1), tức là rất hiệu quả. đây là phương pháp sạch nhất mà tôi có thể nghĩ ra.

L=set([1,2,3,4,5,6...]) #directly input the list to inbuilt function set()
while 1:
    r=L.pop()
    #do something with r , r is random element of initial list L.

Không giống như tùy chọn listshỗ trợ nào A+B, setscũng hỗ trợ A-B (A minus B)cùng với A+B (A union B)A.intersection(B,C,D). siêu hữu ích khi bạn muốn thực hiện các phép toán logic trên dữ liệu.


KHÔNG BẮT BUỘC

NẾU bạn muốn tốc độ khi các thao tác được thực hiện trên đầu và đuôi của danh sách, hãy sử dụng python dequeue (hàng đợi kết thúc kép) để hỗ trợ yêu cầu của tôi đây là hình ảnh. một hình ảnh là hàng nghìn từ.

nhập mô tả hình ảnh ở đây

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.