Рубрики
Без рубрики

Программирование ограничений с помощью python-constraint

Автор оригинала: Olivera Popović.

Вступление

Первое, что мы должны понять, имея дело с программированием ограничений, – это то, что способ мышления очень отличается от нашего обычного способа мышления, когда мы садимся писать код.

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

Что такое парадигма программирования?

Парадигма означает “пример” или “образец” чего-либо. Парадигма программирования часто описывается как “способ мышления” или “способ программирования”. Наиболее распространенные примеры включают Процедурное программирование (например, C), Объектно-ориентированное программирование (например, Java) и Функциональное программирование (например, Haskell).

Большинство парадигм программирования можно отнести к группе императивных или декларативных парадигм.

В чем разница между императивным и декларативным программированием?

  • Императивное программирование, проще говоря, основано на описании разработчиком решения/алгоритма достижения цели (какого-то результата). Это происходит путем изменения состояния программы с помощью операторов присваивания при пошаговом выполнении инструкций. Поэтому имеет огромное значение, в каком порядке написаны инструкции.
  • Декларативное программирование делает обратное – мы не пишем шаги по достижению цели, мы описываем цель , а компьютер дает нам решение. Распространенный пример, с которым вы должны быть знакомы, – это SQL. Говорите ли вы компьютеру как дать вам нужные результаты? Нет, вы описываете то, что вам нужно – вам нужны значения из какого-то столбца, из какой-то таблицы, где выполняются какие-то условия.

Установка модуля python-constraint

В этой статье мы будем работать с модулем под названием python-constraint (Примечание: есть модуль под названием “constraint” для Python, это не то, что мы хотим), который направлен на то, чтобы принести идею программирования ограничений в Python.

Чтобы установить этот модуль, откройте терминал и запустите:

$ pip install python-constraint

Основы использования python-constraint

Это обобщенный скелет программ, написанных с использованием этого модуля (Примечание: мы используем import constraint , а не import python-constraint )

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

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

Пример А
Find all (x,y) where x ∈ {1,2,3} and 0 <= y < 10, and x + y >= 5

Если мы посмотрим на это предложение, то увидим несколько условий (назовем их ограничениями), которым должны соответствовать x и y .

Например, x “ограничен” значениями 1,2,3 , y должно быть меньше, чем 10 и их сумма должна быть больше или равна 5 . Это делается в нескольких строках кода и за несколько минут с помощью программирования ограничений.

Глядя на проблему выше, вы, вероятно, подумали: “Ну и что? Я могу сделать это с 2 петлями for и половиной чашки кофе в Python менее чем за 10 минут”.

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

import constraint

problem = constraint.Problem()

problem.addVariable('x', [1,2,3])
problem.addVariable('y', range(10))

def our_constraint(x, y):
    if x + y >= 5:
        return True

problem.addConstraint(our_constraint, ['x','y'])

solutions = problem.getSolutions()

# Easier way to print and see all solutions
# for solution in solutions:
#    print(solution)

# Prettier way to print and see all solutions
length = len(solutions)
print("(x,y) ∈ {", end="")
for index, solution in enumerate(solutions):
    if index == length - 1:
        print("({},{})".format(solution['x'], solution['y']), end="")
    else:
        print("({},{}),".format(solution['x'], solution['y']), end="")
print("}")

Выход:

(x,y) ∈ {(3,9),(3,8),(3,7),(3,6),(3,5),(3,4),(3,3),(3,2),(2,9),(2,8),(2,7),(2,6),(2,5),(2,4),(2,3),(1,9),(1,8),(1,7),(1,6),(1,5),(1,4)}

Давайте пройдемся по этой программе шаг за шагом. У нас было две переменные: x и y . Мы добавили их к нашей проблеме с соответствующими допустимыми диапазонами.

Эти две строки означают следующее:

I'm adding a variable x that can only have values [1,2,3], and a variable y that can only have values [0,1,2,..,9]

Далее мы определяем наше пользовательское ограничение (то есть x + y ). Методы ограничения должны возвращать True , если комбинация значений переменных приемлема, и None , если это не так.

В нашем методе our_constraint() мы говорим: “Единственная приемлемая ситуация-это когда x + y , иначе не включайте эти значения (x , y) в конечные решения.”

Определив наше ограничение, мы должны добавить его к нашей проблеме. Структура метода .addConstraint() такова:

addConstraint(which_constraint, list_of_variable_order)

Примечание : в нашем случае не имеет значения, пишем ли мы [x,y] или [y,x] в качестве нашего второго параметра, но порядок имеет значение в большинстве случаев.

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

