Алгоритм разрешения зависимостей

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

каждый пакет имеет несколько версий, а каждая версия содержит следующую информацию:

  • сопоставимый ID
  • зависимости (список пакетов и для каждого пакета набор приемлемых версий)
  • конфликты (список пакетов и для каждого пакета набор версии, которые вызывают проблемы вместе с этой версией)
  • предоставляет (список пакетов и для каждого пакета набор версий, которые этот пакет также предоставляет / содержит)

для текущего состояния у меня есть список пакетов и их текущей версии.

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

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

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

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

единственная идея, которую я имею до сих пор, - это построение структуры всех возможные состояния для всех возможных версий рассматриваемых пакетов, а затем удаление недопустимых состояний. Я очень надеюсь, что это не единственное решение, так как он чувствует себя очень "грубой силой". Пребывание в течение нескольких секунд для ~ 500 доступных пакетов с ~100 версиями каждый и ~150 установленных пакетов было бы хорошей целью (хотя чем быстрее, тем лучше).

Я не верю, что это языковой вопрос, но чтобы лучше проиллюстрировать это, вот немного pseudecode:

struct Version
    integer id
    list<Package, set<integer>> dependencies
    list<Package, set<integer>> conflicts
    list<Package, set<integer>> provides

struct Package
    string id
    list<Version> versions

struct State
    map<Package, Version> packages
    map<Package, boolean> isVersionLocked

State resolve(State initialState, list<Package> availablePackages, list<Package> newPackages)
{
    // do stuff here
}

(Если вы должны иметь фактический код или знать о существующей реализации чего-то, что делает это (на любом языке, предпочтительном c++), не стесняйтесь упоминать об этом в любом случае)

1 ответов


это NP-трудно

некоторые плохие новости: эта проблема NP-hard, поэтому, если P=NP, нет алгоритма, который может эффективно решить все ее экземпляры. Я докажу это, показав, как преобразовать в полиномиальное время любой данный экземпляр NP-трудной задачи 3SAT в структуру графа зависимостей, подходящую для ввода вашей проблемы, и как превратить вывод любого алгоритма разрешения зависимостей по этой проблеме обратно в решение исходной проблемы 3SAT, снова в полиномиальное время. Логика в основном заключается в том, что если бы был какой-то алгоритм, который мог бы решить вашу проблему разрешения зависимостей в полиномиальное время, то он также решил бы любой экземпляр 3SAT в полиномиальное время-и поскольку компьютерные ученые потратили десятилетия на поиск такого алгоритма, не найдя его, это считается невозможным.

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

во-первых, давайте сформулируем слегка расслабленную версию проблемы разрешения зависимостей, в которой мы предполагаем, что пакеты еще не установлены. Все, что нам нужно, это алгоритм, который, учитывая" целевой " пакет, либо возвращает набор версий пакета для установки ,который (a) включает некоторую версию целевого пакета и (b) удовлетворяет всем свойствам зависимости и конфликта каждый пакет в наборе или возвращает "невозможно", если ни один набор версий пакета не будет работать. Ясно, что если эта проблема NP-hard, то это более общая проблема, в которой мы также указываем набор уже установленных версий пакета, которые не должны быть изменены.

создания экземпляра

предположим, что нам дан экземпляр 3SAT, содержащий n предложений и K переменных. Мы создадим 2 пакета для каждой переменной: один, соответствующий литералу x_k, и один соответствует буквальному !x_k. Пакет x_k будет иметь конфликт с !пакет x_k, и наоборот, гарантируя, что в один из этих двух пакетов будет установлен менеджер пакетов. Все эти" литеральные " пакеты будут иметь только одну версию и никаких зависимостей.

для каждого предложения мы также создадим один "родительский" пакет и 7 версий "дочернего" пакета. Каждый родительский пакет будет зависеть от любой из 7 версий его дочернего пакета. Дочерние пакеты соответствуют способам выбора хотя бы одного элемента из набора из 3 элементов и будут иметь 3 зависимости от соответствующих пакетов литералов. Например, предложение (p,!q, r) будут иметь дочерние версии пакетов, имеющие зависимости от литеральных пакетов (p, q,!r), (!п !q,!r), (!p, q, r), (p,!q,!r), (p, q, r), (!п !п, р) и (п !q, r): первые 3 версии удовлетворяют ровно одному из литералов p, !q или r; следующие 3 версии удовлетворяют точно 2; и последняя удовлетворяет всем 3.

наконец, мы создаем "корневой" пакет, который имеет все N родительских пакетов предложений в качестве своих зависимостей. Это будет пакет, который мы просим менеджера пакетов установить.

если мы запустим диспетчер пакетов на этом наборе версий пакета 2k + 8n + 1, попросив его установить корневой пакет, он либо вернет "невозможно", либо список версий пакета для установки. В первом случае проблема 3SAT не может быть решена. В последнем случае, мы можем извлечь значения для переменных: если буквальный пакет для x_k был установлен в x_k true; Если буквальный пакет !x_k установлено x_k к false. (Обратите внимание, что не будет никаких переменных ни с одним установленным литеральным пакетом: каждая переменная появляется по крайней мере в одном предложении, и каждое предложение создает 7 дочерних версий пакета, по крайней мере одна из которых должна быть установлена, и которая заставит установку одного из двух литералов для этой переменной.)

даже некоторые ограничения жесткие

эта конструкция не использует предварительно установленные пакеты или" предоставляет " информацию, поэтому проблема остается NP-жесткой, даже если они не разрешены. Более интересно, учитывая наше предположение, что не более одной версии любого пакета может быть установлен в то время, проблема остается NP-hard даже если мы не разрешаем конфликты: вместо того, чтобы делать литералы x_k и !x_k отдельные пакеты с конфликтными предложениями в каждом направлении, мы просто сделайте их двумя разными версиями одного пакета!