简体   繁体   English

如何生成一个不以 0 开头且具有唯一数字的随机 4 位数字?

[英]How to generate a random 4 digit number not starting with 0 and having unique digits?

This works almost fine but the number starts with 0 sometimes:这几乎可以正常工作,但有时数字以 0 开头:

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

I've found a lot of examples but none of them guarantee that the sequence won't start with 0 .我找到了很多例子,但它们都不能保证序列不会以0开头。

We generate the first digit in the 1 - 9 range, then take the next 3 from the remaining digits:我们生成 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)))

The generated numbers are equiprobable, and we get a valid number in one step.生成的数字是等概率的,我们一步得到一个有效的数字。

Just loop until you have something you like:循环直到你有你喜欢的东西:

import random

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

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

This is very similar to the other answers but instead of sample or shuffle you could draw a random integer in the range 1000-9999 until you get one that contains only unique digits:这是非常相似的其他答案,但不是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)

As @Claudio pointed out in the comments the range actually only needs to be 1023 - 9876 because the values outside that range contain duplicate digits.正如@Claudio 在评论中指出的那样,范围实际上只需要 1023 - 9876,因为该范围之外的值包含重复的数字。

Generally random.randint will be much faster than random.shuffle or random.choice so even if it's more likely one needs to draw multiple times (as pointed out by @karakfa) it's up to 3 times faster than any shuffle , choice approach that also needs to join the single digits.通常random.randint将比random.shufflerandom.choice因此即使更有可能需要多次绘制(如@karakfa 所指出的),它也比任何shuffle快 3 倍, choice方法也需要join个位数。

I do not know Python well, but something like我不太了解 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

A more useful iteration, actually creating a number:一个更有用的迭代,实际上创建一个数字:

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)

After stealing pieces from other solutions, plus applying the tip from @DavidHammen:从其他解决方案中窃取碎片后,再加上@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)

rejection sampling method.拒绝抽样法。 Create a 4 digit random combination from 10 digits and resample if it doesn't match the criteria.从 10 位数字创建一个 4 位数字随机组合,如果不符合条件则重新采样。

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

noticed that this is essentially the same as @Austin Haskings's answer注意到这与@Austin Haskings 的回答基本相同

[Fixed] Shift all four digits on one position is not right. [已修复] 在一个位置上移动所有四位数字是不正确的。 Swap leading zero with fixed position is not right too.用固定位置交换前导零也不对。 But random swap of the leading zero with any of nine positions is correct and gives equal probability:但是前导零与九个位置中的任何一个随机交换是正确的,并且概率相等:

