Количество комбинаций в конфигураторе - PullRequest
4 голосов
/ 20 июля 2009

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

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

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

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

Я сделал наивный подход, чтобы решить эту проблему, используя Принцип исключения-включения . Однако, насколько я вижу, любой алгоритм, основанный на этом методе, должен работать в O (2 ^ n), который не будет работать. Конечно, есть несколько возможных оптимизаций, которые должны обеспечить достойное время выполнения, но все еще существуют легко создаваемые наихудшие сценарии.

Это довольно много, где я сейчас нахожусь. Есть предложения?

Обновление

Я понимаю, что не объяснил, как правила применяются достаточно хорошо.

Есть несколько групп с опциями. В каждой группе должен быть выбран один и только один вариант. В группе может быть один или несколько вариантов.

Существует только один тип ограничений. Если выбран вариант A в какой-либо группе, то параметр B в какой-либо другой группе выбрать нельзя. Ограничений может быть любое количество, нет ограничений на количество ограничений / правил, применимых к группе параметров или к самому параметру.

Вот пример:

Группа 1:
x1 x2 x3 x4 x5

Группа 2:
у1 у2 у3

Группа 3:
z1 z2 z3 z4

Ограничения:
x1 <-> y2 *
x1 <-> z4
y2 <-> z2

* Если опция x1 выбрана в группе 1, опция y2 в группе 2 не может быть выбрана.

Используя включение-исключение, я бы рассчитал количество комбинаций как

Комбинации = C без правил - C r [1] - C r [2] - C r [3] + C r [1,2] + C r [1,3] + C r [2,3] - C r [1,2,3]

Где

C без правил = 5 * 3 * 4

C r [a, b, c] = Количество комбинаций, нарушающих правило a, b и c.

Метод, к сожалению, требует 2 ^ | rules | расчеты.

Ответы [ 11 ]

3 голосов
/ 25 июля 2009

Хорошо, я не могу обойтись 2 ^ N, но я могу уменьшить выборку. Для этого мы вычислим «Составные ограничения». Составное ограничение - это ограничение, при котором, если выбраны все параметры на левой стороне, тогда ни один из параметров на правой стороне не может быть выбран, но никакие другие ограничения, основанные на параметрах левой стороны, не могут применяться.

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

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

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

  1. Исправьте ограничения так, чтобы группа левой руки была меньше или равна группе правой руки.
  2. Составьте ограничения:

    1. Сортировка ограничений левой рукой
    2. Последовательно, для каждого ограничения:

      1. Сложите ограничение со всеми последующими ограничениями одной и той же левой рукой, превратив x1 <-> y1, x1 <-> y2 ... x1 <-> yN в Set(x1) <-> Set(y1 ... yN)
      2. Составьте сложенное ограничение с каждым уже сложенным ограничением, если:
        • x1 находится не в правой части уже сложенного ограничения
        • x1 не входит в ту же группу любого элемента в левой руке
      3. Добавить сложенное ограничение и все его композиции к набору свернутых ограничений
  3. Вычислите минимальный набор, выбрав все параметры и удалив те, которые указаны в левой части фиксированных ограничений.

Теперь вы можете вычислить количество комбинаций по формуле ниже. Давайте назовем CC составным ограничением. Тогда количество комбинаций:

C(Mininum Set) + CCC1 + ... + CCCn

Где:

  • C (Minimum Set) - количество возможных комбинаций с минимальным набором.
  • CCCx - это число возможных комбинаций, взяв минимальный набор, заменив любые группы, для которых есть опция на левой стороне CCx, этой опцией, а затем удалив любые опции на правой руке CCx.

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

  1. Никакие два термина не могут содержать одну и ту же комбинацию.
  2. Все комбинации должны учитываться этими условиями.
  3. Никакие недопустимые комбинации не могут быть получены любым термином.

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

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

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

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

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

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

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

И этим заканчивается доказательство.

Посмотрим, как это применимо, на примере из комментариев:

G1: x1, x2, x3 
G2: y1, y2 
G3: z1, z2, z3 

