重複のない乱数のリストを作成するにはどうすればよいですか?


110

を使ってみましたrandom.randint(0, 100)が、同じ数値でした。リストの一意の乱数を作成するメソッド/モジュールはありますか?

注:次のコードは回答に基づいており、回答が投稿された後に追加されています。これは問題の一部ではありません。それが解決策です。

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);

1
それらが一意である場合、適切なコンテキストで真にランダムになる可能性があります。置き換えのないインデックスのランダムなサンプルのように、完全にランダムにすることができます。
gbtimmon 2016

回答:


180

これにより、0から99の範囲から選択された10個の数字のリストが重複なしに返されます。

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

特定のコード例を参照すると、ファイルからすべての行を一度読み取ってから、メモリに保存されているリストからランダムな行を選択する必要があります。例えば:

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

この方法では、ループの前に、実際にファイルから一度だけ読み取る必要があります。これを行う方が、ファイルの先頭に戻ってf1.readlines()ループを繰り返すたびに再度呼び出すよりもはるかに効率的です。


2
この手法は、特に大きなサンプルの場合、メモリを浪費します。線形合同ジェネレーターを使用する、より多くのメモリと計算効率の良いソリューションのコードを以下に投稿しました。
トーマスラックス

しかし、LCGメソッドは「ランダム」ではないことが指摘されたため、多くの固有のランダムシーケンスを生成したい場合、多様性はこのソリューションよりも少なくなります。ランダムなシーケンスが少しだけ必要な場合は、LCGが最適です。
トーマスラックス

グレッグに感謝します、それは役に立ちました
N Sivaram

15

次のように、randomモジュールのshuffle関数を使用できます。

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

ここで、shuffleメソッドは期待どおりのリストを返さず、参照で渡されたリストのみをシャッフルすることに注意してください。


xrangeのは、Python 3でのPython 2でのみ動作していないことをここで言及するのは良いです
Shayan Shafiq

10

最初にからaまでの数値のリストを作成できますb。ここでa、およびbはそれぞれ、リスト内の最小および最大の数値です。次に、Fisher-Yatesアルゴリズムを使用して、またはPythonのrandom.shuffle方法を使用してそれをシャッフルします。


1
インデックスの完全なリストを生成すると、特に大きなサンプルの場合、メモリの浪費になります。線形合同ジェネレーターを使用する、より多くのメモリと計算効率の良いソリューションのコードを以下に投稿しました。
トーマスラックス

8

この回答で提示された解決策は機能しますが、サンプルサイズは小さいが、母集団が大きい場合(例:)、メモリに問題が生じる可能性がありrandom.sample(insanelyLargeNumber, 10)ます。

それを修正するために、私はこれで行きます:

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

今、random.sampleメモリと、この問題は本当にもう存在しないので、大規模な集団からのサンプル数が少ないため、このアプローチを使用しています。この回答が書かれた時点では、の実装はrandom.shuffle異なる可能性があります。
キリル

5

線形合同擬似乱数ジェネレータ

O(1)メモリ

O(k)操作

この問題は、単純な線形合同ジェネレーターで解決できます。これには、一定のメモリオーバーヘッド(8整数)と最大2 *(シーケンス長)の計算が必要です。

他のすべてのソリューションは、より多くのメモリと計算量を使用します!いくつかのランダムなシーケンスのみが必要な場合、この方法は大幅に安価になります。サイズの範囲についてNN一意のkシーケンス以上の順序で生成する場合は、組み込みのメソッドを使用して受け入れられたソリューションをお勧めします。random.sample(range(N),k)これ、Pythonで速度が最適化さているためです。

コード

# 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

使用法

この関数「ran​​dom_range」の使用法は、任意のジェネレーター(「range」など)と同じです。例:

# 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()

結果の例

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, ...]

1
かっこいい!しかし、私はそれが本当に質問に答えることを確信しています。私が0から4までの2つの値をサンプリングしたいとします。自分primeでを生成しないと、関数は4つの可能な答えのみを返しvalueます。 6(非ランダムな順序付けが可能)。random_range(2,4)値{(1、0)、(3、2)、(2、1)、(0、3)}を返しますが、ペア(3,1)(または(1,3))は返しません。各関数呼び出しでランダムに生成された新しい大きな素数を期待していますか?
wowserx 2018年

