Допустим, у меня есть список из 16 номеров. С этими 16 числами я могу создать разные матрицы 4х4. Я хотел бы найти все матрицы 4x4, где каждый элемент в списке используется один раз, и где сумма каждой строки и каждого столбца равна 264.

Сначала я нахожу все комбинации элементов в списке, которые составляют до 264

numbers = [11, 16, 18, 19, 61, 66, 68, 69, 81, 86, 88, 89, 91, 96, 98, 99]

candidates = []
result = [x for x in itertools.combinations(numbers, 4) if sum(x) == 264]

result становится списком, где каждый элемент является списком с 4 элементами, где сумма из 4 элементов = 264. Я считаю их своими строками. Тогда я хотел бы взять все перестановки моих строк, так как сложение коммутативно.

for i in range(0, len(result)):
    candidates.append(list(itertools.permutations(result[i])))

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

test = []
for i in range(0, len(candidates)):
    test = test + candidates[i]
result2 = [x for x in itertools.combinations(test, 4) if list(map(add, x[0], list(map(add, x[1], list( map(add, x[2], x[3])))))) == [264, 264, 264, 264]]

Есть ли более быстрый / лучший способ? Последняя часть, нахождение всех комбинаций из 4 рядов, отнимает много времени и сил компьютера.

7
Olba12 21 Дек 2019 в 03:42

2 ответа

Лучший ответ

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

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

Вместо того, чтобы писать собственный алгоритм поиска с возвратом, вы можете использовать существующий решатель ограничений, такой как библиотека python-ограничений . Вот пример:

numbers = [11, 16, 18, 19, 61, 66, 68, 69, 81, 86, 88, 89, 91, 96, 98, 99]
target = 264

from constraint import *

problem = Problem()
problem.addVariables(range(16), numbers)

for i in range(4):
    # column i
    v = [ i + 4*j for j in range(4) ]
    problem.addConstraint(ExactSumConstraint(target), v)
    # row i
    v = [ 4*i + j for j in range(4) ]
    problem.addConstraint(ExactSumConstraint(target), v)

problem.addConstraint(AllDifferentConstraint())

Примере:

>>> problem.getSolution()
{0: 99, 1: 88, 2: 66, 3: 11, 4: 16, 5: 61, 6: 89, 7: 98, 8: 81, 9: 96, 10: 18, 11: 69, 12: 68, 13: 19, 14: 91, 15: 86}
>>> import itertools
>>> for s in itertools.islice(problem.getSolutionIter(), 10):
...     print(s)
... 
{0: 99, 1: 68, 2: 81, 3: 16, 4: 66, 5: 91, 6: 18, 7: 89, 8: 88, 9: 19, 10: 96, 11: 61, 12: 11, 13: 86, 14: 69, 15: 98}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 66, 5: 91, 6: 18, 7: 89, 8: 11, 9: 86, 10: 69, 11: 98, 12: 88, 13: 19, 14: 96, 15: 61}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 18, 5: 89, 6: 66, 7: 91, 8: 86, 9: 11, 10: 98, 11: 69, 12: 61, 13: 96, 14: 19, 15: 88}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 18, 5: 89, 6: 66, 7: 91, 8: 61, 9: 96, 10: 19, 11: 88, 12: 86, 13: 11, 14: 98, 15: 69}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 11, 5: 86, 6: 69, 7: 98, 8: 66, 9: 91, 10: 18, 11: 89, 12: 88, 13: 19, 14: 96, 15: 61}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 11, 5: 86, 6: 69, 7: 98, 8: 88, 9: 19, 10: 96, 11: 61, 12: 66, 13: 91, 14: 18, 15: 89}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 61, 5: 96, 6: 19, 7: 88, 8: 18, 9: 89, 10: 66, 11: 91, 12: 86, 13: 11, 14: 98, 15: 69}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 61, 5: 96, 6: 19, 7: 88, 8: 86, 9: 11, 10: 98, 11: 69, 12: 18, 13: 89, 14: 66, 15: 91}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 88, 5: 19, 6: 96, 7: 61, 8: 11, 9: 86, 10: 69, 11: 98, 12: 66, 13: 91, 14: 18, 15: 89}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 88, 5: 19, 6: 96, 7: 61, 8: 66, 9: 91, 10: 18, 11: 89, 12: 11, 13: 86, 14: 69, 15: 98}