R1: x1 <-> y2 
R2: x3 <-> y2 
R3: y1 <-> z1 
R4: y2 <-> z2 
R5: y2 <-> z3

CC1: {x1} <-> {y2}
CC2: {x3} <-> {y2}
CC3: {y1} <-> {z1}
CC4: {x1, y1} <-> {y2, z1}
CC5: {x3, y1} <-> {y2, z1}
CC6: {y2} <-> {z2, z3}

Давайте вкратце рассмотрим составные группы, которых нет в списке:

R1&R2: {x1, x3} <-> {y2} -- not in the list because x1 and x3 belongs to the same
                            group
R1&R5: {x1, y2} <-> {y2} -- not in the list because the left hand of R2, y2
                            appears in the right hand of R1

Теперь посмотрим, какие варианты возможны в каждом наборе:

Minimum Set: (x2), (), (z1, z2, z3)    
CC1: (x1), (), (z1, z2, z3) -- replace G1 with x1, remove y2 from G2
CC2: (x3), (), (z1, z2, z3) -- replace G1 with x3, remove y2 from G2
CC3: (x2), (y1), (z2, z3)   -- replace G2 with y1, remove z1 from G3
CC4: (x1), (y1), (z2, z3)   -- replace G1 with x1, G2 with y1, remove y2 and z1
CC5: (x3), (y1), (z2, z3)   -- replace G1 with x3, G2 with y1, remove y2 and z1
CC6: (x2), (y2), (z1)       -- replace G2 with y2, remove z2 and z3 from G3

Теперь давайте добавим:

C(Minimum Set) = 1 * 0 *3 = 0
CCC1 = 1 * 0 * 3 = 0
CCC2 = 1 * 0 * 3 = 0
CCC3 = 1 * 1 * 2 = 2
CCC4 = 1 * 1 * 2 = 2
CCC5 = 1 * 1 * 2 = 2
CCC6 = 1 * 1 * 1 = 1

C(Minimum Set) + CCC1 + CCC2  + CCC3 + CCC4 + CCC5 + CCC6
0              + 0    + 0     + 2    + 2    + 2    + 1    = 7

Я добавлю еще одну мысль здесь. Несмотря на то, что для 5 правил существует только 6 CCC, что намного меньше ожидаемых 32 терминов, в противном случае эти CCC рассчитываются с наихудшим временем 2 ^ N, поскольку для каждого правила вы должны сравнить и объединить его со всеми созданными к настоящему времени CCC. Вы можете думать о них как о двоичных числах, где бит устанавливается, если правило объединяется, и не устанавливается, если нет.

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

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

Альтернативные алгоритмы и соображения

Ходят разговоры о 2-SAT и 3-SAT. Мне кажется, что это проблема 2-SAT, в том смысле, что каждое ограничение a <-> b на самом деле является предложением "! A ||! B". Таким образом, все ограничения вместе можно просто записать как «(! X1 ||! Y2) && (! X1 ||! Z4) && (! Y2 &&! Z3)» и т. Д. Это означает, что вы можете «решить» это в том смысле, что вы можете узнать, есть ли логическое назначение для каждой опции, которое превратит это истинное значение . Для этого существует линейный алгоритм Аспалла, Пласса и Тарьяна со слайд-презентацией здесь .

Но знать, могут ли быть разрешены ограничения или нет - это не то, о чем спрашивали . Был задан номер способов установки всех опций при сохранении истинности проблемы 2-SAT.

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

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

Если мы перечислим все решения проблемы 2-SAT, количество комбинаций для каждой группы будет равно 1, умноженному на количество свободных опций, опций, которые не появляются ни в одном ограничении, каждой группы, для которой нет Вариант был выбран решением.

Например, принимая предыдущий набор групп и ограничений. Проблема 2-SAT, включая взаимное исключение:

(! X1 ||! Y2) && (! X3 ||! Y2) && (! Y1 ||! Z1) && (! Y2 ||! Z2) && (! Y2 ||! Z3) && (! x1 ||! x3) && (! y1 ||! y2) && (! z1 ||! z2) && (! z1 ||! z3) && (! z2 ||! z3)

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

