如何生成一个不以0开头且每个数字都不相同的随机4位数?

40

这个基本可以工作,但是有时候数字以0开头:

import random
numbers = random.sample(range(10), 4)
print(''.join(map(str, numbers)))

我找到了很多例子,但没有一个保证序列不会以0开头。


3
range()函数中,你可以指定起始值。在你的情况下,起始值可以是1。 - kuro
33
这个功能基本上是有效的,但有时候数字会以0开头。如果你运行足够多次,大约有10%的概率会发生这种情况。 - WBT
5
相关 PPCG 挑战 - mbomb007
1
给楼主:请取消您对已接受答案的接受。它不应该是被接受的答案。它是前六个答案中最慢的,而且差距相当大。 - David Hammen
显示剩余5条评论
12个回答

70

我们在1-9范围内生成第一个数字,然后从剩余数字中取接下来的3个数字:

import random

# We create a set of digits: {0, 1, .... 9}
digits = set(range(10))
# We generate a random integer, 1 <= first <= 9
first = random.randint(1, 9)
# We remove it from our set, then take a sample of
# 3 distinct elements from the remaining values
last_3 = random.sample(digits - {first}, 3)
print(str(first) + ''.join(map(str, last_3)))
生成的数字均匀分布,我们可以在一步内获得有效数字。

12
@Claudio 生成的数字的独立性是一个好的伪随机生成器必须满足的基本属性之一。我认为Python的随机数生成器足够好,可以认为后续调用是独立的。 - Thierry Lathuille
2
请注意,尽管此方法确实以相同的概率生成每个可能的输出,但这个事实并不显而易见。它能够产生等概率的原因在于,在选择第一个数字之后,剩余可能数字的数量是相同的,无论选择哪个第一个数字。特别地,如果您尝试颠倒过程(即首先选择最低的三个不同数字,然后从剩下的七个中选择一个非零的第一个数字),输出将 不会 是等概率的。(读者练习:为什么?) - Ilmari Karonen
3
因为这个回答是最慢的,所以被踩了。它将集合运算和random.sample结合起来使用,两者都比较慢。 - David Hammen
1
@DavidHammen,这个回答为什么值得被踩呢?在我的机器上,这个回答比Austin Hastings的回答慢不到4微秒,而且问题中甚至没有提到性能。我不会认为在拒绝抽样技术方面采用建设性方法是“没用”的。 - miradulo
@Mitch - 主要是为了抵消我认为的其他答案因对拒绝采样概念缺乏理解而被完全无效地投票的影响。理解拒绝采样的简单方法:假设我让你生成所有四位数字且没有重复数字并以1开头的数字,你可以想出一个复杂的算法来完成这个任务,或者你可以使用[n for n in range(1000,10000)if len(set(str(n)))== 4]。 (续) - David Hammen
显示剩余2条评论

33

只要循环直到你找到喜欢的东西:

import random

numbers = [0]
while numbers[0] == 0:
    numbers = random.sample(range(10), 4)

print(''.join(map(str, numbers)))

7
@Claudio,概率仍然对所有结果均相等,因此我不确定您所说的“不如应该随机”是什么意思。 - muddyfish
6
@Claudio,很抱歉,但在建议所有答案都不正确之前,您可能应该咨询一些有关接受-拒绝抽样和条件概率的参考资料并进行审查。 - miradulo
1
这可能会无限循环。这不是一个多用途的解决方案。 - Lightness Races in Orbit
12
@BoundaryImposition 和所有其他的投票反对者:这是一个非常通用的解决方案。它甚至有一个名字,叫做“拒绝抽样”。大多数正态分布实现都使用拒绝抽样。 - David Hammen
4
@BoundaryImposition和其他所有的投票反对者:在Austin Hastings、MSeifert、karakfa和Thierry Lathuille的答案中,这个翻译是Python 2.7和Python 3.4中最快的。实际上,被接受的答案是最慢的,比这个答案(Python 2.7)慢40%。 - David Hammen
显示剩余2条评论

20

这与其他答案非常相似,但不是使用sampleshuffle函数,而是在1000-9999的范围内生成随机整数,直到其中的数字都是唯一的为止:

import random

val = 0  # initial value - so the while loop is entered.
while len(set(str(val))) != 4:  # check if it's duplicate free
    val = random.randint(1000, 9999)

print(val)

正如评论中@Claudio指出的那样,范围实际上只需要是1023-9876,因为该范围外的值包含重复数字。

通常情况下,random.randintrandom.shufflerandom.choice要快得多,因此即使需要多次抽取(如@karakfa所指出的),它也比任何shufflechoice方法快3倍,而后者还需要连接单个数字。


2
@MSeifert 当然,这是针对4位数的情况。如果是7位数,你的拒绝空间会爆炸并变慢(我知道这不是问题 :))。实际上,我最喜欢这个答案。 - miradulo
7
要使其更加完美,您可以进一步限制选择范围,例如使用 **random.randint(1023, 9876)**,对吧? - Claudio
1
@DavidHammen 注意,使用Python 3时,时间看起来会非常不同:http://ideone.com/ous7zZ。我对吗?似乎我的解决方案在Python 2上稍微慢了一点,但在Python 3上要快得多? - MSeifert
1
@MSeifert - 我没有注意到python3标签。我已经尝试过python2和python3,发现性能普遍非常糟糕。你的算法是唯一的例外,在python3中只比python2慢10%(其他算法需要两倍或更长时间)。两个版本的语言都有一个共同点,那就是被接受的答案很差劲。 - David Hammen
1
不错的答案。你可能对你代码的一行代码(在导入之后)的变体感兴趣。 - Eric Duminil
显示剩余15条评论

16

我对Python不是很熟悉,但是大概是这样的:

digits=[1,2,3,4,5,6,7,8,9] <- no zero
random.shuffle(digits)
first=digits[0] <- first digit, obviously will not be zero
digits[0]=0 <- used digit can not occur again, zero can
random.shuffle(digits)
lastthree=digits[0:3] <- last three digits, no repeats, can contain zero, thanks @Dubu

一个更有用的迭代,实际上创建一个数字:

digits=[1,2,3,4,5,6,7,8,9]   # no zero
random.shuffle(digits)
val=digits[0]                # value so far, not zero for sure
digits[0]=0                  # used digit can not occur again, zero becomes a valid pick
random.shuffle(digits)
for i in range(0,3):
  val=val*10+digits[i]       # update value with further digits
print(val)

在借鉴其他解决方案的基础上,并采用@DavidHammen的提示后:

val=random.randint(1,9)
digits=[1,2,3,4,5,6,7,8,9]
digits[val-1]=0
for i in random.sample(digits,3):
  val=val*10+i
print(val)

1
我不知道为什么这个被踩了,它很短且组合。这正是我在读标题时想到的做法。在获取第一个数字后将“first”设置为0是“聪明”的,我只会将零添加回列表中。 这看起来与Claudio的代码完全相同,但他的代码太长而且不易读。 这可以生成所有4536个答案,无需进行不必要的循环或测试。 - toddkaufmann
@toddkaufmann 我本来想这么做的,只是我不知道追加项目的语法,后来我意识到其实也不需要。 - tevemadar
@tevemadar -- 虽然正确,但对于一个九个元素的列表来说,这样做相当慢,需要调用两次 random.shuffle - David Hammen
@DavidHammen 我不知道还有其他的随机函数,比如random.sample(尽管我使用了几乎相同的变量名,但我没有发现后来被接受的答案,周围还有很多失败的答案尝试)。顺便添加第三个变量。 - tevemadar
@DavidHammen 谢谢,我已经在最终代码中加入了这个想法。如果你有强大的胃口,请在 https://dev59.com/eVcP5IYBdhLWcg3w8ea5#43631936 中找到一个快速但类似汇编的方法。 - tevemadar
显示剩余3条评论

11

拒绝采样法。从10个数字中创建一个4位数字的随机组合,如果不符合条件,则重新采样。

