Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shuffling a list with restrictions in Python

I have a problem with randomizing a list with restrictions in Python (3). I have seen a few other questions relating to this, but none of them really seem to solve my problem. I'm a beginner, so any help is much appreciated!

I'm designing an experiment using two types of stimuli: shapes and colors (four of each). I need to generate permutations of all 16 combinations, which I have done with random.shuffle-function:

import random

# letters are shapes, numbers are colors
x=["a1","a2","a3","a4","b1","b2","b3","b4","c1","c2","c3","c4","d1","d2","d3","d4"]

random.shuffle(x)

So far so good. However, I want to avoid a shape (letter) or color (number) to appear two times in succession in my result (e.g. "a2" followed by "a4", or "c2" followed by "a2").

Is there a way to make such a restriction?
Thanks in advance!

like image 891
Frederik Avatar asked Mar 03 '16 21:03

Frederik


3 Answers

One way to handle this might be to have two lists one of shapes and one of colors. Shuffle each list individually. Now intermix the two lists. Since each list was built randomly, the mixed list is random as well, but you do not have any two entries together.

Note that using zip, you will actually get sets of pairs which will enable you to handle your test by getting each pair from the result.

In this particular case each color is a member of the list shapes while each color is a member of the list colors

shapes = ['a', 'b', 'c', 'd']
colors = ['1', '2', '3', '4']
zip(shapes, colors)
[('a', '1'), ('b', '2'), ('c', '3'), ('d', '4')]

This gives us each individual shuffle rather than generating all 16 possibilities at once and then shuffling them. This might enable you to generate your test better.

If you want to ensure that two sets of lists do not have the same color or shape in the same position as the previous group of four, then you can test for that after the shuffle against the previous setting.

testing = True
while testing:
    newcolors = colors
    random.shuffle(newcolors)
    # perform the test that you want to make get testresult True or False
    if testresult:
        colors = newcolors
        testing = False

This will keep shuffling until testresult becomes True and discard all invalid results from random.shuffle()

like image 198
sabbahillel Avatar answered Sep 28 '22 23:09

sabbahillel


Something like this should give a reasonable answer in a reasonable time

import random
while 1:
    choices = ["a1", "a2","a3","b1","b2","b3","c1","c2","c3"]

    shuffle = []

    last = ""

    while choices:
        l = choices
        if last:
            l = [x for x in l if x[0] != last[0] and x[1] != last[1]]
        if not l:
            #no valid solution
            break 
        newEl = random.choice(l)
        last = newEl
        shuffle.append(newEl)
        choices.remove(newEl)
    if not choices:
        print(shuffle)
        break
like image 22
Daniele Bernardini Avatar answered Sep 28 '22 23:09

Daniele Bernardini


I doubt this is the best way, but it is a way of doing this. If you think about your input as a matrix like this

a1, b1, c1, d1
a2, b2, c2, d2
a3, b3, c3, d3
a4, b4, c4, d4

Then you're goal becomes choosing an random index at each iteration such that the new index is not in the same row nor the same column of the matrix as the previous index and such that the new element has not been chosen before. Putting that into code naively, it becomes

import random
shapes_and_colors=["a1","a2","a3","a4","b1","b2","b3","b4","c1","c2","c3","c4","d1","d2","d3","d4"]
nRows = 4
nCols = 4
inds = [(x,y) for x in range(nRows) for y in range(nCols)]
def make_random(someArr):
    toRet = []
    n = len(someArr)
    for i in range(n):
        possible = [thing for thing in someArr if thing not in toRet]
        prev = poss = None
        while poss is None:
            next_val = random.choice(possible)
            if next_val == prev:
                #failed so try again
                return make_random(someArr)
            if not toRet or (next_val[0] != toRet[-1][0] and next_val[1] != toRet[-1][1]):
                poss = next_val 
            prev = next_val
        toRet += poss,
    return toRet



ans= [thing for thing in make_random(shapes_and_colors)]
print ans

Outputs After A Couple Runs

['c3', 'd4', 'c1', 'd3', 'b1', 'a4', 'b3', 'c4', 'a3', 'b2', 'a1', 'c2', 'd1', 'a2', 'b4', 'd2']
['d4', 'b3', 'c1', 'a4', 'b2', 'c4', 'd3', 'a1', 'c3', 'a2', 'b4', 'd2', 'a3', 'b1', 'c2', 'd1']

Disclaimer

Since this is a totally naive approach, it sometimes gets stuck! So suppose the last two indices remaining are [(2, 2), (3, 2)]. Then, there is no possible way for the algorithm to proceed without breaking the restrictions. Right now, I'm handling it with a recursive call, which isn't ideal.

like image 41
Garrett R Avatar answered Sep 29 '22 00:09

Garrett R