Решения этой проблемы 2-SAT:

x1    x3    y1    y2    z1    z2    z3    Combinations
true  false true  false false true  false 1
true  false true  false false false true  1
true  false true  false false false false 0
true  false false false true  false false 0
true  false false false false true  false 0
true  false false false false false true  0
true  false false false false false false 0
false true  true  false false true  false 1
false true  true  false false false true  1
false true  true  false false false false 0
false true  false false true  false false 0
false true  false false false true  false 0
false true  false false false false true  0
false true  false false false false false 0
false false true  false false true  false 1
false false true  false false false true  1
false false true  false false false false 0
false false false true  true  false false 1
false false false true  false false false 0
false false false false true  false false 0
false false false false false true  false 0
false false false false false false true  0
false false false false false false false 0

В первых двух решениях нет групп без выбранной опции, поэтому количество комбинаций равно 1. В третьем решении не выбрана опция для группы G3, поэтому мы умножаем 1 на 0. В начинающихся строках при значении «false false» для группы G1 не выбрана ни одна опция, а одна свободная опция: x2. Таким образом, мы умножаем 1 на 1 для них и на 0, если для G2 или G3 не выбрана ни одна опция (в этом случае количество свободных опций равно 0).

Теперь возникает вопрос о том, как заставить один вариант, выбранный в каждой группе, по-прежнему претендовать на 2-SAT. Проблема, как указано, имеет два неявных ограничения: для каждой группы должен быть выбран один и только один вариант. Эти два ограничения могут быть записаны как:

x 1 || х 2 || x 3 (для группы x с параметрами x 1 .. x 3 )
(! x 1 ||! x 2 ) && (! x 1 ||! x 3 ) && (! x 2 ||! X 3 )

Последующее ограничение - 2-SAT, первое - 3-SAT для любого нетривиального случая. Как это происходит, я не применяю первое ограничение, но тогда счет становится равным 0. Алгоритм подсчета должен выглядеть следующим образом:

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

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

Это похоже на обман проблемы с допустимым ограничением> 2-SAT. В конце концов, если бы это было возможно, тогда проблему 3-SAT можно было бы решить, просто перечислив решения ее части 2-SAT, а затем отбросив все, которые не удовлетворяют ее части 3-SAT. Увы, есть одно принципиальное отличие, которое я могу выделить:

  • Все предикаты, не решаемые с помощью 2-SAT-части задачи, свободны от каких-либо дополнительных ограничений.

Учитывая это довольно ограничительное ограничение для предложений, мы можем решить эту проблему, перечислив решения явных ограничений 2-SAT.

Если кто-то хочет продолжить это, продолжайте. Я доволен улучшением предложенного мной решения 2 n .

2 голосов
/ 20 июля 2009

Если у вас есть N группы опций, каждая с Xi опциями (0<=i<N), тогда

X0*X1*...*X(N-1)

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

1 голос
/ 26 июля 2009

Это может быть не совсем полезный ответ, поэтому не стесняйтесь его игнорировать ... однако; В настоящее время я сам не работаю с подобной системой; и, честно говоря, , кроме как для тривиальных примеров . Я не уверен, что полезно пытаться вычислить количество допустимых комбинаций. Например, модели, над которыми я работаю в данный момент, имеют (например) 18000 объектов-кандидатов, распределенных по 80+ выборкам, некоторые из которых выбираются одним или несколькими. За исключением самых маленьких моделей, преимущество в знании числа отсутствует, так как вы просто никогда не напишите его как полную таблицу истинности; вы в значительной степени вынуждены запускать правила (т.е. удалять все, что больше не является допустимым, автоматически выбирать что-либо по необходимости и проверять, не нарушены ли правила) по требованию. Что не обязательно проблема; Мой текущий код обрабатывает эту модель (как веб-сервис) за ~ 450 мс, и большая часть этого времени - фактически время, потраченное на обработку xml для ввода / вывода. Если бы ввод / вывод не был xml, я думаю, это было бы ~ 150 мс, что круто.

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

1 голос
/ 25 июля 2009

EDIT

