Який найбільш пітонічний спосіб вивести випадковий елемент зі списку?


88

Скажімо, у мене є список xз невизначеною довжиною, з якого я хочу випадковим чином вивести один елемент, щоб потім список не містив елемент. Який найбільш пітонічний спосіб це зробити?

Я можу зробити це з допомогою досить незручна combincation з pop, random.randintі len, і хотів би бачити більш короткі або більш гарні рішення:

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

Що я намагаюся досягти, це послідовно виводити випадкові елементи зі списку. (тобто випадковим чином висунути один елемент і перемістити його до словника, випадковим чином висунути інший елемент і перемістити його в інший словник, ...)

Зверніть увагу, що я використовую Python 2.6 і не знайшов рішень за допомогою функції пошуку.


3
Я не дуже питоніст, але це, безсумнівно, виглядає для мене досить добре.
Matt Ball

я зробив детальний аналіз складності часу, див. мою відповідь десь у дорозі. ПЕРЕДАЧА НЕ ЕФЕКТИВНА! але ви все одно можете використовувати, якщо вам потрібно якось змінити порядок елементів. якщо pop (0) стосується вас, використовуйте деквіт, згаданий у моєму аналізі.
nikhil swami

O (2) складність часу для написаної відповіді. оберніть його функцією для швидкого використання. Зверніть увагу, що будь-який list.pop (n), крім list.pop (-1), приймає O (n).
nikhil swami

Відповіді:


94

Те, що ви, здається, задумали, виглядає не дуже пітонічно. Ви не повинні видаляти речі з середини списку, оскільки списки реалізовані як масиви у всіх реалізаціях Python, про які я знаю, тому це O(n)операція.

Якщо вам дійсно потрібна ця функціональність як частина алгоритму, вам слід перевірити структуру даних, наприклад, blistяка підтримує ефективне видалення з середини.

У чистому Python те, що ви можете зробити, якщо вам не потрібен доступ до решти елементів, - це просто перетасувати список, а потім ітерацію над ним:

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

Якщо вам дійсно потрібен залишок (який трохи запах коду, IMHO), принаймні ви можете pop()з кінця списку зараз (що швидко!):

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

Загалом, ви можете часто висловлювати свої програми більш елегантно, якщо ви використовуєте більш функціональний стиль, а не мутуєте стан (як це робите зі списком).


3
Тож кращою (швидшою) ідеєю було б використовувати, random.shuffle(x)а потім x.pop()? Я не розумію, як зробити цей "функціонал"?
Генрік

1
@Henrik: Якщо у вас є дві колекції (наприклад, список словників та список випадкових чисел), і ви хочете повторити їх одночасно, ви можете zipотримати для них список (dict, число) пар. Ви сказали щось про кілька словників, котрий ви хочете пов’язати кожен із випадковим числом. zipідеально для цього
Ніклас Б.

2
Я повинен додати допис, коли проголосую проти. Бувають випадки, коли вам потрібно видалити елемент із середини списку ... Я повинен це зробити зараз. Вибору немає: у мене є впорядкований список, я маю видалити елемент посередині. Це відмовно, але єдиний інший вибір - зробити важку рефакторинг коду для однієї напіврідкісної операції. Питання полягає у впровадженні [], який ПОВИНЕН бути ефективним для таких операцій, але не є.
Марк Героліматос,

5
@NiklasB. OP використовував випадковий приклад (відверто кажучи, його слід було зупинити, це затьмарило проблему). "Не робіть цього" недостатньо. Кращою відповіддю було б запропонувати структуру даних Python, яка ДОПОВІДАЄ такі операції, забезпечуючи при цьому ДОСТАТКОВУ швидкість доступу (явно не така гарна, як arra ... er ... list). У python 2 я не міг його знайти. Якщо я це зроблю, я відповім цим. Зверніть увагу, що через невдачу в браузері я не зміг додати, що до свого початкового коментаря я повинен був додати вторинний коментар. Дякую, що тримаєте мене чесно :)
Марк Героліматос,

1
@MarkGerolimatos У стандартній бібліотеці не існує структури даних як з ефективним довільним доступом, так і з вставкою / видаленням. Напевно, ви хочете використати щось на зразок pypi.python.org/pypi/blist. Я все одно стверджую, що у багатьох випадках використання цього можна уникнути
Ніклас Б.,

49

Ви не отримаєте набагато кращого від цього, але ось невелике покращення:

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

Документація на random.randrange():

random.randrange ([start], stop [, step])
Повернення випадково вибраного елемента з range(start, stop, step). Це еквівалентно choice(range(start, stop, step)), але насправді не створює об'єкт діапазону.


14

Щоб видалити один елемент із випадковим індексом зі списку, якщо порядок інших елементів списку не має значення:

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)

