Qual è il modo più pitonico per estrarre un elemento casuale da un elenco?


90

Supponiamo che io abbia una lista xdi lunghezza sconosciuta da cui voglio estrarre a caso un elemento in modo che l'elenco non contenga l'elemento in seguito. Qual è il modo più pitonico per farlo?

Posso farlo utilizzando un combincation piuttosto unhandy di pop, random.randinte len, e vorrei vedere soluzioni più brevi o più bello:

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

Quello che sto cercando di ottenere è estrarre consecutivamente elementi casuali da un elenco. (cioè, pop casualmente un elemento e spostalo in un dizionario, pop casualmente un altro elemento e spostalo in un altro dizionario, ...)

Nota che sto usando Python 2.6 e non ho trovato alcuna soluzione tramite la funzione di ricerca.


3
Non sono un gran pythonista, ma sicuramente mi sembra abbastanza buono.
Matt Ball

un'analisi dettagliata della complessità temporale è stata eseguita da me, vedere la mia risposta da qualche parte lungo la strada. SHUFFLE NON è EFFICIENTE! ma puoi comunque usarlo se hai bisogno di cambiare l'ordine degli articoli in qualche modo. se pop (0) ti riguarda, usa dequeue, menzionato nella mia analisi.
nikhil swami

O (2) complessità temporale per la risposta ive scritta. avvolgerlo in una funzione per un rapido utilizzo. si noti che qualsiasi list.pop (n) diverso da list.pop (-1) accetta O (n).
nikhil swami

Risposte:


95

Quello che sembri fare non sembra molto pitonico in primo luogo. Non dovresti rimuovere elementi dal centro di un elenco, perché gli elenchi sono implementati come array in tutte le implementazioni Python che conosco, quindi questa è O(n)un'operazione.

Se hai davvero bisogno di questa funzionalità come parte di un algoritmo, dovresti controllare una struttura dati come quella blistche supporta un'eliminazione efficiente dal centro.

In puro Python, quello che puoi fare se non hai bisogno di accedere agli elementi rimanenti è semplicemente mescolare prima l'elenco e poi iterare su di esso:

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