Этот алгоритм неверен. Я представил альтернативный ответ в другом посте, который все еще равен 2 ^ N в худшем случае, но в противном случае может дать лучшие результаты.

Этот работает в выбранном примере, потому что y2 является частью набора исключений x1, а два первых ограничения основаны на x1. Тем не менее, теперь я вижу, что нужно сделать. Это все еще близко к 2 ^ N, но есть оптимизации, которые могут привести к значительному увеличению.

Чтобы исправить этот алгоритм, составленные правила должны иметь форму set (ox) <-> set (oy). Чтобы составить их, для каждого ограничения с левой составляющей oX, которую вы составляете, вы также создаете другие композиции для него с каждым уже составленным правилом , если oX не является частью правой части составного правила или группа совпадает с левой частью группы .

Для полностью независимых ограничений это 2 ^ N. В противном случае вы уменьшаете N, выполняя:

  • объединяет ограничения с общим левым
  • не вычисляющие комбинации правил, которые являются взаимоисключающими, это делится на:
    • не объединяет правила для опций в одной группе
    • не объединяет правила, в которых левая сторона одного появляется справа от другого

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

Конец редактирования

Давайте перевернем это. Как насчет этого для алгоритма:

  1. Исправьте правила по необходимости, чтобы убедиться, что для правила o1 <-> o2, group(o1) < group(o2)
  2. Вычислить «составленные» правила, сложив все правила oX <-> o? в одно правило oX <-> Set(o?)
  3. Вычислить «чистый» набор групп, убрав из них левый вариант каждого правила
  4. Вычислить альтернативные наборы из чистого набора, по одному для каждого составного правила, заменив группу левой опции на саму левую опцию и вычтя из других групп опции правой руки правила.
  5. Для каждого набора групп рассчитайте количество комбинаций, умножив количество опций в каждой группе в наборе.
  6. Добавьте все результаты из шага 5.

Давайте посмотрим на это на работе:

Group 1:
x1 x2 x3 x4 x5

Group 2:
y1 y2 y3

Group 3:
z1 z2 z3 z4

Constraints (already fixed):
x1 <-> y2 *
x1 <-> z4
y2 <-> z2

Composed rules:
x1 <-> (y2, z4)
y2 <-> (z2)

Clean set of groups:
x2x3x4x5, y1y3, z1z2z3z4

Alternate sets:
x1, y1y3, z1z2z3 (first composed rule)
x2x3x4x5, y2, z1z3z4 (second composed rule)

Totals:
4 * 2 * 4 = 32
1 * 2 * 3 = 6
4 * 1 * 3 = 12

Total: 50

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

c(no rules) = 60
c1 => 4
c2 => 3
c3 => 5
c12 => 1
c13 => 1
c23 => 0
c123 => 0

c(no rules) - c1 - c2 - c3 + c12 + c13 + c23 - c123 = 50

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

Вот его реализация в Scala:

case class GroupOption(id: Int, option: Int)
case class Group(id: Int, options: Set[Int])
case class Rule(op1: GroupOption, op2: GroupOption)
case class ComposedRule(op: GroupOption, set: Set[GroupOption])

object ComputeCombinations {
  def fixRules(rules: Set[Rule]) = {
    rules map (rule => if (rule.op1.id > rule.op2.id) Rule(rule.op2, rule.op1) else rule)
  }

  def ruledOptions(id: Int, rules: Set[Rule]): Set[Int] = (
    rules 
    filter (rule => rule.op1.id == id)
    map (rule => rule.op1.option)
  )

  def cleanseSet(groups: Set[Group], rules: Set[Rule]) = {
    groups map (group => 
      Group(group.id, group.options -- ruledOptions(group.id, rules)))
  }

  def composeRules(rules: Set[Rule]): Set[ComposedRule] = Set(
    (
      rules.toList
      sort (_.op1.id < _.op1.id)
      foldLeft (List[ComposedRule]())
      ) { (list, rule) => list match {
        case ComposedRule(option, set) :: tail if option == rule.op1 =>
          ComposedRule(option, set + rule.op2) :: tail
        case _ => ComposedRule(rule.op1, Set(rule.op2)) :: list
      }} : _*
  )

