Python能否生成一个不使用递归的随机数,且排除指定数字集合?

33

我查看了Python文档(可能会有误解),但没有看到不调用递归函数就能完成这个(请看下面)的方法。
我的目的是生成一个排除中间值的随机数。

换句话说,
想象一下,我希望X是一个随机数,它不在
range(a - b, a + b)
范围内。我能否在第一次通过时做到这一点,
或者
1. 我必须不断生成一个数字,
2. 检查是否在range()内,
3. 反复洗涤?

至于为什么我不想编写递归函数:
1. 它“感觉”不应该这样做。
2. 我正在处理的数字集可能实际上会变得相当大,而且...我听说堆栈溢出很糟糕,我可能只是过于谨慎了。

我相信有一种漂亮的、Python式的非递归方式来完成它。

7个回答

44

生成一个随机数,并将其映射到你想要的数字范围中。

如果你想生成一个介于1-47-10之间的整数,但排除56,那么你可以:

  1. 在范围1-8内生成一个随机整数。
  2. 如果这个随机数大于4,则将2加到结果中。

映射如下:

Random number:    1  2  3  4  5  6  7  8
Result:           1  2  3  4  7  8  9 10

用这种方法,你永远不需要“重新洗牌”。上面的例子是针对整数的,但它也可以应用于浮点数。


+1 是对好的解释的肯定,这就是我想表达的,但仅凭代码来解释不够清晰。 - Andrew Gorcester
3
@AndrewG.:谢谢。:) 用几张图片来解释可能会更好,但是我今晚打开 Visio 的激活能有点高。;) - Li-aung Yip
非常感谢您提供的内容,其执行非常直接,并激发读者得出明显答案,而不是仅仅发布代码并说:“这里,运行它。”最终,我选择了Junuxx的解决方案,但我也很欣赏这个答案的优雅。 - user890167

31

使用random.choice()函数。 在这个例子中,a是下限,b到c之间的范围被跳过,而d是上限。

import random
numbers = range(a,b) + range(c,d)
r = random.choice(numbers)

13
除非可能的答案集非常大,否则它将起作用,但如果是这样,它将使用过多的内存并崩溃。 - Andrew Gorcester
@AndrewG:同意,如果您的范围大小为百万/十亿级别,则不是理想的选择。另一方面,它简单易记。虽然您的答案非常好且健壮,但可能更容易出错。 - Junuxx
1
是的。如果a、b、c和d从一开始就是已知的,并且被确认为一个小集合,我会使用你的答案;如果它们依赖于输入或者被确认为一个大集合,我会使用我的答案。 - Andrew Gorcester
1
@ihavenoidea: range 总是包括第一个参数但不包括第二个参数。所以在我的例子中,numbers 是 [a,b) + [c,d)。 - Junuxx
3
需要翻译的内容:May need to write it as numbers = list(range(a,b)) + list(range(c,d))可能需要写成这样:numbers = list(range(a,b)) + list(range(c,d)) - oatmilkyway
显示剩余3条评论

9

一个可能的解决方案是将随机数移出该范围。例如:

def NormalWORange(a, b, sigma):
    r = random.normalvariate(a,sigma)
    if r < a:
        return r-b
    else:
        return r+b

这将生成一个在范围(a-b,a+b)中有空洞的正态分布。

编辑:如果您想要整数,则需要更多的工作。 如果您想要处于范围[c,a-b]或[a + b,d]中的整数,则以下内容应该可以解决问题。

def RangeWORange(a, b, c, d):
    r = random.randrange(c,d-2*b) # 2*b because two intervals of length b to exclude
    if r >= a-b:
        return r+2*b
    else:
        return r

7
我可能误解了你的问题,但是您可以不使用递归来实现这个功能。
def rand(exclude):
    r = None
    while r in exclude or r is None:
         r = random.randrange(1,10)
    return r

rand([1,3,9])

虽然如此,您仍需循环遍历结果,直到找到新的结果。

4
最快的解决方案是这个(其中a和b定义了排除区域,c和d则是包括排除区域在内的好答案集合):
offset = b - a
maximum = d - offset
result = random.randrange(c, maximum)
if result >= a:
    result += offset

嗯,最后一段代码中肯定有逻辑错误——现在正在尝试调试。 - Andrew Gorcester
...哦。应该是偏移量= b - a。正在编辑。 - Andrew Gorcester
并且结果应该大于等于a。好了,就这样。 - Andrew Gorcester

0

你仍然需要一些范围,即排除中间值的最小-最大可能值。

为什么不先随机选择您想要的范围的“一半”,然后在该范围内随机选择一个数字?例如:

def rand_not_in_range(a,b):
    rangechoices = ((0,a-b-1),(a+b+1, 10000000))
    # Pick a half
    fromrange = random.choice(rangechoices)
    # return int from that range
    return random.randint(*fromrange)

2
这将在两个范围之间获得50/50的分布,无论它们相对大小如何。 - Andrew Gorcester

0
Li-aung Yip的回答使递归问题无关紧要,但我必须指出,可以进行任何程度的递归而不必担心栈,这称为“尾递归”。Python不直接支持尾递归,因为GvR认为它不酷。

http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html

但你可以绕过这个问题:

http://paulbutler.org/archives/tail-recursion-in-python/

我觉得有趣的是,Stick认为递归“感觉不对”。在极度面向函数的语言(如Scheme)中,递归是不可避免的。它使您能够进行迭代而不创建状态变量,这与函数式编程范例严格避免的方式相符。

http://www.pling.org.uk/cs/pop.html


作为一个新手程序员,这是我需要阅读的完全内容。谢谢!我并不一定对递归有过敏反应,说实话,我没有意识到即使是Python的BDFL也普遍对它表示鄙视;我只是有一种直觉,认为我不需要在这个特定的项目中使用它。但我不会排除在未来的努力中使用它。 - user890167
嘿,如果我的回答有用,请投票支持一下!我需要声望。 - Isaac Rabinovitch
我以为我做对了,我的错 :) - user890167

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接