我正在以通常的方式实现通常的统一算法:通过表达式树递归下降,沿途添加变量绑定到哈希表,进行发生检查。在Java中,使用覆盖函数来处理语言,因此处理变量的部分实现是:
@Override
public boolean unify(Term a, Map<Var, Term> map) {
if (this == a) {
return true;
}
Term x = map.get(this);
if (x != null) {
return x.unify(a, map);
}
if (a instanceof Var) {
x = map.get((Var) a);
if (x != null) {
return x.unify(this, map);
}
}
if (a.occurs(this)) {
return false;
}
map.put(this, a);
return true;
}
这个版本是正确的,并且对于许多情况来说非常快,但它有一个问题,特别是当它用于类型推断时。当将大量变量统一到同一目标时,最终会得到一组基本上如下所示的绑定:
a=b
b=c
c=d
d=e
然后每当一个新变量必须统一到同一个东西时,它必须一步一步地遍历链,找到它现在的位置,这需要O(N)时间,意味着将一组变量统一到同一个东西需要总时间O(N ^ 2)。
可能最好的解决方案是实现某种快捷方式,即更新a
以直接指向当前最终目标,无论可能是什么。如何以一种在所有情况下都是正确和有效的方式做到这一点并不完全明显。
统一已经众所周知并且已经广泛使用了几十年,所以我认为解决这个问题的方法也必须已经知道了几十年,但是我所看到的关于统一的几个讨论似乎并不多见提到它。
修改算法以确切处理它的方法是什么?
答案 0 :(得分:2)
我同意快捷方式是正确的方法。你应该可以改变这个:
return x.unify(a, map);
到此:
if (! x.unify(a, map)) {
return false;
}
map.put(this, map.get(x));
return true;
和此:
return x.unify(this, map);
到此:
if (! x.unify(this, map)) {
return false;
}
map.put(a, map.get(x));
return true;
(每个人map.put
只是削减了一个间接层,但是因为你在递归调用之后立即执行它也会减少任何不必要的间接,你知道只有一个层次的间接为它 切出。)
这并不能完全阻止链,因为可以将a
与b
和然后 b
统一为c
,依此类推;但是每个链条在第一次重新遇到时都会被完全处理掉,所以你仍然可以按时间摊销。
答案 1 :(得分:2)
这是一个想法:由=
连接的所有变量都是等价类。所以你可以制作地图
unify(Term a, Map<VarClass, Term> map) {...
使用classical union-find algorithm for disjoint sets实施VarClass
。
当您发现以前已添加到地图中的变量对x=y
时,请将x
添加到包含VarClass
的{{1}}中(创建一个并添加一个可变的空占位符映射(如果还不存在)。
地图右侧的y
绝不是Term
。
联合查找操作适用于所有实际目的,在实践中以固定的时间和非常快的速度摊销。