перетасовка python, так что позиция никогда не будет повторяться

Я хотел бы сделать случайное перемешивание списка, но с одним условием: элемент никогда не может находиться в той же исходной позиции после перемешивания.

Есть ли однострочный способ сделать это в python для списка?

Пример:

list_ex = [1,2,3]

каждый из следующих перетасованных списков должен иметь одинаковую вероятность выборки после перетасовки:

list_ex_shuffled = [2,3,1]
list_ex_shuffled = [3,1,2]

но перестановки [1,2,3], [1,3,2], [2,1,3] и [3,2,1] не допускаются, поскольку все они повторяют одну из позиций элементов.

ПРИМЕЧАНИЕ. Каждый элемент в list_ex имеет уникальный идентификатор. Не допускается повторение одного и того же элемента.

Любые идеи? Спасибо!


person Dnaiel    schedule 19.03.2013    source источник
comment
Что вы ожидаете, когда ваш список содержит несколько элементов, которые равны друг другу. Например, что вы хотите, чтобы произошло, когда список [2, 2, 2]?   -  person crayzeewulf    schedule 20.03.2013
comment
Сочетание использования deque и его метода rotate может привести к ожидаемый результат. Но в некоторых случаях, как в приведенном выше комментарии, у вас нет четкого определения ожидаемого результата.   -  person sean    schedule 20.03.2013
comment
@crayzeewulf хорошее замечание! структура моих данных никогда не столкнется с таким случаем, спасибо!   -  person Dnaiel    schedule 20.03.2013
comment
@sean, не могли бы вы дать мне больше деталей, звучит как хороший подход, чтобы попробовать, спасибо!   -  person Dnaiel    schedule 20.03.2013


Ответы (6)


Рандомизируйте в цикле и продолжайте отклонять результаты, пока ваше условие не будет выполнено:

import random

def shuffle_list(some_list):
    randomized_list = some_list[:]
    while True:
        random.shuffle(randomized_list)
        for a, b in zip(some_list, randomized_list):
            if a == b:
                break
        else:
            return randomized_list
person tdelaney    schedule 19.03.2013
comment
спасибо, это то, о чем я думал в начале, хотя не уверен, насколько эффективно с точки зрения времени... - person Dnaiel; 20.03.2013
comment
Ожидаемое количество необходимых перетасовок равно e, что меньше 3 stackoverflow.com/a/15513026/284795 - person Colonel Panic; 20.03.2013
comment
@Dnaiel - эффективность будет проблемой только для больших списков или множества итераций. Легко уменьшить случайность, если поумнеть с оптимизацией. Это решение не заставляет меня слишком много думать. - person tdelaney; 20.03.2013

Я бы назвал такие перетасовки «перестановками без фиксированных точек». Их также называют психами.

Вероятность того, что случайная перестановка является расстройством, приблизительно равна 1/e (забавно доказывать). Это правда, каким бы длинным ни был список. Таким образом, очевидный алгоритм получения случайного расстройства состоит в том, чтобы перетасовать карты как обычно и продолжать перемешивание до тех пор, пока не возникнет расстройство. Ожидаемое количество необходимых перетасовок составляет около 3, и редко вам придется перетасовывать более десяти раз.

(1-1/e)**11 < 1%

Предположим, что на вечеринке n человек, каждый из которых принес зонтик. В конце вечеринки каждый берет зонтик из корзины наугад. Какова вероятность того, что ни у кого нет собственного зонта?

person Colonel Panic    schedule 20.03.2013

Вы можете сгенерировать все возможные допустимые перетасовки:

>>> list_ex = [1,2,3]
>>> import itertools

>>> list(itertools.ifilter(lambda p: not any(i1==i2 for i1,i2 in zip(list_ex, p)),
...                        itertools.permutations(list_ex, len(list_ex))))
[(2, 3, 1), (3, 1, 2)]

Для другой последовательности:

>>> list_ex = [7,8,9,0]
>>> list(itertools.ifilter(lambda p: not any(i1==i2 for i1,i2 in zip(list_ex, p)),
...                        itertools.permutations(list_ex, len(list_ex))))
[(8, 7, 0, 9), (8, 9, 0, 7), (8, 0, 7, 9), (9, 7, 0, 8), (9, 0, 7, 8), (9, 0, 8, 7), (0, 7, 8, 9), (0, 9, 7, 8), (0, 9, 8, 7)]

Вы также можете сделать это немного более эффективным, закоротив итератор, если вам нужен только один результат:

>>> list_ex = [1,2,3]
>>> i = itertools.ifilter(lambda p: not any(i1==i2 for i1,i2 in zip(list_ex, p)),
...                       itertools.permutations(list_ex, len(list_ex)))
>>> next(i)
(2, 3, 1)

