具有非重复元素和变化范围的随机发生器

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 函数的情况下解决这个问题?我不认为这个问题允许它,因为我应该构建自己的类。现在,我一直在尝试添加范围更改功能。

以上是关于具有非重复元素和变化范围的随机发生器的主要内容,如果未能解决你的问题,请参考以下文章

小结非正弦波形发生器的共同特点和分析方法以及与正弦波发生器的区别

发生索引超出范围异常[重复]

/dev/urandom

可重复使用的Pytorch结果和随机种子

NumPy数组元素在选中时会发生奇怪的变化

如何用c语言产生一定范围内的随机数?