This works almost fine but the number starts with 0 sometimes:
import random
numbers = random.sample(range(10), 4)
print(\'\'.join(map(str, numbers)))
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.