Примечание : Если бы, например, мы хотели получить только комбинации , где x , мы бы добавили встроенное ограничение перед выборкой решений:

problem.addConstraint(constraint.AllDifferentConstraint())

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

Примеры разминки

Пример B

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

TWO + TWO = FOUR

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

Он также должен дать вам все знания, необходимые для самостоятельного решения примера D.

Имейте в виду, что ” Т ” и ” Ф ” не могут быть равны нулю, так как они являются ведущими символами, то есть первой цифрой в числе.

import constraint

problem = constraint.Problem()

# We're using .addVariables() this time since we're adding
# multiple variables that have the same interval.
# Since Strings are arrays of characters we can write
# "TF" instead of ['T','F'].
problem.addVariables("TF", range(1, 10))
problem.addVariables("WOUR", range(10))

# Telling Python that we need TWO + TWO = FOUR
def sum_constraint(t, w, o, f, u, r):
    if 2*(t*100 + w*10 + o) == f*1000 + o*100 + u*10 + r:
        return True

# Adding our custom constraint. The
# order of variables is important!
problem.addConstraint(sum_constraint, "TWOFUR")

# All the characters must represent different digits,
# there's a built-in constraint for that
problem.addConstraint(constraint.AllDifferentConstraint())

solutions = problem.getSolutions()
print("Number of solutions found: {}\n".format(len(solutions)))

# .getSolutions() returns a dictionary
for s in solutions:
    print("T = {}, W = {}, O = {}, F = {}, U = {}, R = {}"
        .format(s['T'], s['W'], s['O'], s['F'], s['U'], s['R']))

Запустив этот фрагмент кода, мы встречаемся с возможными решениями:

Number of solutions found: 7

T = 7, W = 6, O = 5, F = 1, U = 3, R = 0
T = 7, W = 3, O = 4, F = 1, U = 6, R = 8
T = 8, W = 6, O = 7, F = 1, U = 3, R = 4
T = 8, W = 4, O = 6, F = 1, U = 9, R = 2
T = 8, W = 3, O = 6, F = 1, U = 7, R = 2
T = 9, W = 2, O = 8, F = 1, U = 5, R = 6
T = 9, W = 3, O = 8, F = 1, U = 7, R = 6
Пример C
You recently got a job as a cashier. You're trying to convince your friend that it's hard work, there are just SO many ways to give someone their change back! Your "friend" shakes his head, obviously not believing you. He says "It can't be that bad. How many ways can there POSSIBLY be to give someone their change back, for like 60 cents?".

Your response is, of course, to sit and quickly write a program that would prove your point. You have a decent amount of pennies (1 cent), nickels (5 cents), dimes (10 cents) and quarters (25 cents), and a lot of kinda suspicious coins worth 3 cents each. Calculate in how many ways you can return change for 60 cents.

Примечание : Порядок, в котором печатается наш результат, не обязательно совпадает с порядком, в котором мы добавили переменные. То есть, если результат ( a,b,c,d,e ), мы не знаем, есть ли у нас a монет по 1 центу, b монет по 3 цента и т. Д.

Поэтому мы должны явно вывести переменную и ее значение. Одним из следствий этого является то, что мы не можем использовать встроенный .ExactSumConstraint() в своей двухпараметрической форме, ExactSumConstraint(50,[1,3,5,10,20]) .

Вторым параметром здесь является “вес” каждой переменной (сколько раз она должна быть умножена), и мы не можем гарантировать, какая из наших переменных будет иметь какой вес.

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

import constraint

problem = constraint.Problem()

# The maximum amount of each coin type can't be more than 60
# (coin_value*num_of_coints) <= 60

problem.addVariable("1 cent", range(61))
problem.addVariable("3 cent", range(21))
problem.addVariable("5 cent", range(13))
problem.addVariable("10 cent", range(7))
problem.addVariable("20 cent", range(4))

problem.addConstraint(
    constraint.ExactSumConstraint(60,[1,3,5,10,20]),
    ["1 cent", "3 cent", "5 cent","10 cent", "20 cent"]
)
# Where we explicitly give the order in which the weights should be allocated

# We could've used a custom constraint instead, BUT in this case the program will
# run slightly slower - this is because built-in functions are optimized and
# they find the solution more quickly
# def custom_constraint(a, b, c, d, e):
#     if a + 3*b + 5*c + 10*d + 20*e == 60:
#         return True
#     problem.addConstraint(o, ["1 cent", "3 cent", "5 cent","10 cent", "20 cent"])


