具有非重复元素和变化范围的随机发生器
Posted
技术标签:
【中文标题】具有非重复元素和变化范围的随机发生器【英文标题】:A random generator with non-repeating elements and changing range 【发布时间】:2022-01-23 07:13:24 【问题描述】:我想实现一个随机生成器,以便它能够生成从 0 到 n 的随机数,但除非范围用尽,否则它不应该返回已返回的元素。例如如果范围是 0 到 7,上一代是 4,那么 4 将不会出现在随机生成中,直到 0-7 中的所有整数都返回。这是我目前所拥有的 - 我将生成的元素交换到数组的前面并缩小范围。
现在我必须实现一个函数来在生成时更改随机生成的范围,并且在范围更改后不重复条件仍然成立。该函数采用下限和上限,表示新范围,可以小于或大于旧范围。比如返回4后,我把范围改成2到7,那么列表2-7用完后,4就不会出现在生成中了。
我不知道这样做最有效的方法是什么。我尝试创建一个黑名单并在该范围内重新生成一个数组,其中包含不在黑名单中的数字,但是在列表用完后重置数组时遇到了一些问题。
class RandomGenerator:
def __init__(self, n):
self.n = n
self.start = 0
self.arr = list(range(n+1))
def generate(self):
if self.start > self.n:
self.arr = list(range(n+1))
r = random.randint(self.start, self.n)
out = self.arr[r]
temp = self.arr[self.start]
self.arr[self.start] = self.arr[r]
self.arr[r] = temp
self.start += 1
return out
编辑:这里我通过黑名单的方式实现了一个改变范围的功能。我不确定它是否是一个有效的解决方案,但它肯定不是有效的,因为我每次调用函数生成时都会生成一个新数组。我不知道如何使用原始交换元素的方法或是否可以。
import random
class RandomGenerator():
def __init__(self, lower, upper):
self.lower = lower
self.upper = upper
self.arr = list(range(lower, upper+1))
self.blacklist = []
def generate(self):
if not self.arr:
self.arr = list(range(self.lower, self.upper+1))
n = len(self.arr)-1
r = random.randint(0, n)
out = self.arr[r]
self.blacklist.append(out)
self.arr = [x for x in self.arr if x not in self.blacklist]
return out
def change_range(self, lower, upper):
self.lower = lower
self.upper = upper
gen = RandomGenerator(4, 7)
print(gen.generate())
print(gen.generate())
print(gen.generate())
print(gen.generate())
print(gen.generate())
gen.change_range(2, 7)
print(gen.generate())
print(gen.generate())
print(gen.generate())
【问题讨论】:
重置逻辑的意图并不完全清楚。你只是想重新回到你开始的地方,还是可能的数字范围应该增加?将其称为“生成器”也有点尴尬,因为这是 Python 中特定事物的名称,而您没有使用它。 我需要重新设置新的范围,新的范围可以比原来的范围小,因为它应该有一个上限和下限 但是新的范围是如何确定的呢?没有机会设置新参数,因为用户只是调用generate()
并期望从中获得新值。
新范围被输入。我需要在类中添加另一个函数 change_range(),所以例如我可以调用 RandomGenerator.change_range(2, 6) 来设置新范围
我们无法解决您未向我们展示的代码,因此如果您的 API 有更多问题,您真的需要将它包含在问题中,否则它不会成为我们答案的一部分。
【参考方案1】:
对于小的n
,你可以生成一个数字列表,随机生成shuffle
它,然后从中取出pop
元素;当它耗尽时,生成一个新的source
,然后重复。
import random
def get_random_without_repeats(n=8):
source = []
while True:
if not source:
source = list(range(n))
random.shuffle(source)
yield source.pop()
for elt in get_random_without_repeats():
print(elt)
您可以添加更复杂的逻辑来在耗尽后重新生成源,以满足您的需求。 例如,以下示例在每次源耗尽时从底部缩小值的范围
import random
def get_random_without_repeats(n=8):
source = []
turn = 0
while True:
if not source:
source = list(range(turn, n))
if not source:
return StopIteration
turn += 1
random.shuffle(source)
yield source.pop()
for elt in get_random_without_repeats():
print(elt)
【讨论】:
【参考方案2】:除非我遗漏了一些明显的东西,否则你可以在生成器中无休止地从 random.sample
中提取数字 - 它们保证永远不会重复,如果你迭代像 itertools.cycle
这样的无限迭代器,你就可以拥有池用尽给定范围内的所有数字后,可能的数字“重置”:
from itertools import islice
def get_numbers(minimum, maximum):
# minimum and maximum are inclusive
from itertools import cycle
from random import sample
for generator in cycle([sample]):
yield from generator(range(minimum, maximum+1), k=maximum+1)
print(list(islice(get_numbers(0, 7), 16)))
输出:
[0, 1, 4, 3, 7, 2, 6, 5, 1, 6, 0, 3, 7, 4, 5, 2]
【讨论】:
有没有办法在不使用内置的 itertools 函数的情况下解决这个问题?我不认为这个问题允许它,因为我应该构建自己的类。现在,我一直在尝试添加范围更改功能。以上是关于具有非重复元素和变化范围的随机发生器的主要内容,如果未能解决你的问题,请参考以下文章