1
(また、私は、あなたは彼らがランダムな順序付けをしたい場合は、人々があなたの関数が戻ることをした後、シーケンスをシャッフルすることを期待することを仮定しているからrandom_range(v)に戻っまでvユニークな配列の代わりv!
wowserx

ほんと!整数のオーバーフローを回避することと十分なランダムシーケンスを生成することのバランスを取るのは困難です。関数を更新してもう少しランダム性を取り入れましたが、それでもv!ほどランダムではありません。関数を複数回使用するかどうかによって異なります。このソリューションは、広範囲の値から生成する場合(他のメモリの消費量がはるかに多い場合)に最適です。もっと考えます、ありがとう!
トーマスラックス

4

1からNまでのN個の数値のリストがランダムに生成された場合、はい、いくつかの数値が繰り返される可能性があります。

ランダムな順序で1からNまでの数値のリストが必要な場合は、配列に1からNまでの整数を入力してから、Fisher-YatesシャッフルまたはPythonを使用しますrandom.shuffle()


3

非常に大きな数をサンプリングする必要がある場合は、使用できません range

random.sample(range(10000000000000000000000000000000), 10)

それが投げるので:

OverflowError: Python int too large to convert to C ssize_t

また、random.sample範囲が小さすぎるために必要な数のアイテムを生成できない場合

 random.sample(range(2), 1000)

それは投げます:

 ValueError: Sample larger than population

この関数は両方の問題を解決します:

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))))]

非常に大きな数での使用:

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

結果の例:

7822019936001013053229712669368
6289033704329783896566642145909
2473484300603494430244265004275
5842266362922067540967510912174
6775107889200427514968714189847
9674137095837778645652621150351
9969632214348349234653730196586
1397846105816635294077965449171
3911263633583030536971422042360
9864578596169364050929858013943

範囲がリクエストされたアイテムの数よりも少ない場合の使用法:

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

結果の例:

2, 0, 1

また、負の範囲とステップでも機能します。

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

結果の例:

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

80億以上の数値を生成すると、遅かれ早かれ、大きくなりすぎます
david_adler

この回答には、大きなサンプルには重大な欠陥があります。衝突の確率は、各ステップで直線的に増加します。私は、O(1)のメモリオーバーヘッドとk個の数値を生成するために必要なO(k)のステップがある線形合同ジェネレータを使用してソリューションを投稿しました。これははるかに効率的に解決できます!
トーマスラックス

この答えは、シーケンスの長さのオーダーで多数のランダムシーケンスを生成する場合に間違いなく優れています。複数の一意のシーケンスを生成することに関しては、LCGメソッドは「ランダム」ではありません。
トーマスラックス

「この機能は両方の問題を解決します」 2番目の問題をどのように解決しますか?それでも、2の母集団から1000サンプルを取得することはできません。例外をスローする代わりに、誤った結果を生成します。これは「問題」の解決策とは言えません(n <kの母集団からk個の一意のサンプルを要求することはまったく理に適っていないため、これは最初から問題ではありません)。
キリル

1

以下に示すように、Numpyライブラリを使用してすばやく回答できます-

与えられたコードスニペットは、0から5の範囲内の6つの一意の数値をリストします。パラメータは、快適に調整できます。

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

出力

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

ここで参照されているrandom.sampleにあるように、制約はありません。

これが少し役に立てば幸いです。


1

ここで提供される答えは、時間とメモリの点で非常にうまく機能しますが、yieldなどの高度なpythonコンストラクトを使用するため、少し複雑になります。単純な答えは、実際にはうまく動作しますが、その答えの問題は、それが実際に必要なセットを構築する前に、多くの偽の整数を発生させることができるということです。PopulationSize = 1000、SampleSize = 999で試してみてください。理論的には、終了しない可能性があります。

