Ruby 解决依赖约束
我有一个典型的依赖解决问题。我原以为我朝着正确的方向前进,但现在我遇到了一个路障,我不知道如何前进 背景 在已知的宇宙(所有工件及其依赖项的缓存)中,每个工件和版本之间都有1->n关系,每个版本可能包含一组不同的依赖项。例如:Ruby 解决依赖约束,ruby,algorithm,graph,directed-graph,Ruby,Algorithm,Graph,Directed Graph,我有一个典型的依赖解决问题。我原以为我朝着正确的方向前进,但现在我遇到了一个路障,我不知道如何前进 背景 在已知的宇宙(所有工件及其依赖项的缓存)中,每个工件和版本之间都有1->n关系,每个版本可能包含一组不同的依赖项。例如: A 1.0.0 B (>= 0.0.0) 1.0.1 B (~> 0.1) B 0.1.0 1.0.0 给定一组“需求约束”,我想找到最好的解决方案(其中“最佳”是仍然满足所有约束的最高可能版本)。以下是解决方案的“需求约束”
A
1.0.0
B (>= 0.0.0)
1.0.1
B (~> 0.1)
B
0.1.0
1.0.0
给定一组“需求约束”,我想找到最好的解决方案(其中“最佳”是仍然满足所有约束的最高可能版本)。以下是解决方案的“需求约束”示例:
solve!('A' => '~> 1.0') #=> {"A" => "1.0.1", "B" => "0.1.0"}
事实上,有更多的需求:
solve!('A' => '~> 1.0', 'B' => '>= 0.0.0', 'C' => '...', 'D' => '...')
(版本遵循标准)
我试过了
当前的解决方案使用回溯,性能不是很好。我做了一些挖掘,发现由于宇宙的大小而导致的性能问题。我决定尝试另一种方法,仅为一组需求构建一个“可能性”DAG图:
类图
def初始化
@节点={}
@边={}
结束
def节点(对象)
@节点[对象]| |=Set.new
自己
结束
def边缘(a、b)
节点(a)
节点(b)
@节点[a]。添加(b)
自己
结束
def节点
@节点.密钥
结束
def边缘
@节点。值
结束
def邻接(节点)
@节点[节点]
结束
结束
然后,我构建了一个包含宇宙中所有可能解的DAG。这大大减少了可能性的数量,并为我提供了一个带有真实工件可能性的实际图形
def填充(工件)
加载时返回?(工件)
@节点(工件)
artifact.dependency.each do | dependency|
版本(依赖)。每个版本都有依赖工件|
@图边(伪影、从属伪影)
填充(依赖_工件)
结束
结束
结束
私有的
def版本(依赖项)
可能值=@universe.versions(dependency.name,dependency.constraint)
#如果此依赖项没有版本,则短路,
#因为我们知道这个图是不可解的。
如果可能,请提出“无#{dependency}解决方案!”是否为空?
可能的
结束
因此,从前面的示例图中,如果我有需求'A','>=0.0.0'
,我的DAG将如下所示:
+---------+ +---------+
| A-1.0.0 | | A-1.0.1 |
+---------+ +---------+
/ \ |
/ \ |
/ \ |
/ \ |
+---------+ +---------+
| B-1.0.0 | | B-0.1.0 |
+---------+ +---------+
由于A-1.0.0的可能值为“B的任何值”,但A-1.0.1的约束条件为“0.1系列中的任何B”。这是目前正在工作(与一个完整的测试套件)的预期
换句话说,DAG接受抽象依赖约束并创建一个“真实”图,其中每个边都是依赖项,每个顶点(我称之为节点
)都是实际工件。如果一个解决方案存在,它就在这个图的某个地方
可悲的是,这就是我陷入困境的地方。我无法想出一个算法或程序来通过这个图表找到“最佳”路径。我也不确定一种方法来检测这个图是否是不可解的
我做了一些研究,我认为拓扑排序(tsort)是我需要的过程。但是,该算法确定依赖项的插入顺序,而不是最佳解决方案
我相当肯定这是一个np难问题,运行时可能效率低下。我认为使用DAG可以减少我必须进行的比较。我的假设错了吗?是否有更好的数据结构可供使用
最后的想法
- 我将这个问题标记为“Ruby”,因为我正在使用Ruby,但我正在寻找psuedo代码/方向。这不是一个家庭作业问题——我真的在努力学习
- 我已经尽可能多地介绍了背景知识,但如果您想了解某个特定主题的更多细节,请留下评论。这已经是一篇很长的文章了,但是我有更多的代码可以分享
堆叠。按
在堆叠的前面插入一个项目
System.assert(Condition a, Condition b):
if (a is INVALID) then return SKIP
else if (b.Range = a.Range) then IDENTICAL
else if (b.Range - a.Range = {}) then VALID
else INVALID
Stack.pop
从堆栈前面移除项目
System.assert(Condition a, Condition b):
if (a is INVALID) then return SKIP
else if (b.Range = a.Range) then IDENTICAL
else if (b.Range - a.Range = {}) then VALID
else INVALID
Set.find(x)
根据条件x搜索项目
Condition.apply(Condition b) = { this.Name, intersection(this.Range,b.Range) }
这很难。看这个问题:你看过Bundler做什么了吗?我看过。他们的解析器非常特定于bundler和Ruby生态系统。您是否绝对保证依赖关系图是非循环的?如果我没记错的话,树状SAT问题是可以处理的。编辑:我为树状SAT调用的算法称为“警告传播”,尽管我在任何地方都找不到好的、简短的编写。如果我能找到时间,我可能自己做一个。@AndyJones据我所知,这些算法只有在约束图的宽度较低(分支、树等)时才有效。一般DAG可能具有线性宽度。
for (Condition c in C)
{
S.find(c.Name).apply(c)
}
While (Q.size > 0)
{
Condition q = Q.pop()
switch (T.assert(S.find(q.Name),q))
{
case VALID:
S.find(q.Name).apply(q)
q.push(S.find(q.Name).Requirement)
case INVALID:
S.find(q.Name).set(INVALID)
case IDENTICAL:
case SKIP:
}
}
return S aka Solution
System.assert(Condition a, Condition b):
if (a is INVALID) then return SKIP
else if (b.Range = a.Range) then IDENTICAL
else if (b.Range - a.Range = {}) then VALID
else INVALID
Condition.apply(Condition b) = { this.Name, intersection(this.Range,b.Range) }