  def subset(groups: Set[Group], composedRule: ComposedRule) = (
    groups
    filter (_.id != composedRule.op.id)
    map (group => Group(group.id, group.options -- 
                                  (composedRule.set 
                                   filter (_.id == group.id)
                                   map (_.option)
                                  )))
  )

  def subsets(groups: Set[Group], composedRules: Set[ComposedRule]) = (
    composedRules map (composedRule => subset(groups, composedRule))
  )

  def combinations(groups: Set[Group]) = (
    groups.toList map (_.options.size) reduceLeft (_*_)
  )

  def allCombinations(groups: Set[Group], rules: Set[Rule]) = {
    val fixedRules = fixRules(rules)
    val composedRules = composeRules(fixedRules)
    val cleanSet = cleanseSet(groups, fixedRules)
    val otherSets = subsets(cleanSet, composedRules)
    val allSets = otherSets + cleanSet
    val totalCombinations = allSets.toList map (set => combinations(set)) reduceLeft (_+_)
    totalCombinations
  }
}

object TestCombinations {
  val groups = Set(Group(1, Set(1, 2, 3, 4, 5)),
                   Group(2, Set(1, 2, 3)),
                   Group(3, Set(1, 2, 3, 4)))
  val rules = Set(Rule(GroupOption(1, 1), GroupOption(2, 2)),
                  Rule(GroupOption(1, 1), GroupOption(3, 4)),
                  Rule(GroupOption(2, 2), GroupOption(3, 2)))
  def test = ComputeCombinations.allCombinations(groups, rules)
}
1 голос
/ 24 июля 2009

Здесь нет ярлыков для общего случая. Это не так плохо, как вы думаете. См. «Переосмысление» ниже.

2 ^ n действительно так плохо? Сколько правил исключения мы говорим здесь? Вы действительно должны сделать это только один раз для каждого конфигуратора, если набор правил / опций постоянно меняется на лету и требует динамического пересчета. Если на самом деле существует огромное количество правил, то я бы не стал искать точное решение - рассмотрим только пересечения k-го порядка и скажем: «количество комбинаций по крайней мере / большинство ...». Могут быть и другие методы сита, которые позволят вам быстро определить границы ответа.

Также имейте в виду: если вы рассматриваете только те исключения, которые вам действительно нужны, то 2 ^ n - это просто верхняя граница, и ваше фактическое количество вычислений может быть значительно меньше для любых реальных сценариев. То есть, если C [1,2] равно нулю, то и C [1,2, ...]. Учтите это: для каждого ограничения «объедините» наборы ограничений вместе, если они совместно используют какие-либо общие параметры. Понятно, что ваше реальное время работы будет определяться размером самого большого «сгустка» (который, да, может быть больше n).


Переосмысление : C [x, y] будет равно нулю в большинстве случаев. Ограничение может перекрываться только с другими ограничениями, включающими другую группу . Другими словами, (x1 <-> y1) может перекрываться только с (x1 <-> z1) или (y1 <-> z2) или чем-то, но не (x1 <-> y2). Аналогично, набор ограничений может перекрываться только с новой группой: комбинация из (x1 <-> y1) с (y1 <-> z2) не взаимодействует с (x3 <-> z2) ( группа x уже зафиксирована на x1). Вы должны учитывать включение / исключение, когда каждое правило, которое вы добавляете в комбинацию, добавляет к соединению ранее нетронутую группу. Таким образом, вы на самом деле O (2 G ), где G - количество групп (а также, возможно, другая граница в зависимости от размера групп). Гораздо более управляемым!

1 голос
/ 20 июля 2009

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

N0 = C1 * C2 * ... * Cn

Одно ограничение формы ci == x => cj != y запрещает следующее количество конфигураций.

        N
Dk = -------
     Ci * Cj

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

N = prod(Ci, i = 1...n) * (1 - sum(1 / (Cxj * Cyj), j = 1...m))

Здесь xj и yj - оба индекса параметров из j-го ограничения.

Пример * * тысяча двадцать-один

Parameters    n = 4

