Ordinazione "bizzarra" di set in pitone


14

Quando converto un elenco Python 3.8.0 in un set, l'ordinamento del set risultante * è altamente strutturato in modo non banale. Come viene estratta questa struttura dall'elenco pseudo-casuale?


Come parte di un esperimento che sto eseguendo, sto generando un set casuale. Sono stato sorpreso di vedere che la trama del set improvvisamente ha mostrato una struttura lineare inaspettata nel set. Quindi ci sono due cose che mi sconcertano: perché la conversione in un risultato impostato ha un ordinamento * che finisce per evidenziare questa struttura; e, in misura minore, perché l'insieme pseudo-casuale ha questa struttura "nascosta"?

Il codice:

X = [randrange(250) for i in range(30)]
print(X)
print(set(X))

quali uscite, ad esempio

[238, 202, 245, 94, 111, 106, 148, 164, 154, 113, 128, 10, 196, 141, 69, 38, 106, 8, 40, 53, 160, 87, 85, 13, 38, 147, 204, 50, 162, 91]

{128, 8, 10, 141, 13, 147, 148, 154, 160, 162, 164, 38, 40, 50, 53, 196, 69, 202, 204, 85, 87, 91, 94, 106, 238, 111, 113, 245}

Un grafico ** dell'elenco sopra sembra abbastanza casuale, come previsto:

Grafico WolframAlpha dell'elenco generato casualmente

mentre la trama dell'insieme (come ordinato nell'output) mostra la struttura presente nell'insieme:

Trama WolframAlpha del set dalla lista casuale

Questo comportamento è coerente al 100% sulla mia macchina (più esempi di seguito) con i valori 250 e 30 utilizzati nel codice sopra (l'esempio che ho usato non è stato scelto da ciliegia - è solo l'ultimo che ho eseguito). L'ottimizzazione di questi valori talvolta comporta una struttura leggermente diversa (ad esempio un sottoinsieme di tre progressioni aritmetiche *** anziché due).

È riproducibile su macchine di altre persone? Ovviamente, che tale struttura esista sembra indicativa di una generazione di numeri pseudo-casuali non così grande, ma ciò non spiega come la conversione in un insieme "estrarrà" in qualche modo questa struttura. Per quanto ne so, non esiste alcuna garanzia formale che l'ordinamento di un set (quando convertito da un elenco) sia deterministico (e anche se lo è, non esiste un ordinamento sofisticato in background). Allora, come sta succedendo questo ?!


(*): Lo so, gli insiemi sono raccolte non ordinate, ma intendo "ordinato", nel senso che, quando si chiama l' printistruzione, l'insieme viene emesso in un ordine che evidenzia in modo coerente la struttura dell'insieme sottostante.

(**): queste trame provengono da Wolfram Alpha. Altri due esempi sono di seguito:

inserisci qui la descrizione dell'immagine

(***): due grafici quando si cambia l'intervallo dei numeri casuali da 250 a 500:

inserisci qui la descrizione dell'immagine

Risposte:


14

Fondamentalmente, questo è dovuto a due cose:

  • Un set in Python è implementato usando una tabella hash ,
  • L'hash di un numero intero è l'intero stesso.

Pertanto, l'indice che appare un numero intero nell'array sottostante sarà determinato dal valore dell'intero, modulo la lunghezza dell'array sottostante. Pertanto, i numeri interi tenderanno a rimanere in ordine crescente quando si inserisce un intervallo contiguo in un set:

>>> list(set(range(10000))) == list(range(10000))
True # this can't be an accident!

Se non hai tutti i numeri di un intervallo contiguo, entra in gioco la parte "modulo la lunghezza dell'array sottostante":

>>> r = range(0, 50, 4)
>>> set(r)
{0, 32, 4, 36, 8, 40, 12, 44, 16, 48, 20, 24, 28}
>>> sorted(r, key=lambda x: x % 32)
[0, 32, 4, 36, 8, 40, 12, 44, 16, 48, 20, 24, 28]

La sequenza è prevedibile se si conosce la lunghezza dell'array sottostante e l'algoritmo (deterministico) per l'aggiunta di elementi. In questo caso la lunghezza dell'array è 32, poiché inizialmente è 8 e viene quadruplicata mentre vengono aggiunti gli elementi.

Tranne un blip vicino alla fine (perché i numeri 52 e 56 non sono nell'insieme), l'intervallo è diviso in due sequenze 0, 4, 8, ...e 32, 36, 40, ...che si alternano perché gli hash, che sono i valori dei numeri stessi, sono presi modulo 32 per scegliere indici nella matrice. Ci sono collisioni; per esempio, 4 e 36 sono uguali modulo 32, ma 4 è stato aggiunto per primo all'insieme, quindi 36 finisce in un indice diverso.

Ecco un grafico per questa sequenza. La struttura nei tuoi grafici è solo una versione più rumorosa, perché hai generato i tuoi numeri in modo casuale piuttosto che da un intervallo con un passo.

inserisci qui la descrizione dell'immagine

Il numero di sequenze interlacciate dipenderà dalla dimensione dell'insieme in proporzione alla lunghezza dell'intervallo da cui vengono campionati i numeri, poiché ciò determina quante volte la lunghezza dell'intervallo "avvolge" il modulo della lunghezza dell'array sottostante dell'hashtable. Ecco un esempio con tre sequenze interfogliati 0, 6, 12, ..., 66, 72, 78, ...e 36, 42, 48, ...:

>>> set(range(0, 90, 6))
{0, 66, 36, 6, 72, 42, 12, 78, 48, 18, 84, 54, 24, 60, 30}

Ah! Questo lo spiega (e anche una bella spiegazione)!
John Don,

E, naturalmente, questo schema nei grafici non ha nulla a che fare con la struttura sottostante nell'insieme (ci aspetteremmo che questo schema sorga nei grafici con elenchi casuali come nel mio esempio) ... Sono stato semplicemente sedotto dagli schemi inattesi in le trame!
John Don,

Come scopri che 30 è la lunghezza dell'array sottostante?
Mark Snyder il

@MarkSnyder Si scopre che sono 32, il che significa che ci sono collisioni, ma l'ordine è lo stesso di un modulo 30.
kaya3

2
@MarkSnyder L'array verrà ridimensionato se viene riempito per più di 2/3 , poiché le prestazioni di una tabella hash diminuiscono in modo molto significativo se si lascia che l'array si riempia o quasi.
kaya3,
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.