web-dev-qa-db-de.com

Wie erstelle ich eine Liste von Zufallszahlen ohne Duplikate?

Ich habe versucht, random.randint(0, 100) zu verwenden, aber einige Zahlen waren die gleichen. Gibt es eine Methode/ein Modul zum Erstellen einer Liste eindeutiger Zufallszahlen?

def getScores():
    # open files to read and write
    f1 = open("page.txt", "r");
    p1 = open("pgRes.txt", "a");

    gScores = [];
    bScores = [];
    yScores = [];

    # run 50 tests of 40 random queries to implement "bootstrapping" method 
    for i in range(50):
        # get 40 random queries from the 50
        lines = random.sample(f1.readlines(), 40);
65

Dadurch wird eine Liste mit 10 Zahlen aus dem Bereich 0 bis 99 ohne Duplikate zurückgegeben.

import random
random.sample(range(100), 10)

In Bezug auf Ihr spezielles Codebeispiel möchten Sie wahrscheinlich alle Zeilen aus der Datei einmal lesen und dann zufällige Zeilen aus der gespeicherten Liste im Speicher auswählen. Zum Beispiel:

all_lines = f1.readlines()
for i in range(50):
    lines = random.sample(all_lines, 40)

Auf diese Weise müssen Sie nur einmal vor der Schleife aus der Datei lesen. Dies ist viel effizienter, als zum Anfang der Datei zurückzukehren und für jede Schleifeniteration erneut f1.readlines() aufzurufen.

109
Greg Hewgill

Sie können zunächst eine Liste mit Zahlen von a bis b erstellen, wobei a und b die kleinsten und größten Zahlen in Ihrer Liste sind. Mischen Sie sie dann mit dem Fisher-Yates -Algorithmus oder mit der Python-Methode random.shuffle .

9
ben

Sie können die Funktion shuffle aus dem Modul random folgendermaßen verwenden:

import random

my_list = list(xrange(1,100)) # list of integers from 1 to 99
                              # adjust this boundaries to fit your needs
random.shuffle(my_list)
print my_list # <- List of unique random numbers

Beachten Sie hierbei, dass die Shuffle-Methode keine Liste zurückgibt, wie Sie vielleicht erwarten. Sie mischt nur die als Referenz übergebene Liste.

8
Ricardo Murillo

Die Lösung in diese Antwort funktioniert, kann jedoch mit dem Speicher problematisch werden, wenn die Stichprobengröße klein ist, die Population jedoch groß ist (z. B. random.sample(insanelyLargeNumber, 10)). 

Um das zu beheben, würde ich folgendes tun:

answer = set()
sampleSize = 10
answerSize = 0

while answerSize < sampleSize:
    r = random.randint(0,100)
    if r not in answer:
        answerSize += 1
        answer.add(r)

# answer now contains 10 unique, random integers from 0.. 100
7
inspectorG4dget

Ich stelle fest, dass dieser Beitrag 6 Jahre alt ist, aber es gibt noch eine andere Antwort mit (normalerweise) besserer algorithmischer Leistung, obwohl dies mit größerem Aufwand weniger praktikabel ist. 

Andere Antworten umfassen die Shuffle-Methode und die Try-to-Valid-Methode mit Sätzen. 

Wenn wir zufällig K Ganzzahlen ohne Ersetzung aus dem Intervall 0 ... N-1 wählen, verwendet die Shuffle-Methode O(N) - Speicherung und O(N) - Operationen, was ärgerlich ist, wenn Wir wählen ein kleines K aus einem großen N. Die Set-Methode verwendet nur O(K) - Speicherung, hat aber den ungünstigsten Fall O(inf), das O (n * log (n)) für K nahe an erwartet N. (Stellen Sie sich vor, Sie versuchen zufällig, die letzte Zahl aus zwei zulässigen Antworten zu erhalten, nachdem Sie bereits 999998 ausgewählt haben, für k = n-1 = 10 ^ 6). 

Die gesetzte Methode ist also gut für K ~ 1 und die Shuffle-Methode ist gut für K ~ N. Beide verwenden erwartete> K RNG-Aufrufe. 

Ein anderer Weg; Sie können Pretend für die Fisher-Yates-Shuffle verwenden und für jede neue Zufallsauswahl eine binäre Suche mit den bereits ausgewählten Elementen durchführen, um den Wert zu ermitteln, den Sie mit () erhalten würden Sie haben tatsächlich ein Array aller Elemente gespeichert, die Sie noch nicht ausgewählt haben. 