# A function that prints out the amount of each coin
# in every acceptable combination
def print_solutions(solutions):
    for s in sols:
        print("---")
        print("""
        1 cent: {0:d}
        3 cent: {1:d}
        5 cent: {2:d}
        10 cent: {3:d}
        20 cent: {4:d}""".format(s["1 cent"], s["3 cent"], s["5 cent"], s["10 cent"], s["20 cent"]))
        # If we wanted to we could check whether the sum was really 60
        # print("Total:", s["1 cent"] + s["3 cent"]*3 + s["5 cent"]*5 + s["10 cent"]*10 + s["20 cent"]*20)
        # print("---")

solutions = problem.getSolutions()
#print_solutions(solutions)
print("Total number of ways: {}".format(len(solutions)))

Запуск этого фрагмента кода приведет к:

Total number of ways: 535
Пример D
CRASH + ERROR + REBOOT = HACKER

Примеры B и D почти идентичны при использовании ограничений, только несколько переменных вверх и вниз и более подробные ограничения. Это одна хорошая вещь в программировании ограничений – хорошая масштабируемость, по крайней мере, когда речь идет о времени, потраченном на кодирование. Есть только одно решение этой загадки, и оно есть.

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

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

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

Более Сложные Примеры

Пример E
You wish to pack chocolates for your mother. Luckily you work in a chocolate factory that has a lot of leftover chocolate. You have a few chocolate types at your disposal.

Your goal is to bring her the sweetest chocolate possible, that you can pack in your bag and sneak through security, and that wouldn't pass a certain net value for which you'd go to prison if you got caught.

Security most likely won't get suspicious if you bring less than 3kg. You can fit 1 dm^3 of chocolate in your bag. You won't go to jail if you steal less than $300 worth of chocolate.
20 8.0 100 Шоколад А 8 × 2.5 × 0.5
16 6.8 45 Шоколад Б 7 × 2 × 0.5
9 4.0 10 Шоколадная стружка 3 × 2 × 0.5
7 3.0 25 Шоколад D 3 × 3 × 0.5

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

Сначала мы выясним, сколько каждого шоколада мы можем получить, если возьмем ТОЛЬКО этот тип, так что у нас будет верхняя граница наших интервалов. Например, для шоколада А, исходя из веса, мы можем принести не более 30 батончиков, исходя из стоимости, мы можем принести не более 37, а исходя из объема, мы можем принести 100.

Наименьшее из этих чисел-30, и это максимальное количество шоколада А, которое мы можем принести. Те же шаги дают нам максимальное количество остальных, B -> 44, C -> 75, D -> 100 .

import constraint

problem = constraint.Problem()

problem.addVariable('A', range(31))
problem.addVariable('B', range(45))
problem.addVariable('C', range(76))
problem.addVariable('D', range(101))

# We have 3kg = 3,000g available
def weight_constraint(a, b, c, d):
    if (a*100 + b*45 + c*10 + d*25) <= 3000:
        return True

# We have 1dm^3 = 1,000cm^3 available
def volume_constraint(a, b, c, d):
    if (a*8*2.5*0.5 + b*6*2*0.5 * c*2*2*0.5 + d*3*3*0.5) <= 1000:
        return True

# We can't exceed $300
def value_constraint(a, b, c, d):
    if (a*8 + b*6.8 + c*4 + d*3) < 300:
        return True

problem.addConstraint(weight_constraint, "ABCD")
problem.addConstraint(volume_constraint, "ABCD")
problem.addConstraint(value_constraint, "ABCD")

maximum_sweetness = 0
solution_found = {}
solutions = problem.getSolutions()

for s in solutions:
    current_sweetness = s['A']*10 + s['B']*8 + s['C']*4.5 + s['D']*3.5
    if current_sweetness > maximum_sweetness:
        maximum_sweetness = current_sweetness
        solution_found = s

print("""
The maximum sweetness we can bring is: {}
We'll bring:
{} A Chocolates,
{} B Chocolates,
{} C Chocolates,
{} D Chocolates
""".format(maximum_sweetness, solution_found['A'], solution_found['B'], solution_found['C'], solution_found['D']))

Запуск этого фрагмента кода приведет к:

The maximum sweetness we can bring is: 365.0
We'll bring:
27 A Chocolates,
2 B Chocolates,
16 C Chocolates,
2 D Chocolates

Примечание : Мы можем хранить всю релевантную информацию для каждого типа шоколада в словаре, например weight_dictionary = {'A': 100, 'B': 45, 'C': 10, 'D': 25} , и получать доступ к значениям таким образом, а не жестко кодировать их в функциях. Однако ради удобства чтения, длины кода и сосредоточения внимания на вещах, более важных для этого урока, я предпочитаю жестко кодировать сами функции ограничений.

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