Обмін використовується, щоб уникнути поведінки O (n) при видаленні з середини списку.


9

Ось ще одна альтернатива: чому б вам спочатку не перетасувати список , а потім не почати вискакувати його елементи, поки не залишиться більше елементів? подобається це:

import random

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

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

3
@NiklasB. тому що ми вилучаємо елементи зі списку. Якщо не зовсім потрібно видаляти елементи, так, я згоден з вами:[for p in x]
Іскар Лопес,

Оскільки він змінює список, і якщо ви просто хочете вибрати половину елементів зараз, а іншу половину пізніше, решту ви отримаєте пізніше.
Генрік,

@Henrik: Гаразд, саме тому я запитав вас, чи потрібен вам залишився список. Ви не відповіли на це.
Ніклас Б.

2

Один із способів це зробити:

x.remove(random.choice(x))

7
Це може стати проблематичним, якщо елементи трапляються кілька разів.
Ніклас Б.

2
Це видалить крайній лівий елемент, якщо є дублікати, що спричинить не зовсім випадковий результат.
FogleBird

За допомогою popви можете вказати ім’я на вилучений елемент, а з цим ви не можете.
agf

Досить справедливо, я погоджуюсь, що це не дуже випадково, коли елементи трапляються більше одного разу.
Симеон Віссер,

1
Окрім питання про перекіс вашого розповсюдження, removeпотрібно лінійне сканування списку. Це надзвичайно неефективно порівняно з пошуком індексу.
aaronasterling

2

Не вискакуючи зі списку, я зіткнувся з цим запитанням у Google, намагаючись отримати X випадкових елементів зі списку без дублікатів. Ось що я врешті використав:

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

Це може бути трохи неефективним, оскільки ви перетасовуєте цілий список, але використовуєте лише невелику його частину, але я не експерт з оптимізації, тому можу помилитися.


3
random.sample(items, items_needed)
jfs

2

Я знаю, що це старе питання, але лише заради документації:

Якщо ви (людина, яка шукає те саме запитання) робите те, що, на мою думку, ви робите, тобто випадковим чином вибираєте k кількість елементів зі списку (де k <= len (ваш список)), але переконайтесь, що кожен елемент ніколи не вибирається більше ніж один раз (= вибірка без заміни), ви можете використовувати random.sample, як пропонує @ jf-sebastian. Але, не знаючи більше про варіант використання, я не знаю, чи це те, що вам потрібно.


1

Ця відповідь надана люб’язно @ niklas-b :

" Ви, мабуть, хочете використовувати щось на зразок pypi.python.org/pypi/blist "

Щоб процитувати сторінку PYPI :

... тип, подібний до списку, з кращою асимптотичною продуктивністю та подібною продуктивністю в невеликих списках

Blist - це заміна для списку Python, що забезпечує кращу продуктивність при зміні великих списків. Пакет розмивання також надає типи сортування, сортування, сортування списку, списку сортування, сортування та двох типів.

Можна припустити знижену продуктивність на кінці довільного доступу / випадкового запуску , оскільки це структура даних "копіювання під час запису". Це порушує багато припущень щодо випадків використання у списках Python, тому використовуйте його обережно .

Втім, якщо ваш основний варіант використання - зробити щось дивне і неприродне зі списком (як у примусовому прикладі, наведеному @OP, або моїй проблемі з чергою з передачею Python 2.6 FIFO), це буде добре відповідати рахунку .


1

незважаючи на багато відповідей, що передбачають використання, random.shuffle(x)і x.pop()дуже повільний для великих даних. і час, необхідний для переліку 10000елементів, зайняв, 6 secondsколи ввімкнено перетасовку. при відключенні перетасовки швидкість була0.2s

виявилося, що найшвидший метод після тестування всіх наведених вище методів написав @jfs

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)

на підтвердження мого твердження наведено графік складності часу з цього джерела введіть тут опис зображення


ЯКЩО у списку немає дублікатів,

Ви можете досягти своєї мети, використовуючи також набори. як тільки список, складений у набір дублікатів, буде видалений. remove by valueі remove randomвартість O(1), тобто дуже ефективна. це найчистіший метод, який я міг придумати.

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.

На відміну від listsякого A+Bваріанту підтримки , setsтакож підтримуйте A-B (A minus B)разом із A+B (A union B)та A.intersection(B,C,D). надзвичайно корисний, коли потрібно виконати логічні операції з даними.


НЕОБОВ'ЯЗКОВО

ЯКЩО вам потрібна швидкість, коли операції виконуються над головою та хвостом списку, використовуйте python dequeue (подвійна черга) на підтримку моєї вимоги, ось зображення. зображення - це тисяча слів.

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.