如何创建一个没有重复的随机数列表?
- 2024-12-05 08:38:00
- admin 原创
- 86
问题描述:
我尝试使用random.randint(0, 100)
,但有些数字相同。是否有方法/模块可以创建唯一随机数列表?
解决方案 1:
这将返回从 0 到 99 范围内选择的 10 个数字的列表,没有重复。
import random
random.sample(range(100), 10)
解决方案 2:
您可以像这样使用随机模块中的shuffle函数:
import random
nums = list(range(1, 100)) # list of integers from 1 to 99
# adjust this boundaries to fit your needs
random.shuffle(nums)
print(nums) # <- List of unique random numbers
请注意,shuffle 方法不会返回任何预期的列表,它只会对通过引用传递的列表进行打乱。
解决方案 3:
a
您可以首先创建一个从到的数字列表b
,其中a
和b
分别是列表中的最小数字和最大数字,然后使用Fisher-Yates算法或使用 Python 的random.shuffle
方法对其进行打乱。
解决方案 4:
线性同余伪随机数生成器
O(1) 内存
O(k) 运算
这个问题可以用一个简单的线性同余生成器来解决。这需要恒定的内存开销(8 个整数)和最多 2*(序列长度)次计算。
所有其他解决方案都使用更多内存和更多计算!如果您只需要几个随机序列,这种方法将便宜得多。对于大小范围N
,如果您想生成按N
唯一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
用法
此函数“random_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, ...]
解决方案 5:
该答案中提出的解决方案有效,但如果样本量很小但总体很大(例如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
解决方案 6:
如果随机生成从 1 到 N 的 N 个数字列表,那么确实存在一些数字重复的可能性。
如果您想要以随机顺序排列从 1 到 N 的数字列表,请用从 1 到 N 的整数填充数组,然后使用Fisher-Yates shuffle或 Python 的random.shuffle()
。
解决方案 7:
如果需要对非常大的数字进行采样,则不能使用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('
'.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
解决方案 8:
这是我制作的一个非常小的功能,希望对您有所帮助!
import random
numbers = list(range(0, 100))
random.shuffle(numbers)
解决方案 9:
一个非常简单的功能也可以解决你的问题
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]
"""
解决方案 10:
一个简单的替代方法是使用 np.random.choice(),如下所示
np.random.choice(range(10), size=3, replace=False)
这会产生三个彼此不同的整数。例如,[1, 3, 5],[2, 5, 1]...
解决方案 11:
这里提供的答案在时间和内存方面都运行良好,但由于使用了诸如yield之类的高级python构造,因此有点复杂。更简单的答案在实践中效果很好,但该答案的问题在于它可能在实际构建所需集合之前生成许多虚假整数。尝试使用populationSize = 1000,sampleSize = 999。理论上,它有可能不会终止。
下面的答案解决了这两个问题,因为它是确定性的并且有一定效率,尽管目前不如其他两个效率高。
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]
解决方案 12:
为了获得一个生成无重复随机值列表的程序,该程序是确定性的、高效的、用基本的编程结构构建的,请考虑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
我们还可以与早期的解决方案进行比较(针对较低的人口规模值)
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:虽然核心方法与我之前的回答类似,但在实施和方法上都有很大的修改,并且清晰度也有所提高。
解决方案 13:
基于集合的方法(“如果返回值中是随机值,则重试”)的问题在于,它们的运行时间由于碰撞而不确定(这需要另一次“重试”迭代),尤其是当从范围中返回大量随机值时。
不易出现这种非确定性运行时的替代方法如下:
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]
解决方案 14:
您可以使用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
正如我们在此处看到的,它没有施加任何限制。
解决方案 15:
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)
解决方案 16:
尝试使用...
import random
LENGTH = 100
random_with_possible_duplicates = [random.randrange(-3, 3) for _ in range(LENGTH)]
random_without_duplicates = list(set(random_with_possible_duplicates)) # This removes duplicates
优势
快速、高效、易读。
可能的问题
如果有重复,此方法可以改变列表的长度。
解决方案 17:
我做了一个快速而粗糙的调整函数(不会删除重复项)。您可以生成一个随机数列表并将其传递给此函数以获取唯一数字列表。这在您需要固定数量的值并保证随机数总和为固定值的情况下特别有用。
def adjust_dupes(rand_list):
while (len(set(rand_list)) != len(rand_list)):
for item in enumerate(rand_list):
# Check if duplicate element
if rand_list.count(item[1]) > 1:
rdx = random.randint(-1, 1)
rand_list[item[0]] += rdx;
if item[0] != len(rand_list)-1:
rand_list[item[0]+1] -= rdx;
else: rand_list[0] -= rdx
该set()
函数的最坏情况复杂度为 O(n),这意味着该函数的最坏情况复杂度可能为 O(n^n^n)(在您提供所有重复值的情况下)。请明智使用。
欢迎提出改进此功能的建议。
解决方案 18:
如果你希望确保添加的数字是唯一的,则可以使用Set 对象
如果使用 2.7 或更高版本,则导入集合模块。
正如其他人提到的,这意味着这些数字并不是真正随机的。
解决方案 19:
如果您想要的数字数量是随机的,您可以这样做。在这种情况下,长度是您想要选择的最大数字。
如果它注意到新的随机数已被选中,它将从计数中减去 1(因为在知道它是否重复之前添加了计数)。如果它不在列表中,则对其进行处理并将其添加到列表中,这样它就不会再次被选中。
import random
def randomizer():
chosen_number=[]
count=0
user_input = int(input("Enter number for how many rows to randomly select: "))
numlist=[]
#length = whatever the highest number you want to choose from
while 1<=user_input<=length:
count=count+1
if count>user_input:
break
else:
chosen_number = random.randint(0, length)
if line_number in numlist:
count=count-1
continue
if chosen_number not in numlist:
numlist.append(chosen_number)
#do what you want here
解决方案 20:
编辑:忽略我的回答。使用python的random.shuffle
或random.sample
,如其他答案中提到的。
在 minval
和 maxval
之间不放回地抽取整数:
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]
解决方案 21:
从 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:homelotto.bat
,或者直接运行C:homelotto
。
因为random.randint
经常重复一个数字,所以我使用set
withrange(7)
然后将其缩短为6长度。
偶尔,如果数字重复超过 2 次,则生成的列表长度将小于 6。
编辑:但是,random.sample(range(6,49),6)
这是正确的做法。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件