Пример F

Теперь для чего – то более веселого-давайте сделаем решатель судоку (классический 9х9). Мы прочитаем головоломку из файла JSON и найдем все решения для этой конкретной головоломки (предполагая, что у головоломки есть решение).

Если вы забыли правила решения судоку:

  • Ячейки могут иметь значения от 1 до 9
  • Все ячейки в одной строке должны иметь разные значения
  • Все ячейки в одном столбце должны иметь разные значения
  • Все ячейки в квадрате 3х3 (всего девять) должны иметь разные значения

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

Мы собираемся использовать систему, в которой мы будем рассматривать числа как имена переменных (это разрешено) и делать вид, что у нас есть матрица. Индексы начинаются с (1,1) вместо обычных (0,0). Используя это, мы получим доступ к элементам доски так, как мы привыкли.

Затем нам нужно сделать простую часть, сказав Python, что все эти ячейки могут иметь значения от 1 до 9.

Затем заметим, что ячейки в одной строке имеют один и тот же первый индекс,например (1, x) для первой строки. Мы можем легко перебрать все строки и сказать, что все ячейки должны содержать разные значения. То же самое касается и колонок. Остальное легче понять, глядя на код.

Давайте взглянем на пример JSON файла:

[[0, 9, 0, 7, 0, 0, 8, 6, 0],
 [0, 3, 1, 0, 0, 5, 0, 2, 0],
 [8, 0, 6, 0, 0, 0, 0, 0, 0],
 [0, 0, 7, 0, 5, 0, 0, 0, 6],
 [0, 0, 0, 3, 0, 7, 0, 0, 0],
 [5, 0, 0, 0, 1, 0, 7, 0, 0],
 [0, 0, 0, 0, 0, 0, 1, 0, 9],
 [0, 2, 0, 6, 0, 0, 0, 5, 0],
 [0, 5, 4, 0, 0, 8, 0, 7, 0]]
# 1 - - - - - - - - -
# 2 - - - - - - - - -
# 3 - - - - - - - - -
# 4 - - - - - - - - -
# 5 - - - - - - - - -
# 6 - - - - - - - - -
# 7 - - - - - - - - -
# 8 - - - - - - - - -
# 9 - - - - - - - - -
#   1 2 3 4 5 6 7 8 9

import constraint
import json

problem = constraint.Problem()

# We're letting VARIABLES 11 through 99 have an interval of [1..9]
for i in range(1, 10):
    problem.addVariables(range(i * 10 + 1, i * 10 + 10), range(1, 10))

# We're adding the constraint that all values in a row must be different
# 11 through 19 must be different, 21 through 29 must be all different,...
for i in range(1, 10):
    problem.addConstraint(constraint.AllDifferentConstraint(), range(i * 10 + 1, i * 10 + 10))

# Also all values in a column must be different
# 11,21,31...91 must be different, also 12,22,32...92 must be different,...
for i in range(1, 10):
    problem.addConstraint(constraint.AllDifferentConstraint(), range(10 + i, 100 + i, 10))

# The last rule in a sudoku 9x9 puzzle is that those nine 3x3 squares must have all different values,
# we start off by noting that each square "starts" at row indices 1, 4, 7
for i in [1,4,7]:
    # Then we note that it's the same for columns, the squares start at indices 1, 4, 7 as well
    # basically one square starts at 11, the other at 14, another at 41, etc
    for j in [1,4,7]:
        square = [10*i+j,10*i+j+1,10*i+j+2,10*(i+1)+j,10*(i+1)+j+1,10*(i+1)+j+2,10*(i+2)+j,10*(i+2)+j+1,10*(i+2)+j+2]
        # As an example, for i = 1 and j = 1 (bottom left square), the cells 11,12,13,
        # 21,22,23, 31,32,33 have to be all different
        problem.addConstraint(constraint.AllDifferentConstraint(), square)

file_name = input("Enter the name of the .json file containing the sudoku puzzle: ")
try:
    f = open(file_name, "r")
    board = json.load(f)
    f.close()
except IOError:
    print ("Couldn't open file.")
    sys.exit()

# We're adding a constraint for each number on the board (0 is an "empty" cell),
# Since they're already solved, we don't need to solve them
for i in range(9):
    for j in range(9):
        if board[i][j] != 0:
            def c(variable_value, value_in_table = board[i][j]):
                if variable_value == value_in_table:
                    return True

            # Basically we're making sure that our program doesn't change the values already on the board
            # By telling it that the values NEED to equal the corresponding ones at the base board
            problem.addConstraint(c, [((i+1)*10 + (j+1))])