Wenn Ihre bereits ausgewählten Werte [2,4] sind und Ihr Zufallszahlengenerator im Intervall 2 ausspuckt (N - num_already_selected), geben Sie vor, eine Auswahl aus [0,1,3,5,6, ..] zu treffen. .] durch Zählen der Werte, die unter der bereits ausgewählten Antwort liegen. In diesem Fall wäre Ihr dritter ausgewählter Wert 3. Wenn Ihre Zufallszahl dann im nächsten Schritt 2 wieder wäre, würde es 5 (in der pretend list [ 0,1,5,6]), weil der (potentielle Index von 5 in der sortierten Liste der bereits ausgewählten Werte [2,3,4], der 3 ist) + 2 = 5. 

Speichern Sie also die bereits ausgewählten Werte in einem ausgeglichenen binären Suchbaum, speichern Sie den Rang (Anzahl der Werte unter diesem Wert) an jedem Knoten, wählen Sie eine Zufallszahl R aus dem Bereich (0 ... n- (bereits gewählte Nummer) aus. ). Steigen Sie dann den Baum ab, als würden Sie suchen, aber Ihr Suchwert ist R plus dem Rang des Knotens, an dem Sie gerade sind. Wenn Sie einen Blattknoten erreichen, fügen Sie die Zufallszahl zum Rang dieses Knotens hinzu und fügen Sie die Summe in den ausgeglichenen Binärbaum ein. 

Sobald Sie K-Elemente haben, lesen Sie sie aus dem Baum in ein Array und mischen Sie sie (wenn die Reihenfolge wichtig ist). 

Dies erfordert O(K) Speicher, O (K * log (K)) - Leistung und genau K Randint-Aufrufe. 

Beispielhafte Implementierung von Zufallsstichproben (nicht zufällige endgültige Reihenfolge, aber Sie können O(K) Shuffle danach), O(k) Speicherung und O (k log ^ 2 (k)) Performance ( nicht O (k log (k)), da wir für diese Implementierung keine ausgeglichenen Binärbäume benutzerdefiniert absteigen können): 

from sortedcontainers import SortedList


def sample(n, k):
    '''
    Return random k-length-subset of integers from 0 to n-1. Uses only O(k) 
    storage. Bounded k*log^2(k) worst case. K RNG calls. 
    '''
    ret = SortedList()
    for i in range(k):
        to_insert = random.randint(0, n-1 - len(ret))
        to_insert = binsearch_adding_rank(ret, to_insert)
        ret.add(to_insert)

    return ret

def binsearch_adding_rank(A, v):
    l, u = 0, len(A)-1
    m=0
    while l <= u:
        m = l+(u-l)//2
        if v + m >= A[m]:
            l = m+1
            m+=1 # We're binary searching for partitions, so if the last step was to the right then add one to account for offset because that's where our insert would be.
        Elif v+m < A[m]:
            u = m-1
    return v+m

Und um Gültigkeit zu zeigen:

Wenn wir die Fisher-Yates-Shuffle machen würden, nachdem wir bereits [1,4,6,7,8,9,15,16] mit der Zufallszahl 5 gewählt haben, würde unser noch zu wählendes Array wie [0] aussehen 2,3,5,10,11,12, ...], also Element 5 ist 11. Also sollte unsere Binsearch-Funktion 11 zurückgeben, gegeben 5 und [1,4,6,7,8,9,15 16]:

assert binsearch_adding_rank([1,4,6,7,8,9,15,16], 5) == 11

Inverse von [1,2,3] ist [0,4,5,6,7,8, ...], dessen fünftes Element 8 ist, also: 

assert binsearch_adding_rank([1,2,3], 5) == 8

Inverse von [2,3,5] ist [0,1,4,6, ...], dessen 1. Element (noch) 1 ist, also: 

assert binsearch_adding_rank([2,3,5], 1) == 1

Inverse ist [0,6,7,8, ...], drittes Element ist 8 und:

assert binsearch_adding_rank([1,2,3,4,5,10], 3) == 8

Und um die Gesamtfunktion zu testen:

# Edge cases: 
assert sample(50, 0) == []
assert sample(50, 50) == list(range(0,50))

# Variance should be small and equal among possible values:
x = [0]*10
for i in range(10_000):
    for v in sample(10, 5):
        x[v] += 1
for v in x:
    assert abs(5_000 - v) < 250, v
del x

# Check for duplication: 

y = sample(1500, 1000)
assert len(frozenset(y)) == len(y)
del y

