Another way of doing this is to notice that you condition excludes one option, e.g.: if the previous item was 7, then it only makes sense to choose one of the remaining 9 options. Hence make the max = 9, and add one if the variate is equal to or greater than 7.
For example:
from typing import List
from random import randint
def randseq_nondup(a: int, b: int, k: int) -> List[int]:
"""Generate k random values in [a,b], i.e. inclusive.
Ensures that subsequent values are never the same.
"""
result = [randint(a, b)]
for _ in range(k-1):
i = randint(a, b-1)
# add one when appropriate to keep values distinct
i += i >= result[-1]
result.append(i)
return result
For example, randseq_nondup(0, 9, 6) gives:
[1, 0, 5, 3, 4, 2]
when the RNG seeded with 42.
Some clarification for hiro:
This gives the same result as the other rejection sampling based approaches, but can be more efficient. Specifically it'll get more efficient as the as the number of options deceases, because there's a higher chance of randomly choosing the same value twice and the other approaches having to loop around and draw another value from the RNG.
Also, the probability of picking any value is only 1/10 the first time around, every time after that you can't pick the same value again so the probability of any specific value goes up to 1/9 (this is what the rejection is doing).
The way this algorithm shuffles up based on >= (e.g. rather than just ==) means that the resulting distribution is uniform. For example, imagine the first time we picked 4 the adjustment would work as follows:
before : 0 1 2 3 4 5 6 7 8
after : 0 1 2 3 5 6 7 8 9
notice that every value is uniquely mapped to a value (i.e. it's a bijection). So this algorithm will give uniform variates if the underlying RNG is uniform (the same as the rejection sampling based approaches).