以下の回答は両方の問題に対処しています。決定論的であり、多少効率的ですが、現在他の2つほど効率的ではありません。

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

関数getElem、percolateUpは以下のように定義されています

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

最後に、以下に示すように、nの大きな値の平均タイミングは約15msでした。

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]

あなたは考えることの答えが複雑になりますか?では、これは何ですか。そして、多くの「偽の整数」を生成する別の答えがあります。私はあなたが与えた入力例(populationSize = 1000、sampleSize = 999)で実装を実行しました。あなたのバージョンはrandom.randint関数を3996回呼び出しますが、もう1つはccaです。6000回。それほど大きな改善ではありませんか?
キリル


1

確定的で効率的であり、基本的なプログラミング構造で構築された重複のないランダムな値のリストを生成するプログラムを取得するには、extractSamples以下で定義されている関数を検討します。

def extractSamples(populationSize, sampleSize, intervalLst) :
    import random
    if (sampleSize > populationSize) :
        raise ValueError("sampleSize = "+str(sampleSize) +" > populationSize (= " + str(populationSize) + ")")
    samples = []
    while (len(samples) < sampleSize) :
        i = random.randint(0, (len(intervalLst)-1))
        (a,b) = intervalLst[i]
        sample = random.randint(a,b)
        if (a==b) :
            intervalLst.pop(i)
        elif (a == sample) : # shorten beginning of interval                                                                                                                                           
            intervalLst[i] = (sample+1, b)
        elif ( sample == b) : # shorten interval end                                                                                                                                                   
            intervalLst[i] = (a, sample - 1)
        else :
            intervalLst[i] = (a, sample - 1)
            intervalLst.append((sample+1, b))
        samples.append(sample)
    return samples

基本的な考え方はintervalLst、必要な要素を選択するための可能な値の間隔を追跡することです。これは、固定数のステップ(populationSizeおよびにのみ依存sampleSize)内でサンプルを生成することが保証されているという意味で確定的です。

上記の関数を使用して必要なリストを生成するには、

In [3]: populationSize, sampleSize = 10**17, 10**5

In [4]: %time lst1 = extractSamples(populationSize, sampleSize, [(0, populationSize-1)])
CPU times: user 289 ms, sys: 9.96 ms, total: 299 ms
Wall time: 293 ms

以前のソリューションと比較することもできます(populationSizeの値が小さい場合)

In [5]: populationSize, sampleSize = 10**8, 10**5

In [6]: %time lst = random.sample(range(populationSize), sampleSize)
CPU times: user 1.89 s, sys: 299 ms, total: 2.19 s
Wall time: 2.18 s

In [7]: %time lst1 = extractSamples(populationSize, sampleSize, [(0, populationSize-1)])
CPU times: user 449 ms, sys: 8.92 ms, total: 458 ms
Wall time: 442 ms

ソリューションpopulationSizeを使用すると、より高い値に対してメモリエラーが発生するため、値を減らしたことに注意してくださいrandom.sample(以前の回答のここここにも記載されています)。上記の値についてextractSamplesは、random.sampleアプローチよりも優れていることも確認できます。

PS:コアアプローチは以前の回答と似ていますが、明確化の改善に加えて、実装とアプローチに大幅な変更があります。


0

問題を解決する非常にシンプルな機能

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

セットベースのアプローチの問題(「戻り値にランダムな値がある場合は、再試行する」)は、特に大量のランダムな値が返される場合、衝突(別の「再試行」が必要)が原因でランタイムが不定になることです。範囲から。

この非決定的なランタイムになりにくい代替策は次のとおりです。

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
import random

sourcelist=[]
resultlist=[]

for x in range(100):
    sourcelist.append(x)

for y in sourcelist:
    resultlist.insert(random.randint(0,len(resultlist)),y)

print (resultlist)

1
Stackoverflowへようこそ。他の人があなたの答えを簡単に理解できるように、なぜ、どのように問題を解決するのか、あなたの答えを説明してください。
オクトバス