Это первые десять решений. Метод problem.getSolutions() возвращает список, содержащий все из них, но для его запуска требуется немало времени (около 2 минут на моем компьютере), поскольку их нужно найти 6 912.

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

# permute rows/cols so that lowest element is in top-left corner
m = min(numbers)
problem.addConstraint(InSetConstraint([m]), [0])

from operator import lt as less_than

for i in range(3):
    # permute columns so first row is in order
    problem.addConstraint(less_than, [i, i+1])
    # permute rows so first column is in order
    problem.addConstraint(less_than, [4*i, 4*i + 4])

# break transpose symmetry by requiring grid[0,1] < grid[1,0]
problem.addConstraint(less_than, [1, 4])

Это нарушает все симметрии, поэтому теперь возвращает примерно 6 912 / (4! * 4! * 2) = 6 решений примерно за 0,2 секунды.

7
kaya3 21 Дек 2019 в 02:36

Вот подход, использующий z3py, версию Python Z3 SAT / SMT solver. Обратите внимание, что каждая перестановка строк и / или столбцов, а также зеркалирование дает дополнительное решение. Вместе каждое примитивное решение приводит к 24 * 24 * 2 эквивалентным решениям.

Добавление ограничений для навязывания порядка должно позволить найти все примитивные решения. Если ошибок нет, следующая программа находит все 6 из них. Итак, всего вместе должно быть 6 * 24 * 24 * 2 = 6912 решений.

from z3 import Solver, BitVec, Or, Distinct, sat

numbers = [11, 16, 18, 19, 61, 66, 68, 69, 81, 86, 88, 89, 91, 96, 98, 99]

# X is a table to store the 16 variables for the solution
X = [BitVec(f'x{i}{j}', 16) for i in range(4) for j in range(4)]
s = Solver()
for x in X:
    s.add(Or([x == n for n in numbers]))  # all X[i] should be one of the given numbers

# constraints to avoid reordered solutions
s.add(X[0] == 11)
s.add(X[0] < X[1])
s.add(X[1] < X[2])
s.add(X[2] < X[3])
s.add(X[1] < X[4])
s.add(X[4] < X[8])
s.add(X[8] < X[12])

# all X[i] have to be distinct
s.add(Distinct(X))
for i in range(4):
    # all rows and all columns need to sum to 264
    s.add(sum([X[4*i+j] for j in range(4)]) == 264)
    s.add(sum([X[4*j+i] for j in range(4)]) == 264)

# start solving
res = s.check()

while res == sat:
    m = s.model()
    # show the solution
    for i in range(4):
        print([m[X[i*4+j]] for j in range(4)])
    print()

    # add the just found solution as a constraint so it doesn't get outputted again
    s.add(Or([X[i] != m[X[i]].as_long() for i in range(16)]))

    # solve again to find different solutions
    res = s.check()

Выход:

[11, 68, 89, 96]
[69, 16, 91, 88]
[86, 99, 18, 61]
[98, 81, 66, 19]

[11, 68, 86, 99]
[69, 16, 98, 81]
[88, 91, 19, 66]
[96, 89, 61, 18]

[11, 66, 89, 98]
[69, 18, 91, 86]
[88, 99, 16, 61]
[96, 81, 68, 19]

[11, 66, 88, 99]
[68, 19, 91, 86]
[89, 98, 16, 61]
[96, 81, 69, 18]

[11, 66, 88, 99]
[69, 18, 96, 81]
[86, 91, 19, 68]
[98, 89, 61, 16]

[11, 66, 89, 98]
[68, 19, 96, 81]
[86, 91, 18, 69]
[99, 88, 61, 16]
2
JohanC 21 Дек 2019 в 02:18