Как сгенерировать следующий кортеж в декартовом произведении?

#algorithm #combinatorics #cartesian-product

#алгоритм #комбинаторика #Декартово произведение

Вопрос:

У меня есть n-кортеж, x = (x[0], .., x[n-1]) где каждый член кортежа происходит из отдельного упорядоченного множества S[i] , такого, что x[i] in S[i] . Все наборы S[i] имеют разную мощность N[i] . Я хочу знать, как сгенерировать следующий кортеж в лексическом порядке с учетом наборов S[i] .

Пример:

 S[0] = {0,1,2}
S[1] = {1,2,3,4}
S[2] = {8,9,7}

x = {2,2,7}
xnext = {2,3,8}
xnextnext = {2,3,9}
 

и т. д

Это не обязательно должно быть очень эффективным, просто замкнутая форма с точки зрения текущих элементов кортежа и наборов. Если это проще, было бы эквивалентно думать о n-кортеже как об индексах в наборах.

Ответ №1:

Для данного кортежа вы можете сопоставить элементы кортежа с их соответствующими индексами в каждом наборе S , а затем попытаться «увеличить» число со смешанным основанием, представленное этим кортежем индексов. Затем возьмите увеличенное «число» и сопоставьте его обратно с кортежем элементов. Вот доказательство концепции в Python:

 def next_tuple(x, S):
    assert len(x) == len(S)
    assert all(element in set_ for element, set_ in zip(x, S))

    # compute the bases for our mixed-radix system
    lengths = [len(set_) for set_ in S]
    # convert tuple `x` to a mixed-radix number
    indices = [set_.index(element) for element, set_ in zip(x, S)]

    # try to increment, starting from the right
    for k in reversed(range(len(indices))):
        indices[k]  = 1

        if indices[k] == lengths[k]: 
            # case 1: we have a carry, rollover this index and continue
            indices[k] = 0
        else:
            # case 2: no carry, map indices back to actual elements and return
            return [set_[index] for index, set_ in zip(indices, S)]

    # we have gone through each position and still have a carry.
    # this means the "increment" operation overflowed, and there
    # is no "next" tuple.
    return None


S = [[0, 1, 2], [1, 2, 3, 4], [8, 9, 7]]

print("next tuple after {} is {}".format([2, 2, 7], next_tuple([2, 2, 7], S)))
print("all tuples in order:")

x = [0, 1, 8]

while x is not None:
    print(x)
    x = next_tuple(x, S)
 

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

Комментарии:

1. Я думаю, что я сделал что-то подобное, хотя я никогда раньше не использовал zip(). Проверьте мой ответ и посмотрите, имеет ли он смысл

Ответ №2:

Я заставил его работать, используя этот псевдокод:

 # x = [2,2,7]
sets = [[0,1,2], [1,2,3,4], [8,9,7]]
def next_tuple(x):
    for i, el in enumerate(x):
        if(i < len(sets[i]) - 1):
            x[i] = sets[i, sets[i].index(x[i]) 1] // requires lists to have unique elements
            return x
        else :
            x[i] = sets[i,0]
 

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

Комментарии:

1. Это будет работать с двумя изменениями: 1) сначала вам нужно увеличить крайнюю правую позицию (поэтому вместо for i, el in enumerate(x) этого вам понадобится что-то вроде for i from len(x)-1 down to 0 в псевдокоде); 2) if оператор должен проверить, можно ли увеличить индекс элемента, а не i (поэтому вместо if i < len(sets[i]) - 1 этого вы бы сделали if sets[i].index(x[i]) < len(sets[i]) - 1 . С этими изменениями это было бы правильно, и с некоторыми незначительными изменениями синтаксиса это был бы даже действительный код Python.

2. Спасибо за это изменение, я знал, что перевел часть этого неправильно. Также верно, что мне пришлось выполнить индексацию ni, чтобы сохранить лексикографический порядок. Я опубликую фрагмент кода на реальном языке (LabVIEW), если вам интересно.