4
\$\begingroup\$

The task is simple: given N, generate a random integer with no repeating digits.

'''
Generate a random number of length, N and all digits
are unique
'''
from __future__ import print_function
import random
from collections import OrderedDict

# keep generating till all are unique
# This is a brute force approach where I store the digits generated
# so far in a OrderedDict and if the next random number is already
# there, i ignore it.
def randN(n):
    digits = OrderedDict()
    while len(digits) < n:
        d = random.randint(0, 9)
        if d == 0 and not digits.keys():
            continue
        else:
            if not digits.get(str(d), None):
                digits[str(d)] = 1
    return int(''.join(digits.keys()))

def _assert(randi, n):
    assert len(str(randi)) == n
    assert len(set(str(randi))) == n

for _ in range(100000):
    _assert(randN(10), 10)
    _assert(randN(1), 1)
    _assert(randN(5), 5)

I have a feeling there is a better approach to solve this, which is why I am posting it here.

\$\endgroup\$
  • \$\begingroup\$ You need to clarify whether the digits must be unique (in which case N must be <= 10) or have no repeating digits. The latter will accept any N and we want to make sure we generate the digits uniformly. I point this out because you say unique in a couple places, but "no repeating" in the task description. For uniqueness, you could use a regex like /(\d).*\1/ while leads to a very simple (though not efficient) solution. \$\endgroup\$ – DanaJ Nov 14 '14 at 4:59
4
\$\begingroup\$

Not quite one line, but here's a much simpler solution:

import random

def randN(n):
    assert n <= 10
    l = list(range(10)) # compat py2 & py3
    while l[0] == 0:
        random.shuffle(l)
    return int(''.join(str(d) for d in l[:n]))

Explanation:

The problem wants N unique digits. To get those, first make a list of all the digits, then shuffle them and cut off the unwanted digits (it should be noted that if you have many more than 10 digits, shuffling the whole lot would be expensive if you only want a handful).

To turn the list of digits into an integer, the shortest way is to stringify each digit and then join them into one string, and parse that back into an integer.

But wait! If the first digit is 0, the parsed string will be one digit shorter sometimes. So, earlier in the code, simply repeat the shuffle until some other digit comes first.

Since the initial order of the list has the 0 at the beginning, we don't need to do an extra shuffle before the loop.

\$\endgroup\$
  • 3
    \$\begingroup\$ You can use random.sample and xrange to simplify things a bit \$\endgroup\$ – raptortech97 Nov 14 '14 at 3:15
  • 1
    \$\begingroup\$ @raptortech97 Ugh, I should've thought of sample, but avoiding xrange was a deliberate simplification for py2/py3 compat since the performance won't matter for a list that small. \$\endgroup\$ – o11c Nov 14 '14 at 3:38
  • 1
    \$\begingroup\$ @raptortech97 thanks for the reference to random.sample(). \$\endgroup\$ – Amit Nov 14 '14 at 6:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.