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

后端 未结 12 1897
没有蜡笔的小新
没有蜡笔的小新 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:50

    I don't know Python so I will post a pseudo-code-ish solution for this specific problem:

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

    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:

    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());
    }

    0 讨论(0)
  • 2020-12-28 12:50

    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.

    numbers = iter(lambda: random.randrange(1000, 10000), None) # infinite generator
    test = lambda n: len(set(str(n))) == 4
    return next(filter(test, numbers))
    
    0 讨论(0)
  • 2020-12-28 12:52

    You could use full range for 3 numbers, then choose the leading number among the remaining numbers:

    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:

    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.

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-28 12:53

    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)))
    
    0 讨论(0)
  • 2020-12-28 12:59

    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:

    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.

    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.

    0 讨论(0)
提交回复
热议问题