Se hai davvero bisogno del resto (che è un po 'un odore di codice, IMHO), almeno puoi pop()dalla fine della lista ora (che è veloce!):

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

In generale, puoi spesso esprimere i tuoi programmi in modo più elegante se usi uno stile più funzionale, invece di mutare lo stato (come fai con l'elenco).


3
Quindi un'idea migliore (più veloce) sarebbe quella di usare random.shuffle(x)e poi x.pop()? Non capisco come fare questo "funzionale"?
Henrik

1
@Henrik: Se hai due raccolte (ad esempio, un elenco di dizionari e un elenco di numeri casuali) e desideri iterarle contemporaneamente, puoi zipottenere un elenco di coppie (dict, numero). Hai detto qualcosa su più dizionari di cui desideri associare ciascuno a un numero casuale. zipè perfetto per questo
Niklas B.

2
Dovrei aggiungere un post quando voto contro. Ci sono momenti in cui è necessario rimuovere un elemento dal centro di un elenco ... devo farlo adesso. Nessuna scelta: ho un elenco ordinato, devo rimuovere un elemento al centro. Fa schifo, ma l'unica altra scelta è fare un pesante refactoring del codice per un'operazione semi-rara. Il problema è l'implementazione di [], che DOVREBBE essere efficiente per tali operazioni, ma non lo è.
Mark Gerolimatos

5
@NiklasB. L'OP stava usando random come esempio (francamente, avrebbe dovuto essere lasciato fuori, ha offuscato il problema). "Non farlo" è insufficiente. Una risposta migliore sarebbe stata quella di suggerire una struttura dati Python che SUPPORTA tali operazioni fornendo una velocità di accesso SUFFICIENTE (chiaramente non buona come arra ... ehm ... list). In Python 2, non sono riuscito a trovarne uno. Se lo faccio, risponderò con quello. Nota che a causa di un incidente del browser, non sono stato in grado di aggiungerlo al mio commento originale, avrei dovuto aggiungere un commento secondario. Grazie per avermi mantenuto onesto :)
Mark Gerolimatos

1
@MarkGerolimatos Non esiste una struttura dati con accesso casuale efficiente e inserimento / eliminazione nella libreria standard. Probabilmente vuoi usare qualcosa come pypi.python.org/pypi/blist, direi ancora che in molti casi d'uso questo può essere evitato
Niklas B.

51

Non otterrai molto meglio di così, ma ecco un leggero miglioramento:

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

Documentazione su random.randrange():

random.randrange ([start], stop [, step])
Restituisce un elemento selezionato casualmente da range(start, stop, step). Questo è equivalente a choice(range(start, stop, step)), ma in realtà non crea un oggetto intervallo.


14

Per rimuovere un singolo elemento all'indice casuale da un elenco se l'ordine del resto degli elementi dell'elenco non è importante:

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)

Lo scambio viene utilizzato per evitare il comportamento O (n) durante la cancellazione da un centro di un elenco.


9

Ecco un'altra alternativa: perché non mescoli prima l'elenco e poi inizi a estrarne gli elementi finché non rimangono più elementi? come questo:

import random

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

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

3
@NiklasB. perché stiamo rimuovendo elementi dall'elenco. Se non è assolutamente necessario rimuovere elementi, sì, sono d'accordo con te:[for p in x]
Óscar López

Perché altera l'elenco e se vuoi selezionare solo metà degli elementi ora e l'altra metà in seguito, avrai il gruppo rimanente in seguito.
Henrik

@Henrik: Ok, ecco perché ti ho chiesto se hai bisogno dell'elenco rimanente. Non hai risposto.
Niklas B.

2

Un modo per farlo è:

x.remove(random.choice(x))

7
Ciò potrebbe diventare problematico se gli elementi si verificano più di una volta.
Niklas B.

2
Questo rimuoverà l'elemento più a sinistra quando ci sono duplicati, causando un risultato non perfettamente casuale.
FogleBird

Con poppuoi puntare un nome all'elemento rimosso, con questo non puoi.
agf

Giusto, sono d'accordo che questo non è molto casuale quando gli elementi si verificano più di una volta.
Simeon Visser

1
A parte la questione di inclinare la tua distribuzione, removerichiede una scansione lineare dell'elenco. È terribilmente inefficiente rispetto alla ricerca di un indice.
aaronasterling

2

Pur non essendo saltato dall'elenco, ho riscontrato questa domanda su Google mentre cercavo di ottenere X elementi casuali da un elenco senza duplicati. Ecco cosa ho usato alla fine:

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

Questo potrebbe essere leggermente inefficiente poiché stai mescolando un intero elenco ma ne usi solo una piccola parte, ma non sono un esperto di ottimizzazione, quindi potrei sbagliarmi.


3
random.sample(items, items_needed)
jfs

2

So che questa è una vecchia domanda, ma solo per motivi di documentazione:

Se tu (la persona che cerca su Google la stessa domanda) stai facendo quello che penso tu stia facendo, ovvero selezionare k numero di elementi a caso da un elenco (dove k <= len (la tua lista)), ma assicurandoti che ogni elemento non venga mai selezionato più di una volta (= campionamento senza sostituzione), potresti usare random.sample come suggerisce @ jf-sebastian. Ma senza saperne di più sul caso d'uso, non so se questo è ciò di cui hai bisogno.


2

nonostante molte risposte che suggeriscono l'uso random.shuffle(x)ed è x.pop()molto lento su dati di grandi dimensioni. e il tempo richiesto per un elenco di 10000elementi 6 secondsquando è abilitato lo shuffle. quando la riproduzione casuale è disabilitata la velocità era0.2s

il metodo più veloce dopo aver testato tutti i metodi sopra indicati è stato scritto da @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)

a sostegno della mia affermazione ecco il grafico della complessità temporale da questa fonte inserisci qui la descrizione dell'immagine


SE non ci sono duplicati nell'elenco,

puoi raggiungere il tuo scopo anche usando i set. una volta che l'elenco trasformato in set duplicati verrà rimosso. remove by valuee remove randomcosto O(1), cioè molto efficiente. questo è il metodo più pulito che potrei trovare.

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.

A differenza di listsquale A+Bopzione di supporto , setssupporta anche A-B (A minus B)insieme a A+B (A union B)e A.intersection(B,C,D). super utile quando si desidera eseguire operazioni logiche sui dati.


OPZIONALE

SE vuoi velocità quando le operazioni vengono eseguite in testa e in coda alla lista, usa python dequeue (coda doppia) a sostegno della mia affermazione, ecco l'immagine. un'immagine è mille parole.

inserisci qui la descrizione dell'immagine


1

Questa risposta arriva per gentile concessione di @ niklas-b :

" Probabilmente vorrai usare qualcosa come pypi.python.org/pypi/blist "

Per citare la pagina PYPI :

... un tipo simile a un elenco con prestazioni asintotiche migliori e prestazioni simili su elenchi piccoli

Il blist è un sostituto immediato per l'elenco Python che fornisce prestazioni migliori quando si modificano elenchi di grandi dimensioni. Il pacchetto blist fornisce anche i tipi Sortlist, SortSet, Weaksortedlist, Weaksortedset, Sortdict e btuple.

Si presume che le prestazioni siano ridotte sul lato accesso casuale / esecuzione casuale , in quanto si tratta di una struttura di dati "copia in scrittura". Ciò viola molte ipotesi di casi d'uso sugli elenchi di Python, quindi usalo con attenzione .

TUTTAVIA, se il tuo caso d'uso principale è fare qualcosa di strano e innaturale con un elenco (come nell'esempio forzato fornito da @OP, o il mio problema di coda con pass-over FIFO di Python 2.6), allora questo si adatterà perfettamente al conto .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.