In der Praxis verwenden Sie jedoch die Shuffle-Methode für K ~> N/2 und die Set-Methode für K ~ <N/2. 

edit: Hier ist eine andere Möglichkeit, Rekursion zu verwenden! O (k * log (n)) denke ich. 

def divide_and_conquer_sample(n, k, l=0):
    u = n-1
    # Base cases:
    if k == 0:
        return []
    Elif k == n-l:
        return list(range(l, n))
    Elif k == 1:
        return [random.randint(l, u)]

    # Compute how many left and how many right:
    m = l + (u-l)//2
    k_right = 0
    k_left = 0
    for i in range(k):
        # Base probability: (# of available values in right interval) / (total available values)
        if random.random() <= (n-m - k_right)/(n-l-k_right-k_left):
            k_right += 1
        else:
            k_left += 1
    # Recur
    return divide_and_conquer_sample(n, k_right, m) + divide_and_conquer_sample(m, k_left, l)
5
wowserx

Wenn Sie extrem große Zahlen abfragen müssen, können Sie range nicht verwenden. 

random.sample(range(10000000000000000000000000000000), 10)

weil es wirft:

OverflowError: Python int too large to convert to C ssize_t

Wenn random.sample nicht die gewünschte Anzahl von Elementen erzeugen kann, weil der Bereich zu klein ist

 random.sample(range(2), 1000)

es wirft:

 ValueError: Sample larger than population

Diese Funktion löst beide Probleme:

import random

def random_sample(count, start, stop, step=1):
    def gen_random():
        while True:
            yield random.randrange(start, stop, step)

    def gen_n_unique(source, n):
        seen = set()
        seenadd = seen.add
        for i in (i for i in source() if i not in seen and not seenadd(i)):
            yield i
            if len(seen) == n:
                break

    return [i for i in gen_n_unique(gen_random,
                                    min(count, int(abs(stop - start) / abs(step))))]

Verwendung mit extrem großen Zahlen:

print('\n'.join(map(str, random_sample(10, 2, 10000000000000000000000000000000))))

Beispielergebnis:

7822019936001013053229712669368
6289033704329783896566642145909
2473484300603494430244265004275
5842266362922067540967510912174
6775107889200427514968714189847
9674137095837778645652621150351
9969632214348349234653730196586
1397846105816635294077965449171
3911263633583030536971422042360
9864578596169364050929858013943

Verwendung, bei der der Bereich kleiner als die Anzahl der angeforderten Elemente ist:

print(', '.join(map(str, random_sample(100000, 0, 3))))

Beispielergebnis:

2, 0, 1

Es funktioniert auch mit negativen Bereichen und Schritten:

print(', '.join(map(str, random_sample(10, 10, -10, -2))))
print(', '.join(map(str, random_sample(10, 5, -5, -2))))

Beispielergebnisse:

2, -8, 6, -2, -4, 0, 4, 10, -6, 8
-3, 1, 5, -1, 3
3
Handcraftsman

Wenn die Liste der N Zahlen von 1 bis N zufällig generiert wird, besteht die Möglichkeit, dass sich einige Zahlen wiederholen.

Wenn Sie eine Liste mit Zahlen von 1 bis N in zufälliger Reihenfolge wünschen, füllen Sie ein Array mit Ganzzahlen von 1 bis N und verwenden Sie dann eine Fisher-Yates-Zufallswiedergabe oder Python's random.shuffle() .

3
Mitch Wheat

Linearer kongruentieller Pseudozufallszahlengenerator

O (1) Speicher

O (k) Operationen

Dieses Problem kann mit einem einfachen Linear Congruential Generator gelöst werden. Dies erfordert einen konstanten Speicheraufwand (8 Ganzzahlen) und höchstens 2 * (Sequenzlänge) Berechnungen.

Alle anderen Lösungen benötigen mehr Speicher und mehr Rechenleistung! Wenn Sie nur wenige Zufallssequenzen benötigen, ist diese Methode deutlich günstiger. Wenn Sie für Bereiche der Größe N in der Reihenfolge N eindeutige k-Sequenzen oder mehr generieren möchten, empfehle ich die akzeptierte Lösung mit den integrierten Methoden random.sample(range(N),k), da dies wurde optimiert in Python auf Geschwindigkeit optimiert.

Code