このコードは問題を解決する可能性がありますが、これが問題を解決する方法と理由の説明含めると、投稿の品質が向上し、投票数が増える可能性があります。あなたが今尋ねている人だけでなく、将来の読者のための質問に答えていることを忘れないでください。回答を編集して説明を追加し、適用される制限と前提を示してください。口コミから
ダブルビープ

-1

追加する番号が一意であることを確認したい場合は、Setオブジェクトを使用できます

2.7以上を使用している場合、または使用していない場合は、setsモジュールをインポートします。

他の人が述べたように、これは数が本当にランダムではないことを意味します。


-1

間の交換なしのサンプル整数にminvalmaxval

import numpy as np

minval, maxval, n_samples = -50, 50, 10
generator = np.random.default_rng(seed=0)
samples = generator.permutation(np.arange(minval, maxval))[:n_samples]

# or, if minval is 0,
samples = generator.permutation(maxval)[:n_samples]

jax付き:

import jax

minval, maxval, n_samples = -50, 50, 10
key = jax.random.PRNGKey(seed=0)
samples = jax.random.shuffle(key, jax.numpy.arange(minval, maxval))[:n_samples]

おそらく、非常に多数の要素の順列を生成し、n_samplesそれらの最初の要素のみを選択するのはなぜですか?このアプローチの背後にあるあなたの理由は何ですか?既存の多数の回答(ほとんどは8年前のもの)と比較して、アプローチの利点は何ですか?
キリル

実際、私の回答は他のトップ投票の回答と同様の複雑さを持ち、numpyを使用しているためより高速です。他の、トップ投票されたメソッドrandom.shuffleはMersenne Twisterを使用し、qhichはnumpy(そしておそらくjax)が提供するアルゴよりはるかに遅いです。numpyとjaxでは、他の乱数生成アルゴリズムを使用できます。jaxはjitコンパイルと微分も可能にします。これは確率的微分に役立ちます。また、「可能性大」配列に関して、いくつかのトップは、答えはとまったく同じことやる投票random.shuffle私は相対的あるいは絶対的な意味で罪深いとは思わない、
grisaitisを

1
random.shuffleMersenne Twisterを使用している」という意味がわからない-いくつかの回答で述べたように、Fisher-Yatesのシャッフルです。それは線形時間の複雑さを持っているので、他のライブラリ、numpyなどによって提供されるアルゴリズムよりも漸近的に遅くなる可能性はありません。numpyの方が速い場合、それはCで実装されているためですが、これは巨大な順列(メモリに収まらない場合もある)を生成することを保証せず、そこからいくつかの要素を選択するだけです。これを行うあなたのほかに単一の答えはありません。
キリル

申し訳ありませんが、私はpythonランダムがMersenne Twisterを使用していることを読みました。フィッシャーイェイツとrandom.shuffleでの役割について詳しく知るためのソースはありますか?
グリザ炎

ウィキペディアへの2つの別々のリンクがここに2つの別々の答えですでにあります。ウィキペディアが十分な情報源でない場合は、記事の最後に14の参考文献があります。そして、Googleがあります。それは役に立ちますか?ああ、randomモジュールはPythonで書かれているので、そのソースを簡単に表示できます(random.__file__)。
キリル

-3

win xpのCLIから:

python -c "import random; print(sorted(set([random.randint(6,49) for i in range(7)]))[:6])"

カナダでは6/49ロトがあります。上記のコードをlotto.batでラップして実行するC:\home\lotto.batか、単にC:\home\lotto

random.randintしばしば数字を繰り返すので、私はで使用setrange(7)、次にそれを6の長さに短くします。

数が2回以上繰り返される場合、結果のリストの長さが6未満になることがあります。

編集:ただし、random.sample(range(6,49),6)正しい方法です。


-3
import random
result=[]
for i in range(1,50):
    rng=random.randint(1,20)
    result.append(rng)

1
これにより重複を回避する方法を説明できますか?このコードダンプからは明らかではありません。
Toby Speight 2018

そうではありません。print len(result), len(set(result))。あなたはそれresultが毎回一度だけユニークな要素を持っていることを見ると期待するでしょ1.0851831788708547256608362340568947172111832359638926... × 10^20う。
ジェダイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.