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

后端 未结 12 1977
没有蜡笔的小新
没有蜡笔的小新 2020-12-28 12:48

This works almost fine but the number starts with 0 sometimes:

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


        
12条回答
  •  不知归路
    2020-12-28 12:52

    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:

    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)

    First, d1 has to be patched up

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

    Then d2 has to "skip" d1 if necessary

    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. e.g. 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)

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

    2) It will print 4536 at the end (all results are distinct)

    One can add a verification for the first digit (d1), here and now I just assume that
    "(something mod 9)+1" will not be 0.

提交回复
热议问题