""" 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])))

You could use full range for 3 numbers, then choose the leading number among the remaining numbers:您可以对 3 个数字使用全范围,然后在剩余数字中选择前导数字:

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)))

Another way if the choice needs to be repeated (and if you remain reasonable on the number of digits), is to pre-compute the list of the possible outputs using itertools.permutations , filtering out the ones with a leading zero, and building a list of integers from it:如果需要重复选择(并且如果您对位数保持合理),另一种方法是使用itertools.permutations预先计算可能的输出列表,过滤掉带有前导零的那些,并构建一个来自它的整数列表:

import itertools,random

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

That's some computation time, but after than you can call:这是一些计算时间,但之后您可以调用:

random.choice(l)

as many times you want.你想要多少次。 It's very fast and provides an evenly distributed random.它非常快,并提供均匀分布的随机数。

I don't know Python so I will post a pseudo-code-ish solution for this specific problem:我不知道 Python,所以我将针对这个特定问题发布一个伪代码解决方案:

  • Create a lookup variable containing a 0-based list of digits:创建一个包含从 0 开始的数字列表的查找变量:

     lu = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • Generate four 0-based random numbers as follows:生成四个基于 0 的随机数,如下所示:

     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)

It is possible to generalize this idea a little.可以稍微概括一下这个想法。 For example you can create a function that accepts a list (of digits) and a number (desired length of result);例如,您可以创建一个函数,它接受一个列表(数字)和一个数字(所需的结果长度); the function will return the number and mutate the list by removing used-up digits.该函数将返回数字并通过删除用完的数字来改变列表。 Below is a JavaScript implementation of this solution:以下是此解决方案的 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()); }

Here's how I'd do it这是我的方法

while True:
    n = random.randrange(1000, 10000)
    if len(set(str(n))) == 4: # unique digits
        return n

More generally, given a generator, you can use the built-ins filter and next to take the first element that satisfies some test function.更一般地,给定一个生成器,您可以使用内置filternext来获取满足某个测试函数的第一个元素。

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

Combine generators with next将生成器与next结合

A Pythonic way to write would be to use 2 nested generators and next :一种 Pythonic 的编写方式是使用 2 个嵌套生成器和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

It's basically a one-liner variant of @MSeifert's answer它基本上是@MSeifert 答案的单行变体

Preprocess all the acceptable numbers预处理所有可接受的数字

If you need many random numbers, you could invest some time and memory for preprocessing all the acceptable numbers:如果您需要许多随机数,您可以投入一些时间和内存来预处理所有可接受的数字:

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

1023 and 9877 are used as boundaries because no int lower than 1023 or greater than 9876 can have 4 unique, distince numbers. 10239877被用作边界,因为低于 1023 或大于 9876 的整数不能有 4 个唯一的区别数字。

Then, you'd just need random.choice for a very fast generation:然后,您只需要random.choice生成非常快的一代:

print(random.choice(possible_numbers))
# 7234

Disclaimer: this is a terrible anti-Python approach, strictly for the benchmarking part (see @DavidHammen's comments around, and http://ideone.com/qyopLF ) The idea is to generate the sequence numbers of the digits in one step, and then fix any collisions:免责声明:这是一种糟糕的反 Python 方法,严格用于基准测试部分(参见 @DavidHammen 周围的评论和http://ideone.com/qyopLF )其想法是一步生成数字的序列号,并且然后修复任何冲突:

rnd=random.randint(0,4535)
(rnd,d1)=divmod(rnd,9)
(rnd,d2)=divmod(rnd,9)
#(rnd,d3)=divmod(rnd,8)
#(rnd,d4)=divmod(rnd,7)
(d4,d3)=divmod(rnd,8) # miracle found: 1 divmod happens to run faster than 2

Now we have d1=0..8, d2=0..8, d3=0..7, d4=0..6, it can be tested via running the snippet with rnd=4535 (4535=9*9*8*7-1, by the way)现在我们有 d1=0..8, d2=0..8, d3=0..7, d4=0..6,可以通过运行 rnd=4535 (4535=9*9* 8*7-1,顺便)

First, d1 has to be patched up首先,必须修补d1

d1=d1+1 # now d1 = 1..9

Then d2 has to "skip" d1 if necessary然后 d2 必须在必要时“跳过” d1

if d2>=d1
  d2=d2+1 # now d2 = 0..9 "-" d1

Then the same has to be done with the remaining digits, getting ugly fast:然后剩下的数字也要做同样的事情,变得很难看:

if d3>=d1:
  d3=d3+1    # now d3 = 0..8 "-" d1
  if d3>=d2:
    d3=d3+1  # now d3 = 0..9 "-" {d1,d2}
elif d3>=d2: # this branch prepares for the other variant
  d3=d3+1
  if d3>=d1: # ">=" is preserved for consistency, here "==" may occur only
    d3=d3+1

And the final part is the catastrophic one:最后一部分是灾难性的:

if d4>=d1:
  d4=d4+1
  if d4>=d2:
    d4=d4+1
    if d4>=d3:
      d4=d4+1
  elif d4>=d3:
    d4=d4+1
    if d4>=d2:
      d4=d4+1
elif d4>=d2:
  d4=d4+1
  if d4>=d1:
    d4=d4+1
    if d4>=d3:
      d4=d4+1
  elif d4>=d3:
    d4=d4+1
    if d4>=d1:
      d4=d4+1
elif d4>=d3:
  d4=d4+1
  if d4>=d2:
    d4=d4+1
    if d4>=d1:
      d4=d4+1
  elif d4>=d1:
    d4=d4+1
    if d4>=d2:
      d4=d4+1

For longer numbers, it might work faster with bitfields, but I do not see a trivial way.对于更长的数字,使用位域可能会更快,但我没有看到一种微不足道的方法。 (Checking the >= relations once is not enough, because the collision can easily occur after doing an incrementation. eg d1=1, d2=2, d3=1: d3 collides with d1, but it does not collide with d2 initially. However after "puching the hole" at 1, d3 becomes 2 and now it collides with d2. There is no trivial way to spot this collision in advance) (检查 >= 关系一次是不够的,因为在进行增量后很容易发生碰撞。例如 d1=1, d2=2, d3=1: d3 与 d1 碰撞,但最初并没有与 d2 碰撞。但是在 1 处“挖洞”后,d3 变为 2,现在它与 d2 碰撞。没有简单的方法可以提前发现这种碰撞)

As the code stinks as hell, I put a verification step at the end由于代码很烂,所以我在最后放了一个验证步骤

val = d1*1000 + d2*100 + d3*10 + d4
#if len(set(str(val))) != 4: print(str(val)+" "+str(o1)+","+str(o2)+","+str(o3)+","+str(o4))
if len(set(str(val))) != 4: print(val)

It is already faster than the other really fast code (the commented verification displayed the original digits preserved after the divmod-s, for debugging purposes. This is not the kind of code which works immediately...).它已经比其他真正快速的代码快了(注释验证显示了在 divmod-s 之后保留的原始数字,用于调试目的。这不是那种立即工作的代码......)。 Commenting both verifications makes it even faster.评论这两个验证使它更快。

EDIT: about checking this and that编辑:关于检查这个和那个

This is an approach maintaining an 1:1 relation between the minimal set of valid inputs (0...4535) and valid outputs (the 9*9*8*7 possible 4-digit numbers with distinct digits, not-starting-with-0).这是一种在最小有效输入集 (0...4535) 和有效输出(具有不同数字的 9*9*8*7 可能的 4 位数字,不以-0). So a simple loop can and should generate all the numbers, they can be checked one-by-one and they can be collected into a set for example in order to see if they are all distinct results所以一个简单的循环可以而且应该生成所有的数字,它们可以被一个一个地检查,它们可以被收集到一个集合中,例如,以查看它们是否都是不同的结果

Practically:几乎:

collect=set()
for rnd in range(0,4536):
    (rnd,d1)=divmod(rnd,9)
    ... rest of the code, also the verification step kept active ...
    collect.add(val)
print(len(collect))

1) It will not print anything in the loop (all results are 4-digit numbers with distinct digits) 1) 它不会在循环中打印任何内容(所有结果都是具有不同数字的 4 位数字)

2) It will print 4536 at the end (all results are distinct) 2)最后会打印4536(所有结果都不同)

One can add a verification for the first digit (d1), here and now I just assume that可以为第一个数字 (d1) 添加验证,现在我只是假设
"(something mod 9)+1" will not be 0. "(something mod 9)+1" 不会是 0。

这将允许第一个数字后为零 -

numbers = random.sample(range(1,10),1) + random.sample(range(10),3)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM