Подтвердить что ты не робот

Создание кортежей всех возможных комбинаций элементов из двух списков без дублирования элементов внутри кортежей

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

get_combinations_without_duplicates(3) = [(0, 1, 2), (1, 2, 0), (2, 0, 1)]

Для диапазона (3) это просто вращение списка, но для более высоких диапазонов возможны более возможные комбинации. Я хотел бы иметь возможность случайным образом генерировать список троек, удовлетворяющих этим ограничениям.

Предположим, что мы начнем с указания первого элемента каждой тройки для случая, когда n = 4:

[(0,), (1,), (2,), (3,)]

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

[(0, 1, 2), (1, 2, 3), (2, 3, 0), (3, 0, 1),]

или

[(0, 2, 3), (1, 3, 0), (2, 0, 1), (3, 1, 2)]

Вот реализация этой функции:

def get_combinations_without_duplicates(n):
    output = []
    second = range(n)
     third = range(n)
for i in range(n):
    triple = [i]
    #Get the second value of the triple, but make sure that it isn't a 
    #duplicate of the first value
    #in the triple or any value that has appeared in the second position of any triple
    choices_for_second = [number for number in second if number not in triple]
    #Randomly select a number from the allowed possibilities
    n_second = random.choice(choices_for_second) 
    #Append it to the triple
    triple.append(n_second)
    #Remove that value from second so that it won't be chosen for other triples
    second = [number for number in second if number != n_second]
    #Do the same for the third value
    choices_for_third = [number for number in third if number not in triple]
    n_third = random.choice(choices_for_third)
    triple.append(n_third)
    third = [number for number in third if number != n_third]
    output.append(tuple(triple))
return output

Как указано ниже, этот процесс иногда случайно выбирает комбинации, которые не работают. Это можно обработать, если вы выполните что-то вроде:

def keep_trying(n):
    try:
        return get_combinations_without_duplicates(n)
    except IndexError:
        return keep_trying(n)

Однако мне интересно, есть ли лучший способ сделать это в целом.

4b9b3361

Ответ 1

Повторите попытку.

Несколько наблюдений.

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

Основываясь на этих спецификациях, мы можем придумать процедурный метод;

  • Сгенерируйте 2 списка последовательных целых чисел, один из которых выбирается, а другой - для семени.
  • Для каждого номера в списке семян [0, 1, 2, 3], произвольно добавьте и удалите номер, который еще не находится в элементе. [01, 13, 20, 32]
  • Создайте еще один список последовательных целых чисел и повторите. [012, 130, 203, 321]

Но это не работает. Для некоторых итераций он вернется в угол и не сможет генерировать число. Например, [01, 13, 20, 32].. appending [3, 0, 1... crap, I'm stuck.

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

Итак, процедурно говоря:

Решение 1: Случайное поколение

  • Заполните список своим диапазоном. [0, 1, 2, 3]
  • Создайте еще один список. [0, 1, 2, 3]
  • Перемешать список. [1, 0, 2, 3]
  • Перемешать, пока не найдете тот, который подходит... [1, 2, 3, 0]
  • Повторите с третьим элементом.

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

Поэтому самый быстрый гарантированный метод будет использовать использование процедуры проверки, а не процедуру генерации. Прежде всего, сгенерируйте все возможные перестановки.

from itertools import permutations

n = 4
candidates = [i for i in permutations(xrange(n),3)]

Тогда.

Решение 2: Случайная проверка

  • Выберите триплет, начинающийся с 0.
  • Поп, случайно, триплет, который не начинается с 0.
  • Проверьте, является ли случайно выбранный триплет промежуточным решением.
  • Если нет, добавьте еще один триплет.
  • Если да, добавьте триплет и REPOPULATE TRIPLET QUEUE.
  • Повторите n раз. # или пока вы не исчерпаете очередь, после чего повторите n раз, естественно, станет TRUE

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

Решение 3: Итеративная проверка

Для результатов с равной вероятностью избавитесь от рандомизации и сгенерируйте каждую возможную комбинацию из трех кортежей, n-списки long - и проверьте каждый из этих кандидатов.

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

from itertools import combinations

results = [verify(i) for i in combinations(candidates, n)]
# this is 10626 calls to verify for n=4, 5 million for n=5 
# this is not an acceptable solution.  

Ни одно решение 1 или 3 не очень быстро, O (n ** 2), но, учитывая ваши критерии, возможно, это будет так же быстро, как это получится, если вы хотите действительно случайное решение. Решение 2 будет гарантировано быть самым быстрым из этих трех, часто раз превышая 1 или 3, решение 3 имеет наиболее стабильные результаты. Какой из этих подходов вы выберете, будет зависеть от того, что вы хотите делать с выходом.

После:

В конечном счете скорость кода будет зависеть от того, насколько случайным вы хотите, чтобы ваш код был. Алгоритм для выдачи ОЧЕНЬ первого (и только самого первого) экземпляра серии кортежей, который удовлетворяет вашим требованиям, может работать в высшей степени быстро, поскольку он просто атакует перестановки по порядку один раз и будет работать в O (n) времени, Тем не менее, он ничего не сделает случайно...

Кроме того, здесь можно найти быстрый код для проверки (i). Это основано на наблюдении, что два кортежа могут не иметь одинакового числа в одном и том же индексе.

def verify(t):
    """ Verifies that a set of tuples satisfies the combinations without duplicates condition. """
    zipt = zip(*t)
    return all([len(i) == len(set(i)) for i in zipt])

n = 4 Полный набор решений

((0, 1, 2), (1, 0, 3), (2, 3, 0), (3, 2, 1))
((0, 1, 2), (1, 0, 3), (2, 3, 1), (3, 2, 0))
((0, 1, 2), (1, 2, 3), (2, 3, 0), (3, 0, 1))
((0, 1, 2), (1, 3, 0), (2, 0, 3), (3, 2, 1))
((0, 1, 3), (1, 0, 2), (2, 3, 0), (3, 2, 1))
((0, 1, 3), (1, 0, 2), (2, 3, 1), (3, 2, 0))
((0, 1, 3), (1, 2, 0), (2, 3, 1), (3, 0, 2))
((0, 1, 3), (1, 3, 2), (2, 0, 1), (3, 2, 0))
((0, 2, 1), (1, 0, 3), (2, 3, 0), (3, 1, 2))
((0, 2, 1), (1, 3, 0), (2, 0, 3), (3, 1, 2))
((0, 2, 1), (1, 3, 0), (2, 1, 3), (3, 0, 2))
((0, 2, 1), (1, 3, 2), (2, 0, 3), (3, 1, 0))
((0, 2, 3), (1, 0, 2), (2, 3, 1), (3, 1, 0))
((0, 2, 3), (1, 3, 0), (2, 0, 1), (3, 1, 2))
((0, 2, 3), (1, 3, 2), (2, 0, 1), (3, 1, 0))
((0, 2, 3), (1, 3, 2), (2, 1, 0), (3, 0, 1))
((0, 3, 1), (1, 0, 2), (2, 1, 3), (3, 2, 0))
((0, 3, 1), (1, 2, 0), (2, 0, 3), (3, 1, 2))
((0, 3, 1), (1, 2, 0), (2, 1, 3), (3, 0, 2))
((0, 3, 1), (1, 2, 3), (2, 1, 0), (3, 0, 2))
((0, 3, 2), (1, 0, 3), (2, 1, 0), (3, 2, 1))
((0, 3, 2), (1, 2, 0), (2, 1, 3), (3, 0, 1))
((0, 3, 2), (1, 2, 3), (2, 0, 1), (3, 1, 0))
((0, 3, 2), (1, 2, 3), (2, 1, 0), (3, 0, 1))

n = 5 имеет 552 уникальных решения. Вот первые 20.

((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 0), (4, 2, 1))
((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 1), (4, 2, 0))
((0, 1, 2), (1, 0, 3), (2, 4, 0), (3, 2, 4), (4, 3, 1))
((0, 1, 2), (1, 0, 3), (2, 4, 1), (3, 2, 4), (4, 3, 0))
((0, 1, 2), (1, 0, 4), (2, 3, 0), (3, 4, 1), (4, 2, 3))
((0, 1, 2), (1, 0, 4), (2, 3, 1), (3, 4, 0), (4, 2, 3))
((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 0), (4, 3, 1))
((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 0), (2, 3, 4), (3, 4, 1), (4, 0, 3))
((0, 1, 2), (1, 2, 0), (2, 4, 3), (3, 0, 4), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 0), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 0), (4, 0, 1))
((0, 1, 2), (1, 2, 3), (2, 4, 0), (3, 0, 4), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 4, 1), (3, 0, 4), (4, 3, 0))
((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 0), (4, 3, 1))
((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 4), (2, 3, 0), (3, 4, 1), (4, 0, 3))
((0, 1, 2), (1, 2, 4), (2, 3, 1), (3, 4, 0), (4, 0, 3))
((0, 1, 2), (1, 2, 4), (2, 4, 3), (3, 0, 1), (4, 3, 0))