Parameter 1   C1 = 4   0 1 2 3 
Parameter 2   C2 = 3   4 5 6 
Parameter 3   C3 = 2   X Y
Parameter 4   C4 = 3   7 8 9

Constraints   m = 2

Constraint 1  c2 == 4 => c3 != X
Constraint 2  c3 == X => c4 != 9

N0 = 4 * 3 * 2 * 3 = 72

D1 = 72 / (3 * 2) = 12
D2 = 72 / (2 * 3) = 12

N = 72 - (12 + 12) = 48

UPDATE

Я думаю, что это еще не совсем правильно, поскольку не учитывает зависимости ограничений.

0 голосов
/ 28 июля 2009

Это кратко упоминается в превосходном ответе sdcvvc выше, но может ли приближение Монте-Карло быть достаточно хорошим? Сгенерируйте N случайных конфигураций (где N выбрано так, чтобы мощность вашего эксперимента была достаточно высокой: я не знаю достаточно, чтобы помочь вам в этом), затем проверьте, сколько из них совместимо с вашими ограничениями. Экстраполируйте эту пропорцию на оставшуюся часть конфигурационного пространства.

0 голосов
/ 27 июля 2009

Ваша проблема довольно неосуществима.

  • Подсчет количества решений является # P-полным, даже если вы ограничите каждую группу радиобоксов двумя вариантами
  • Проверка наличия ЛЮБОГО выбора опций, соответствующих ограничениям, является NP-полной
  • Проверка наличия ЛЮБОГО выбора опций, соответствующих ограничениям, может быть выполнена довольно быстро, если вы ограничите каждую группу радиобоксов двумя опциями (2SAT)

Так что в общем случае не рассчитывайте на полиномиальный алгоритм; существование таких алгоритмов подразумевало бы P = NP.

Что вы можете сделать:

  • Используйте алгоритм приближения. В соответствии со статьей Википедии, на которую я ссылался, они часто к ним относятся.
  • Используйте SAT solver http://en.wikipedia.org/wiki/SAT_solver или связанный инструмент для подсчета (к сожалению, я не знаю ни одного); Люди создали много эвристик, и программы в целом будут работать намного быстрее, чем ваше самодельное решение. Есть даже соревнования SAT, поэтому в настоящее время это поле расширяется очень быстро.
  • Проверьте, нужна ли вам такая общность. Возможно, у вашей проблемы есть дополнительные предположения, и это изменит ее сложность.

Доказательства:

  1. Подсчет количества решений легко показать как #P, поэтому достаточно сократить 2SAT до этого. Возьмите 2SAT, например,

(p1 или не p2) и (p2 или не p3)

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

  1. Вы можете легко проверить, разрешен ли какой-либо выбор опций или нет, так что это NP, поэтому достаточно уменьшить 3SAT до этого. Возьмите 3SAT, например,

(p1 или p2 или не p3) и (p2 или не p1 или p4)

Дайте варианты:

группа p1 p1true p1false

группа p2 p2false p2true

группа p3 p3false p3true

группа p4 p4false p4true

группа clause_1 c1a c1b c1c

группа clause_2 c2a c2b c2c

clause_1 будет управлять первым предложением: (p1 или p2 или не p3). В частности, c1a будет проверяться, если был выбран p1true, c1b будет проверяться, если был выбран p2true, c1c будет проверяться, если был выбран p3false. Итак, ограничения:

p1false <-> c1a

p2false <-> c1b

p3true <-> c1c

То же самое с пунктом 2, содержание

p2false <-> c2a

p1true <-> c2b

p4false <-> c2c

Если пользователь сможет выбрать все ответы (таким образом, число конфигураций> 0), он докажет, что существует некоторая оценка переменных p1, ..., p4, которые делают экземпляр 3SAT истинным. И наоборот, если пользователь не сможет выбрать ответы в соответствии с предположениями (количество доступных конфигураций = 0), экземпляр 3SAT не будет удовлетворен. Поэтому знание, является ли ответ> 0, означает знание, разрешим ли экземпляр 3SAT.

Конечно, это сокращение - полиномиальное время. Конец доказательства.