r4=0    
while r4 < 1000:
    r4=int(''.join(map(str,random.sample(range(10),4))))

注意到这与@Austin Haskings的答案本质上是相同的。


3
概率为零......你会惊讶于这种技术在实践中的普遍应用。 - karakfa
2
概率肯定_不是_零。 - Lightness Races in Orbit
1
n -> Infinity(也就是永远)时,lim 10^(-n) = 0 - karakfa
2
所有实际目的下的概率都是可以忽略不计的。例如,在我的电脑上循环1000次而没有生成有效样本(这需要16毫秒)的概率是10^(-1000)事件。 - karakfa
7
@BoundaryImposition - 你在这里打错了主意。拒绝抽样是一种非常常用的技术。这个答案唯一的问题是,Austin Hasking在两分钟之前写了一个非常相似的答案(可能是在karakfa写这个答案的时候),而且Austin Hasking的答案稍微更好一些。 - David Hammen
显示剩余2条评论

11

[修正] 将所有四个数字向左移动一个位置是不正确的。将前导零与固定位置交换也不正确。但是将前导零与九个位置中的任意一个位置进行随机交换是正确的,并且能够获得相等的概率:

""" Solution: randomly shuffle all numbers. If 0 is on the 0th position,
              randomly swap it with any of nine positions in the list.

  Proof
    Lets count probability for 0 to be in position 7. It is equal to probability 1/10 
  after shuffle, plus probability to be randomly swapped in the 7th position if
  0 come to be on the 0th position: (1/10 * 1/9). In total: (1/10 + 1/10 * 1/9).
    Lets count probability for 3 to be in position 7. It is equal to probability 1/10
  after shuffle, minus probability to be randomly swapped in the 0th position (1/9)
  if 0 come to be on the 0th position (1/10) and if 3 come to be on the 7th position
  when 0 is on the 0th position (1/9). In total: (1/10 - 1/9 * 1/10 * 1/9).
    Total probability of all numbers [0-9] in position 7 is:
  9 * (1/10 - 1/9 * 1/10 * 1/9) + (1/10 + 1/10 * 1/9) = 1
    Continue to prove in the same way that total probability is equal to
  1 for all other positions.
    End of proof. """

import random
l = [0,1,2,3,4,5,6,7,8,9]
random.shuffle(l)
if l[0] == 0:
    pos = random.choice(range(1, len(l)))
    l[0], l[pos] = l[pos], l[0]
print(''.join(map(str, l[0:4])))

3
但并不是每个可能的值都具有相同的概率。例如,123401234会产生相同的结果,而包含非领导零的数字(例如1023)只有一种可能性被抽取。 - MSeifert
随机打乱列表 l;如果 l[0] == 0,则从范围为1到10的数字中选择一个位置 pos,交换 l[0]l[pos] - miradulo
@MSeifert -- 是的。例如,取列表[0,1,2],仅组合两个不以零开头的唯一随机数字。只有3!== 6种可能性:[0,1,2]; [0,2,1]; [1,0,2]; [1,2,0]; [2,0,1]; [2,1,0]。如果我向右移动一个位置,那么[1,2]和[2,1]出现的次数将是[1,0]和[2,0]的两倍。因此,我应该使用Mitch的建议,并随机交换第二或第三位置的前导零。这将给出相同的概率。 - FooBar167
1
@MSeifert -- 让我们举一个简单的例子:列表[0,1,2]和两个不以0开头的唯一数字。共有3!= = 6种可能性。每种可能性的概率为1/6。有2个前导零:[0,1,2]和[0,2,1]。如果随机交换前导0和其余2个位置,则有四种组合:[0,1,2]==>([1,0,2]和[2,1,0]); [0,2,1]==>([2,0,1]和[1,2,0])。这四种组合中的每一种都具有相同的概率,等于1/6 * 1/2 == 1/12。但是这4种组合与其余4种可能性重合。因此,随机交换后的每种可能性的概率应该等于1/6 + 1/12。 - FooBar167
1
@MSeifert -- 添加了数学证明。 - FooBar167
显示剩余5条评论

7