# Return a randomized "range" using a Linear Congruential Generator
# to produce the number sequence. Parameters are the same as for 
# python builtin "range".
#   Memory  -- storage for 8 integers, regardless of parameters.
#   Compute -- at most 2*"maximum" steps required to generate sequence.
#
def random_range(start, stop=None, step=None):
    import random, math
    # Set a default values the same way "range" does.
    if (stop == None): start, stop = 0, start
    if (step == None): step = 1
    # Use a mapping to convert a standard range into the desired range.
    mapping = lambda i: (i*step) + start
    # Compute the number of numbers in this range.
    maximum = (stop - start) // step
    # Seed range with a random integer.
    value = random.randint(0,maximum)
    # 
    # Construct an offset, multiplier, and modulus for a linear
    # congruential generator. These generators are cyclic and
    # non-repeating when they maintain the properties:
    # 
    #   1) "modulus" and "offset" are relatively prime.
    #   2) ["multiplier" - 1] is divisible by all prime factors of "modulus".
    #   3) ["multiplier" - 1] is divisible by 4 if "modulus" is divisible by 4.
    # 
    offset = random.randint(0,maximum) * 2 + 1      # Pick a random odd-valued offset.
    multiplier = 4*(maximum//4) + 1                 # Pick a multiplier 1 greater than a multiple of 4.
    modulus = int(2**math.ceil(math.log2(maximum))) # Pick a modulus just big enough to generate all numbers (power of 2).
    # Track how many random numbers have been returned.
    found = 0
    while found < maximum:
        # If this is a valid value, yield it in generator fashion.
        if value < maximum:
            found += 1
            yield mapping(value)
        # Calculate the next value in the sequence.
        value = (value*multiplier + offset) % modulus

Verwendungszweck

Die Verwendung dieser Funktion "random_range" ist dieselbe wie für jeden Generator (wie "range"). Ein Beispiel:

# Show off random range.
print()
for v in range(3,6):
    v = 2**v
    l = list(random_range(v))
    print("Need",v,"found",len(set(l)),"(min,max)",(min(l),max(l)))
    print("",l)
    print()

Beispielergebnisse

Required 8 cycles to generate a sequence of 8 values.
Need 8 found 8 (min,max) (0, 7)
 [1, 0, 7, 6, 5, 4, 3, 2]

Required 16 cycles to generate a sequence of 9 values.
Need 9 found 9 (min,max) (0, 8)
 [3, 5, 8, 7, 2, 6, 0, 1, 4]

Required 16 cycles to generate a sequence of 16 values.
Need 16 found 16 (min,max) (0, 15)
 [5, 14, 11, 8, 3, 2, 13, 1, 0, 6, 9, 4, 7, 12, 10, 15]

Required 32 cycles to generate a sequence of 17 values.
Need 17 found 17 (min,max) (0, 16)
 [12, 6, 16, 15, 10, 3, 14, 5, 11, 13, 0, 1, 4, 8, 7, 2, ...]

Required 32 cycles to generate a sequence of 32 values.
Need 32 found 32 (min,max) (0, 31)
 [19, 15, 1, 6, 10, 7, 0, 28, 23, 24, 31, 17, 22, 20, 9, ...]

Required 64 cycles to generate a sequence of 33 values.
Need 33 found 33 (min,max) (0, 32)
 [11, 13, 0, 8, 2, 9, 27, 6, 29, 16, 15, 10, 3, 14, 5, 24, ...]
2
Thomas Lux

Eine sehr einfache Funktion, die auch Ihr Problem löst

from random import randint

data = []

def unique_Rand(inicial, limit, total):

        data = []

        i = 0

        while i < total:
            number = randint(inicial, limit)
            if number not in data:
                data.append(number)
                i += 1

        return data


data = unique_Rand(1, 60, 6)

print(data)


"""

prints something like 

[34, 45, 2, 36, 25, 32]

"""
0
Vinicius Torino

Das Problem bei den satzbasierten Ansätzen ("Wenn Zufallswerte in Rückgabewerten vorhanden sind, versuchen Sie es erneut") besteht darin, dass ihre Laufzeit aufgrund von Kollisionen (die eine weitere Iteration "Erneut versuchen" erfordern) unbestimmt ist, insbesondere wenn eine große Menge von Zufallswerten zurückgegeben wird aus dem Bereich.

Eine Alternative, die für diese nicht deterministische Laufzeit nicht anfällig ist, ist die folgende:

import bisect
import random

def fast_sample(low, high, num):
    """ Samples :param num: integer numbers in range of
        [:param low:, :param high:) without replacement
        by maintaining a list of ranges of values that
        are permitted.

        This list of ranges is used to map a random number
        of a contiguous a range (`r_n`) to a permissible
        number `r` (from `ranges`).
    """
    ranges = [high]
    high_ = high - 1
    while len(ranges) - 1 < num:
        # generate a random number from an ever decreasing
        # contiguous range (which we'll map to the true
        # random number).
        # consider an example with low=0, high=10,
        # part way through this loop with:
        #
        # ranges = [0, 2, 3, 7, 9, 10]
        #
        # r_n :-> r
        #   0 :-> 1
        #   1 :-> 4
        #   2 :-> 5
        #   3 :-> 6
        #   4 :-> 8
        r_n = random.randint(low, high_)
        range_index = bisect.bisect_left(ranges, r_n)
        r = r_n + range_index
        for i in xrange(range_index, len(ranges)):
            if ranges[i] <= r:
                # as many "gaps" we iterate over, as much
                # is the true random value (`r`) shifted.
                r = r_n + i + 1
            Elif ranges[i] > r_n:
                break
        # mark `r` as another "gap" of the original
        # [low, high) range.
        ranges.insert(i, r)
        # Fewer values possible.
        high_ -= 1
    # `ranges` happens to contain the result.
    return ranges[:-1]
0
orange

Sie können die Numpy -Bibliothek für eine schnelle Antwort verwenden (siehe unten).

Im Code-Snippet werden 6 unique -Zahlen zwischen 0 und 5 aufgeführt. Sie können die Parameter für Ihren Komfort einstellen.

import numpy as np
import random
a = np.linspace( 0, 5, 6 )
random.shuffle(a)
print(a)

Ausgabe

[ 2.  1.  5.  3.  4.  0.]

Es gibt keine Einschränkungen, wie wir in random.sample als hier sehen.

Hoffe das hilft ein bisschen.

0
dataLeo

Wenn Sie sicherstellen möchten, dass die hinzugefügten Zahlen eindeutig sind, können Sie ein Set-Objekt verwenden

wenn Sie 2.7 oder höher verwenden, importieren Sie das Sets-Modul, falls nicht.

Wie bereits erwähnt, sind die Zahlen nicht wirklich zufällig.

0
Recaiden

Die gelieferte Antwort hier funktioniert sehr gut in Bezug auf die Zeit Wie auch der Arbeitsspeicher, ist jedoch etwas komplizierter, da erweiterte Python-Konstrukte wie ___ verwendet werden. Die einfachere Antwort funktioniert in der Praxis gut, aber das Problem mit dieser -Antwort ist, dass sie viele falsche Ganzzahlen erzeugen kann, bevor sie tatsächlich die erforderliche Menge erstellt. Probieren Sie es mit PopulationSize = 1000, SampleSize = 999 ... aus. Theoretisch besteht die Möglichkeit, dass es nicht beendet wird.

In der Antwort unten werden beide Themen angesprochen, da sie deterministisch und etwas effizient ist Derzeit ist sie jedoch nicht so effizient wie die beiden anderen.

def randomSample(populationSize, sampleSize):
  populationStr = str(populationSize)
  dTree, samples = {}, []
  for i in range(sampleSize):
    val, dTree = getElem(populationStr, dTree, '')
    samples.append(int(val))
  return samples, dTree

wo die Funktionen getElem, percolateUp wie folgt definiert sind

import random

def getElem(populationStr, dTree, key):
  msd  = int(populationStr[0])
  if not key in dTree.keys():
    dTree[key] = range(msd + 1)
  idx = random.randint(0, len(dTree[key]) - 1)
  key = key +  str(dTree[key][idx])
  if len(populationStr) == 1:
    dTree[key[:-1]].pop(idx)
    return key, (percolateUp(dTree, key[:-1]))
  newPopulation = populationStr[1:]
  if int(key[-1]) != msd:
    newPopulation = str(10**(len(newPopulation)) - 1)
  return getElem(newPopulation, dTree, key)

def percolateUp(dTree, key):
  while (dTree[key] == []):
    dTree[key[:-1]].remove( int(key[-1]) )
    key = key[:-1]
  return dTree

Schließlich betrug das Timing im Durchschnitt etwa 15 ms für einen großen Wert von n (siehe unten).

In [3]: n = 10000000000000000000000000000000

In [4]: %time l,t = randomSample(n, 5)
Wall time: 15 ms

In [5]: l
Out[5]:
[10000000000000000000000000000000L,
 5731058186417515132221063394952L,
 85813091721736310254927217189L,
 6349042316505875821781301073204L,
 2356846126709988590164624736328L]
0
aak318