0

I have 6 test questions that I want to randomize, together with their correct answers. Questions #1 and #2, #3 and #4, #5 and #6 are of the same type. In order not to make the test too easy, I don't want show #1 and #2 in a row (nor #3 and #4, or #5 and #6, for this matter).

For this purpose, I think I should shuffle a list [1, 2, 3, 4, 5, 6] with this constraint: 1 and 2, 3 and 4, 5 and 6 are not adjacent. For example, [1, 2, 4, 6, 3, 5] is not acceptable because 1 and 2 are next to each other. Then, I want apply the new order to both the question list and the answer list.

As someone new to programming, I only know how to shuffle a list without constraint, like so:

question = [1, 3, 5, 2, 4, 6]
answer = ['G', 'Y', 'G', 'R', 'Y', 'R']
order = list(zip(question, answer))
random.shuffle(order)
question, answer = zip(*order)

Any help would be appreciated!

ramund
  • 185
  • 11
  • 1
    what do you mean by "not adjacent to each other"? example or examples? – nikpod Jun 21 '17 at 05:47
  • Can you provide what you have tried so far and sample output in the question? – kuro Jun 21 '17 at 05:50
  • @nikpod By "not adjacent", I mean something like [1, 2, 3, 5, 4, 6], [3, 1, 5, 6, 2, 4], etc. is not acceptable, because 1 and 2 are adjacent in the former and 5 and 6 in the latter. – ramund Jun 21 '17 at 05:53
  • https://stackoverflow.com/questions/9660085/python-permutations-with-constraints this might help you get the solution – Aashish P Jun 21 '17 at 06:03

5 Answers5

5

Here's a "brute force" approach. It just shuffles the list repeatedly until it finds a valid ordering:

import random

def is_valid(sequence):
    similar_pairs = [(1, 2), (3, 4), (5, 6)]
    return all(
        abs(sequence.index(a) - sequence.index(b)) != 1
        for a, b in similar_pairs
    )

sequence = list(range(1, 7))
while not is_valid(sequence):
    random.shuffle(sequence)

print(sequence)

# One output: [6, 2, 4, 5, 3, 1]

For inputs this small, this is fine. (Computers are fast.) For longer inputs, you'd want to think about doing something more efficient, but it sounds like you're after a simple practical approach, not a theoretically optimal one.

user94559
  • 59,196
  • 6
  • 103
  • 103
  • You can do it in a more optimized way if you just not test the shuffles and create the random indices by yourself. This is somehow the most bruteforce way to go. – Mazdak Jun 21 '17 at 06:03
1

I see two simple ways:

  1. Shuffle the list and accept the shuffle if it satisfies the constraints, else repeat.

  2. Iteratively sample numbers and use the constraints to limit the possible numbers. For example, if you first draw 1 then the second draw can be 3..6. This could also result in a solution that is infeasible so you'll have to account for that.

Jacques Kvam
  • 2,856
  • 1
  • 26
  • 31
1

Draw a graph with your list elements as vertices. If elements u and v can be adjacent in the output list, draw an edge (u,v) between them, otherwise do not.

Now you have to find a Hamiltonian path on this graph. This problem is generally intractable (NP-complete) but if the graph is almost complete (there are few constraints, i.e. missing edges) it can be effectively solved by DFS.

For a small input set like in your example it could be easier to just generate all permutations and then filter out those that violate one of the constraints.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
0

You can try this. This should work fine for small lists. So as you can see below, I used a list of python sets for the constraints. The code builds the permutation you require element by element.

Building element by element can lead to invalid permutation if at some point the remaining elements in the list are all limited by the constraint.

Example: If the code makes 4,1,3,2,6 It is forced to try using 5 as the last element, but that is invalid, so the function tries to make another permutation.

It is better than the brute force approach(in terms of performance) of generating a random shuffle and checking if its valid(The answer given by smarx).

Note: The function would result in an infinite loop if no permutation satisfying the constraints is possible.

import random

def shuffler(dataList, constraints):
    my_data_list = list(dataList)
    shuffledList = [random.choice(dataList)]
    my_data_list.remove(shuffledList[0])
    for i in range(1, list_size):
        prev_ele = shuffledList[i - 1]
        prev_constraint = set()
        for sublist in constraints:
            if prev_ele in sublist:
                prev_constraint = set.union(prev_constraint, sublist)
        choices = [choice for choice in my_data_list if choice not in prev_constraint]
        if len(choices) == 0:
            print('Trying once more...')
            return shuffler(dataList,constraints)
        curr_ele = random.choice(choices)
        my_data_list.remove(curr_ele)
        shuffledList.append(curr_ele)
    return shuffledList

if __name__ == '__main__':
    dataList = [1, 2, 3, 4, 5, 6]
    list_size = len(dataList)
    constraints = [{1,2},{3,4},{5,6}]
    print(shuffler(dataList,constraints))
Arjun Balgovind
  • 596
  • 1
  • 5
  • 13
0

You could try something like:

shuffle the list
while (list is not good)
  find first invalid question
  swap first invalid question with a different random question
endwhile

I haven't done any timings, but it might run faster than reshuffling the whole list. It partly preserves the valid part before the first invalid question, so it should reach a good ordering faster.

rossum
  • 15,344
  • 1
  • 24
  • 38