web-dev-qa-db-de.com

Wählen Sie zufällig N Elemente aus einer Sequenz unbekannter Länge

Ich versuche, einen Algorithmus zu schreiben, der zufällig N verschiedene Elemente aus einer Sequenz auswählt, ohne die Größe der Sequenz im Voraus zu kennen, und bei dem es teuer ist, die Sequenz mehr als einmal zu durchlaufen. Zum Beispiel könnten die Elemente der Sequenz die Zeilen einer großen Datei sein.

Ich habe eine Lösung gefunden, wenn N = 1 ist (das heißt, wenn versucht wird, aus einer riesigen Sequenz genau ein Element zufällig auszuwählen):

import random
items = range(1, 10) # Imagine this is a huge sequence of unknown length
count = 1
selected = None
for item in items:
    if random.random() * count < 1:
        selected = item
    count += 1

Aber wie kann ich dasselbe für andere Werte von N erreichen (zB N = 3)?

36
akonsu

Verwenden Sie Behälterprobenahme . Es ist ein sehr einfacher Algorithmus, der für jeden N funktioniert.

Hier ist eine Python-Implementierung und hier ist eine andere.

41
NPE

Wenn Ihre Sequenz so kurz ist, dass das Einlesen in den Speicher und das zufällige Sortieren akzeptabel sind, können Sie einfach random.shuffle verwenden:

import random
arr=[1,2,3,4]

# In-place shuffle
random.shuffle(arr)

# Take the first 2 elements of the now randomized array
print arr[0:2]
[1, 3]

Abhängig vom Typ Ihrer Sequenz müssen Sie sie möglicherweise in eine Liste konvertieren, indem Sie list(your_sequence) aufrufen. Dies funktioniert jedoch unabhängig von den Typen der Objekte in Ihrer Sequenz.

Wenn Sie Ihre Sequenz nicht in den Speicher einpassen können oder die Speicher- oder CPU-Anforderungen dieses Ansatzes für Sie zu hoch sind, müssen Sie natürlich eine andere Lösung verwenden.

65
Carl Bellingan

Das einfachste was ich gefunden habe ist das Antwort in SO:

import random

my_list = [1, 2, 3, 4, 5]
num_selections = 2

new_list = random.sample(my_list, num_selections)

# To preserve the order of the list, you could do:
randIndex = random.sample(range(len(my_list)), n_selections)
randIndex.sort()
new_list = [my_list[i] for i in randIndex]
20
Solomon Vimal

Wenn Sie eine Python-Version 3.6 oder höher haben, können Sie eine Auswahl treffen 

from random import choices

items = range(1, 10)
new_items = choices(items, k = 3)

print(new_items) 
[6, 3, 1]
13
Christof Henkel

@NPE ist korrekt, aber die Implementierungen, mit denen verknüpft wird, sind nicht optimal und nicht sehr "Pythonic". Hier ist eine bessere Implementierung:

def sample(iterator, k):
    """
    Samples k elements from an iterable object.

    :param iterator: an object that is iterable
    :param k: the number of items to sample
    """
    # fill the reservoir to start
    result = [next(iterator) for _ in range(k)]

    n = k - 1
    for item in iterator:
        n += 1
        s = random.randint(0, n)
        if s < k:
            result[s] = item

    return result

Bearbeiten Als @ panda-34 darauf hingewiesen, dass die ursprüngliche Version fehlerhaft war, aber nicht, weil ich randint vs randrange verwendete. Das Problem ist, dass mein Anfangswert für n die Tatsache nicht berücksichtigt hat, dass randint an beiden Enden des Bereichs inklusive ist. Wenn Sie dies berücksichtigen, wird das Problem behoben. (Hinweis: Sie können auch randrange verwenden, da sie beim Mindestwert einschließlich und beim Höchstwert ausschließlich enthalten ist.)

4
JesseBuesking

Nachfolgend erhalten Sie N zufällige Elemente aus einem Array X

import random
list(map(lambda _: random.choice(X), range(N)))
4

Es sollte ausreichen, jedes neue Element nur einmal anzunehmen oder abzulehnen, und wenn Sie es akzeptieren, werfen Sie einen zufällig ausgewählten alten Artikel aus.

Angenommen, Sie haben zufällig N Elemente von K ausgewählt und sehen ein (K + 1) -tes Element. Akzeptiere es mit der Wahrscheinlichkeit N/(K + 1) und seine Wahrscheinlichkeiten sind in Ordnung. Die aktuellen Gegenstände sind mit der Wahrscheinlichkeit N/K eingestiegen und werden mit der Wahrscheinlichkeit (N/(K + 1)) (1/N) = 1/(K + 1) hinausgeworfen ) (K/(K + 1)) = N/(K + 1), so dass auch deren Wahrscheinlichkeiten in Ordnung sind.

Und ja, ich sehe, jemand hat Sie auf das Reservoir-Sampling aufmerksam gemacht - dies ist eine Erklärung, wie das funktioniert.

3
mcdowella

Als aix erwähnte Reservoir-Probenahme funktioniert. Eine weitere Option besteht darin, für jede angezeigte Zahl eine Zufallszahl zu generieren und die obersten k-Nummern auszuwählen. 

Um dies iterativ durchzuführen, müssen Sie einen Haufen von k (Zufallszahl, Anzahl) -Paaren beibehalten und immer dann, wenn eine neue Zahl in den Heap eingefügt wird, wenn diese größer als der kleinste Wert im Heap ist.

2
ElKamina

Dies war meine Antwort auf eine doppelte Frage (geschlossen, bevor ich posten konnte), die etwas verwandt war ("Zufallszahlen ohne Duplikate erzeugen"). Da dies ein anderer Ansatz als die anderen Antworten ist, lasse ich es hier, falls es zusätzliche Einsichten gibt.

from random import randint

random_nums = []
N = # whatever number of random numbers you want
r = # lower bound of number range
R = # upper bound of number range

x = 0

while x < N:
    random_num = randint(r, R) # inclusive range
    if random_num in random_nums:
        continue
    else:
        random_nums.append(random_num)
        x += 1

Der Grund für die while-Schleife über der for-Schleife ist die einfachere Implementierung von Nicht-Überspringen bei der Zufallsgenerierung (d. H. Wenn Sie 3 Duplikate erhalten, erhalten Sie keine N-3-Zahlen).

0
tooty44