您可以使用三个数字的全范围,然后在剩下的数字中选择前导数字:

import random
numbers = random.sample(range(0,10), 3)
first_number = random.choice(list(set(range(1,10))-set(numbers)))
print(''.join(map(str, [first_number]+numbers)))

如果需要重复选择(且数字数量合理),另一种方法是使用itertools.permutations预先计算可能的输出列表,过滤掉前导零的输出,并从中构建一个整数列表:

import itertools,random

l = [int(''.join(map(str,x))) for x in itertools.permutations(range(10),4) if x[0]]

那需要一些计算时间,但之后你就可以调用以下内容:
random.choice(l)

您可以随意多次使用它。它非常快速,并提供均匀分布的随机数。

10
除非允许的结果是等可能的,否则就不是等可能的。例如,得到1230有两种方法,但是得到1234只有一种方法。 - Tim Peters
@TimPeters 你是对的。添加了另一种解决方案。 - Jean-François Fabre
如果你得到了0023,那么你就有同样的问题。 - janscas
4
但是你不能拥有 0023。只有一个 0 - Jean-François Fabre

4

我不懂Python,因此我将发布一个伪代码般的解决方案:

  • Create a lookup variable containing a 0-based list of digits:

    lu = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • Generate four 0-based random numbers as follows:

    r1 = random number between 0 and 8
    r2 = random number between 0 and 8
    r3 = random number between 0 and 7
    r4 = random number between 0 and 6
    
  • Use the lookup variable to convert random numbers to digits one-by-one. After each lookup, mutate the lookup variable by removing the digit that has been used:

    d1 = lu[r1]
    lu.remove(d1)
    lu.insert(0)
    
    d2 = lu[r2]
    lu.remove(d2)
    
    d3 = lu[r3]
    lu.remove(d3)
    
    d4 = lu[r4]
    lu.remove(d4)
    
  • Print the result:

    print concatenate(d1, d2, d3, d4)
    

这个想法可以稍微概括一下。例如,您可以创建一个函数,接受一个数字列表和一个数字(结果的期望长度);该函数将返回数字并通过删除已使用完的数字来改变列表。以下是此解决方案的JavaScript实现:

function randomCombination(list, length) {
    var i, rand, result = "";
    for (i = 0; i < length; i++) {
        rand = Math.floor(Math.random() * list.length);
        result += list[rand];
        list.splice(rand, 1);
    }
    return result;
}

function desiredNumber() {
    var list = [1, 2, 3, 4, 5, 6, 7, 8, 9],
        result;
    result = randomCombination(list, 1);
    list.push(0);
    result += randomCombination(list, 3);
    return result;
}

var i;
for (i = 0; i < 10; i++) {
    console.log(desiredNumber());
}


2
这是我的操作步骤:
while True:
    n = random.randrange(1000, 10000)
    if len(set(str(n))) == 4: # unique digits
        return n

更一般地说,给定一个生成器,您可以使用内置的filternext函数来获取满足某些测试函数的第一个元素。

numbers = iter(lambda: random.randrange(1000, 10000), None) # infinite generator
test = lambda n: len(set(str(n))) == 4
return next(filter(test, numbers))

1
在Python中,函数外的return是语法错误。此外,请注意,您可以使用从10239876的范围,如这里所指出的那样。 - MSeifert

1

将生成器与next结合使用

编写Python代码的一种Pythonic方式是使用两个嵌套的生成器和next

from random import randint
from itertools import count

print(next(i for i in (randint(1023, 9876) for _ in count()) if len(set(str(i))) == 4))
# 8756

这基本上是 @MSeifert的答案 的一行代码版本。

预处理所有可接受的数字

如果您需要许多随机数字,可以投入一些时间和内存来预处理所有可接受的数字:

import random    
possible_numbers = [i for i in range(1023, 9877) if len(set(str(i))) == 4]

10239877被用作边界,因为没有小于1023或大于9876的整数可以有4个唯一的不同数字。

然后,您只需要使用random.choice进行非常快速的生成:

print(random.choice(possible_numbers))
# 7234

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