问题
I have a set of 4 strings and want to generate a list of 16 elements, but with enforcing the rule (or obtaining the same result as enforcing such rule) to never have the same element repeated in two contiguous positions in the resulting list.
Being almost a total newbie in Python I went to check the different methods in the random library and found many different and useful ways to do something similar (random.shuffle would almost do the trick), but no one of those addressed this my particular need.
What data format and what methods should I use?
回答1:
Pseudocode algorithm:
- For i in n (n being the amount of elements you want)
- Generate the next element
- If it's the same as the previous element, repeat 2
Use random.choice
to pick an element from a list of elements randomly.
Here's a proof of concept Python code:
import random
sources = ['a', 'b', 'c', 'd'] # you said 4 strings
result = [random.choice(sources)]
while len(result) < 16: # you said you need 16 elements
elem = random.choice(sources)
if elem != result[-1]:
result.append(elem)
This code is optimized for clarity, not succinctness, cleverness or speed.
回答2:
For a more general solution, you could turn to Python generators.
Given an arbitrary iterable of inputs (eg: your four input strings), the following generator will generate an infinite iterable of choices from that list, with no two side-by-side elements being the same:
import random
def noncontiguous(inputs):
last = random.choice(inputs)
yield last
while True:
next = random.choice(inputs)
if next != last:
last = next
yield next
You can then use list comprehensions or a basic for loop to obtain the 16 element subset of this infinite sequence:
>>> gen = noncontiguous(['a', 'b', 'c', 'd'])
>>> [gen.next() for i in range(16)]
['c', 'b', 'c', 'b', 'a', 'c', 'b', 'c', 'd', 'a', 'd', 'c', 'a', 'd', 'b', 'c']
More interestingly, you can continue to use the same generator object to create more noncontiguous elements
>>> for i in range(8):
... gen.next()
...
'b'
'c'
'd'
'c'
'b'
'd'
'a'
'c'
回答3:
Zart's code modified to (a) work and (b) pre-calculate the set subtractions:
import random
def setsub():
# 4 strings
sources = ['a', 'b', 'c', 'd']
# convert them to set
input = set(sources)
subs = {}
for word in sources:
subs[word] = list(input - set([word]))
# choose first element
output = [random.choice(sources)]
# append random choices excluding previous element till required length
while len(output) < 16:
output.append(random.choice(subs[output[-1]]))
return output
回答4:
A rather severe abuse of itertools:
import itertools
import random
print list(itertools.islice((x[0] for x in
itertools.groupby(random.randint(1, 10) for y in itertools.count())), 16))
It uses islice()
to get the first 16 elements of an infinite generator based around count()
, using groupby()
to collapse equal adjacent elements.
回答5:
This is revised Eli's version that doesn't brute-forces elements, and hopefully doesn't lack clarity:
import random
# 4 strings
sources = ['a', 'b', 'c', 'd']
# convert them to set
input = set(sources)
# choose first element
output = [random.choice(input)]
# append random choices excluding previous element till required length
while len(output) < 16:
output.append(random.choice(input - set(output[-1:])))
来源:https://stackoverflow.com/questions/6647174/enforcing-no-2-same-contiguous-elements-in-random-list-generation