Но это не будет случайным выбором. Вам нужно будет сгенерировать их все и выбрать один, чтобы он был фактическим случайным результатом:

>>> list_ex = [1,2,3]
>>> i = itertools.ifilter(lambda p: not any(i1==i2 for i1,i2 in zip(list_ex, p)),
...                       itertools.permutations(list_ex, len(list_ex)))
>>> import random
>>> random.choice(list(i))
(2, 3, 1)
person jterrace    schedule 19.03.2013
comment
Спасибо! Я думаю, это хорошая идея. Для очень больших списков память может привести к тому, что этот метод может иметь некоторые проблемы... представьте, что у вас есть список из 10 элементов MM и все возможные комбинации... - person Dnaiel; 20.03.2013
comment
Правильно, совершенно не подходит для очень больших списков. - person jterrace; 20.03.2013
comment
Существует n! перестановок из n элементов, очень большое количество. Я получаю ошибку памяти с длиной списка 12. - person Colonel Panic; 20.03.2013
comment
Это нормально только для тривиально маленьких списков. - person Matthew Read; 08.12.2020

Вот еще один взгляд на это. Вы можете выбрать то или иное решение в зависимости от ваших потребностей. Это не один вкладыш, а перетасовка индексов элементов вместо самих элементов. Таким образом, в исходном списке могут быть повторяющиеся значения или значения типов, которые нельзя сравнивать или сравнение которых может быть дорогостоящим.

#! /usr/bin/env python
import random

def shuffled_values(data):
    list_length = len(data)
    candidate = range(list_length)
    while True:
        random.shuffle(candidate)
        if not any(i==j for i,j in zip(candidate, range(list_length))):
            yield [data[i] for i in candidate]

list_ex = [1, 2, 3]
list_gen = shuffled_values(list_ex)
for i in range(0, 10):
    print list_gen.next()

Это дает:

[2, 3, 1]
[3, 1, 2]
[3, 1, 2]
[2, 3, 1]
[3, 1, 2]
[3, 1, 2]
[2, 3, 1]
[2, 3, 1]
[3, 1, 2]
[2, 3, 1]

Если list_ex равно [2, 2, 2], этот метод будет возвращать [2, 2, 2] снова и снова. Другие решения дадут вам пустые списки. Я не уверен, что вы хотите в этом случае.

person crayzeewulf    schedule 19.03.2013

Используйте Knuth-Durstenfeld, чтобы перетасовать список. Если во время процесса тасования обнаруживается, что он находится в исходном положении, новый процесс тасования начинается с самого начала, пока он не вернется к квалифицированному расположению. Временная сложность этого алгоритма является наименьшим постоянным членом:

def _random_derangement(x: list, randint: Callable[[int, int], int]) -> None:
    '''
        Random derangement list x in place, and return None.
        An element can never be in the same original position after the shuffle. provides uniform distribution over permutations.
        The formal parameter randint requires a callable object such as rand_int(b, a) that generates a random integer within the specified closed interval.
    '''

    from collections import namedtuple

    sequence_type = namedtuple('sequence_type', ('sequence_number', 'elem'))

    x_length = len(x)
    if x_length > 1:
        for i in range(x_length):
            x[i] = sequence_type(sequence_number = i, elem = x[i])
    
        end_label = x_length - 1
        while True:
            for i in range(end_label, 0, -1):
                random_location = randint(i, 0)
                if x[random_location].sequence_number != i:
                    x[i], x[random_location] = x[random_location], x[i]
                else:
                    break
            else:
                if x[0].sequence_number != 0: break
    
        for i in range(x_length):
            x[i] = x[i].elem

complete_shuffle

person sosei    schedule 06.11.2020

Вот еще один алгоритм. Берите карты наугад. Если ваша i-я карта — i-я, положите ее обратно и повторите попытку. Единственная проблема, что если, когда вы доберетесь до последней карты, это та, которая вам не нужна. Поменяйте его местами с одним из других.

Я думаю, что это справедливо (равномерно случайно).

import random

def permutation_without_fixed_points(n):
    if n == 1:
        raise ArgumentError, "n must be greater than 1"

    result = []
    remaining = range(n)

    i = 0
    while remaining:
        if remaining == [n-1]:
            break

        x = i
        while x == i:
            j = random.randrange(len(remaining))
            x = remaining[j]

        remaining.pop(j)
        result.append(x)

        i += 1

    if remaining == [n-1]:
        j = random.randrange(n-1)
        result.append(result[j])
        result[j] = n

    return result
person Colonel Panic    schedule 20.03.2013