Если вы не удовлетворены тем фактом, что ответ может быть 0: это все еще NP-трудно, если вы игнорируете такие конфигураторы. (Вы бы добавили некоторые «фиктивные» опции для всех групп и позволили бы экспоненциально много вариантов, если «фиктивные» не были выбраны. Это более сложное объяснение, поэтому я сделаю это по запросу.)

0 голосов
/ 25 июля 2009

Комментарий к Пост Даниила :

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

Например, рассмотрим этот случай:

Group 1:
a1 a2 a3 a4 a5

Group 2:
b1 b2 b3

Group 3:
c1 c2 c3 c4

Group 4:
d1 d2 d3 d4 d5

Rules:
a1 <-> b2
a1 <-> c2
b2 <-> c2
b2 <-> d1
a2 <-> d2

Я настроил свой основной алгоритм сита с этой конфигурацией и получил следующие результаты ( 227 комбинаций):

Without rules => 300
Rules: [1] => 20
Rules: [2] => 15
Rules: [3] => 25
Rules: [4] => 20
Rules: [5] => 12
Order: 1 => 208 (diff: -92)
Rules: [1, 2] => 5
Rules: [1, 3] => 5
Rules: [2, 3] => 5
Rules: [1, 4] => 4
Rules: [2, 4] => 1
Rules: [3, 4] => 5
Rules: [1, 5] => 0
Rules: [2, 5] => 0
Rules: [3, 5] => 1
Rules: [4, 5] => 0
Order: 2 => 234 (diff: 26)
Rules: [1, 2, 3] => 5
Rules: [1, 2, 4] => 1
Rules: [1, 3, 4] => 1
Rules: [2, 3, 4] => 1
Rules: [1, 2, 5] => 0
Rules: [1, 3, 5] => 0
Rules: [2, 3, 5] => 0
Rules: [1, 4, 5] => 0
Rules: [2, 4, 5] => 0
Rules: [3, 4, 5] => 0
Order: 3 => 226 (diff: -8)
Rules: [1, 2, 3, 4] => 1
Rules: [1, 2, 3, 5] => 0
Rules: [1, 2, 4, 5] => 0
Rules: [1, 3, 4, 5] => 0
Rules: [2, 3, 4, 5] => 0
Order: 4 => 227 (diff: 1)
Rules: [1, 2, 3, 4, 5] => 0
Order: 5 => 227 (diff: 0)

***Combinations: 227***

Однако, используя этот код в Scala:

  val groups = Set(Group(1, Set(1, 2, 3, 4, 5)),
                   Group(2, Set(1, 2, 3)),
                   Group(3, Set(1, 2, 3, 4)),
                   Group(4, Set(1, 2, 3, 4, 5)))

  val rules = Set(Rule(GroupOption(1, 1), GroupOption(2, 2)),
                  Rule(GroupOption(1, 1), GroupOption(3, 2)),
                  Rule(GroupOption(2, 2), GroupOption(3, 2)),
                  Rule(GroupOption(2, 2), GroupOption(4, 1)),
                  Rule(GroupOption(1, 2), GroupOption(4, 2)))

Я получил ответ 258 .

Я проверил вычисления в методе просеивания, и они, похоже, правы. Возможно, можно исправить ваш алгоритм? Я не могу понять, что не так.

0 голосов
/ 24 июля 2009

Я думаю, Зак думает в правильном направлении. Глядя на ваше выражение для числа комбинаций, вы видите, что члены второго порядка Cr [i, j] намного меньше, чем члены C [k]. Представьте себе куб, где каждая ось представляет собой группу параметров. Каждая точка в кубе представляет определенную комбинацию параметров. Коррекция C [k] первого порядка исключает линию параметров между двумя поверхностями куба. Корректировка второго порядка C [i, j] происходит только тогда, когда две такие линии встречаются в одной точке (комбинация опций) в пространстве в кубе. Таким образом, независимо от количества групп, исправления более высокого порядка всегда становятся все меньше. Если вы придерживаетесь

Комбинации = C (без правил) - Cr [1] - Cr [2] - Cr [3]

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

...