solutions = problem.getSolutions()

for s in solutions:
    print("==================")
    for i in range(1,10):
        print("|", end='')
        for j in range(1,10):
            if j%3 == 0:
                print(str(s[i*10+j])+" | ", end='')
            else:
                print(str(s[i*10+j]), end='')
        print("")
        if i%3 == 0 and i!=9:
            print("------------------")
    print("==================")

if len(solutions) == 0:
    print("No solutions found.")

Вывод (когда мы используем наш пример JSON-файла в качестве входных данных):

==================
|295 | 743 | 861 |
|431 | 865 | 927 |
|876 | 192 | 345 |
------------------
|387 | 459 | 216 |
|612 | 387 | 594 |
|549 | 216 | 783 |
------------------
|768 | 524 | 139 |
|923 | 671 | 458 |
|154 | 938 | 672 |
==================
==================
|295 | 743 | 861 |
|431 | 865 | 927 |
|876 | 192 | 345 |
------------------
|387 | 459 | 216 |
|612 | 387 | 594 |
|549 | 216 | 738 |
------------------
|763 | 524 | 189 |
|928 | 671 | 453 |
|154 | 938 | 672 |
==================
==================
|295 | 743 | 861 |
|431 | 865 | 927 |
|876 | 192 | 543 |
------------------
|387 | 459 | 216 |
|612 | 387 | 495 |
|549 | 216 | 738 |
------------------
|763 | 524 | 189 |
|928 | 671 | 354 |
|154 | 938 | 672 |
==================

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

Если бы мы попытались запустить код без этой части, программа попыталась бы придумать ВСЕ МЫСЛИМЫЕ ГОЛОВОЛОМКИ СУДОКУ. Что с таким же успехом могло быть бесконечной петлей.

Выводы и недостатки

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

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

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

Этот друг нуждался в решении следующей проблемы:

Generate all combinations (that have a length equal to the number of keys) of values stored in a dictionary (the order of output doesn't matter). The dictionary is {String : List_of_Strings}. In such a way that every combination has exactly one value from the List_of_Strings of a key.

You don't know the number of keys in the dictionary in advance, nor do you know how long a List_of_String is, every List_of_String can be of different length. I.e. the dictionary is dynamically generated via user input.

Example input: dictionary = {"A" : [1,2], "B" -> [4], "C" -> [5,6,7], "D" -> [8,9]}
Example output: (1,4,5,8), (1,4,5,8), (1,4,6,8), (1,4,6,9), (1,4,7,8)....

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

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

import constraint

# input example
generated_dictionary = {'A' : [1,2], 'B' : [4], 'C' : [5,6,7], 'D' : [8,9]}

problem = constraint.Problem()

for key, value in generated_dictionary.items():
    problem.addVariable(key, value)

solutions = problem.getSolutions()

for solution in solutions:
    print(solution)

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

Еще одна вещь, которую следует отметить, заключается в том, что python-constraint может сделать больше, чем просто проверить, подходит ли комбинация всем ограничениям бездумно.

Реализованы возможности обратного отслеживания (и рекурсивного обратного отслеживания), а также решатель задач, основанный на теории минимальных конфликтов. Они могут быть переданы в качестве аргумента в .Problem() метод, например .Задача(Backtracking Solver) , остальное делается так же, как и в приведенных выше примерах.

Список встроенных ограничений

Все Разные Ограничения Обеспечивает, чтобы значения всех заданных переменных были разными
Все Равное Ограничение Обеспечивает равенство значений всех заданных переменных
Ограничение Максимальной суммы Обеспечивает, чтобы значения заданных переменных суммировались до заданной суммы
ExactSumConstraint Обеспечивает, чтобы значения заданных переменных суммировались точно до заданной суммы
Ограничение минимальной суммы Ограничение, обеспечивающее, чтобы значения заданных переменных суммировались по крайней мере до заданной величины
Вставить ограничение Ограничение, обеспечивающее наличие значений заданных переменных в заданном наборе
NotInSetConstraint Ограничение, обеспечивающее отсутствие значений заданных переменных в заданном наборе
Некоторые ограничения вставки Ограничение, принуждающее к тому, что по крайней мере некоторые значения заданных переменных должны присутствовать в данном наборе
SomeNotInSetConstraint Ограничение, обеспечивающее, что по крайней мере некоторые значения заданных переменных не должны присутствовать в данном наборе

При использовании ограничений, которые могут принимать список множителей в качестве параметра (например, Exact Sum или Min Sum ), позаботьтесь о том, чтобы явно указать порядок или переменные, если это необходимо, как мы сделали в Примере E

Вывод

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