Итак, вы можете создавать такие решения, но это требует времени. Если вы собираетесь использовать это, я буду кэшировать созданные решения, как есть, а затем случайным образом извлекать из них, когда вам это нужно, для любого числа n. Кстати, n = 5 потребовалось немного меньше минуты, чтобы закончить грубую силу. Поскольку решение O (n ** 2), я ожидаю, что n = 6 займет более часа, n = 7 в течение дня. Единственный способ вернуть истинное рандомизированное решение - это сделать так.

Отредактировано: Случайное решение без равного распределения:

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

def seeder(n):
    """ Randomly generates the first element in a solution. """
    seed = [0]
    a = range(1, n)
    for i in range(1, 3):
        seed.append(a.pop(random.randint(0,len(a)-1)))
    return [seed]

def extend_seed(seed, n):
    """ Semi-Randomly generates the next element in a solution. """
    next_seed = [seed[-1][0] + 1]
    candidates = range(0, n)
    for i in range(1, 3):
        c = candidates[:]
        for s in next_seed:
            c.remove(s)
        for s in seed:
            try:
                c.remove(s[i])
            except ValueError:
                pass
        next_seed.append(c.pop(random.randint(0,len(c)-1)))
    seed.append(next_seed)
    return seed

def combo(n):
    """ Extends seed until exhausted. 
    Some random generations return results shorter than n. """
    seed = seeder(n)
    while True:
        try:
            extend_seed(seed, n)
        except (ValueError, IndexError):
            return seed

def combos(n):
    """ Ensures that the final return is of length n. """
    while True:
        result = combo(n)
        if len(result) == n:
            return result

Ответ 2

Вы, по сути, хотите Латинский квадрат, подсеть сетки чисел, где каждая строка и каждый столбец содержат каждое число ровно один раз, за ​​исключением того, что вы только заботитесь о первых трех числах в каждой строке (латинский прямоугольник).

Update:

Я удалил свой неэффективный пример кода, потому что генерация случайных латинских квадратов с равной вероятностью нетривиальна, как обсуждалось в вопросе о math.stackexchange.com,

проект SAGE реализует алгоритм, упомянутый в этом вопросе, поэтому вы можете посмотреть на код для вдохновения.

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

Ответ 3

Фактически itertools решил это для вас уже.

import itertools

allp = [x for x in itertools.permutations(range(3))]
print allp

mylist = ['A','B','C']
allp2 = [x for x in itertools.permutations(mylist)]
print allp2

Выход

[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]

Ответ 4

Просто другая точка зрения на вашу проблему. Посмотрите, работает ли это для вас.

>>> from itertools import chain,combinations
>>> def get_combinations_without_duplicates(iterable):
        return (tuple(chain(*(set(iterable) - set(e) , e))) for e in combinations(iterable,2))

>>> list(get_combinations_without_duplicates(range(3)))
[(2, 0, 1), (1, 0, 2), (0, 1, 2)]

Ответ 5

Простое вращение списка обеспечивает правильное решение для всех n >= 3:

Рассмотрим решение вращения для n = 5:

[
    (0, 1, 2),
    (1, 2, 3),
    (2, 3, 4),
    (3, 4, 0),
    (4, 0, 1)
]

Каждый номер появляется в каждой позиции только один раз, и для каждой позиции присутствуют все числа.


В общем случае len(get_combinations_without_duplicates(n)) == n при n >= 3

Ответ 6

вот подход, который использует deque.rotate

>>> datax = []
>>> from collections import deque
>>> data_length = 10
>>> subset_length = 3
>>> for i in xrange(0, subset_length):
...     datax.append(deque(xrange(data_length)))
...
>>> for i in xrange(0, subset_length):
...     datax[i].rotate(i)
...
>>> print zip(*datax)
[(0, 9, 8), (1, 0, 9), (2, 1, 0), (3, 2, 1), (4, 3, 2), (5, 4, 3), (6, 5, 4), (7, 6, 5), (8, 7, 6), (9, 8, 7)]