以下函数seqToSet接受一系列元素并返回一个包含给定序列中所有(且仅)元素的集合。它通过使用相同的序列和空集调用递归辅助函数addSeqToSet来完成此操作。辅助函数将序列中的每个元素添加到给定的集合中并返回结果。它通过序列的头/尾结构的递归来实现。它在序列为空时完成,否则用序列的尾部递归调用自身,并使用包含序列头部的单例集的联合。
Dafny无法自行验证后置条件,声明结果集包含原始序列中的所有元素。
帮助它了解情况的正确策略是什么?
function method seqToSet(q: seq<int>): set<int>
{
addSeqToSet(q, {})
}
function method addSeqToSet(q: seq<int>, t: set<int>): (r: set<int>)
ensures forall i :: i in q ==> i in r
{
if | q | == 0 then t
else addSeqToSet (q[1..], t + { q[0] })
}
答案 0 :(得分:1)
当Dafny尝试验证递归函数的后置条件时,它的归纳原因是:假设后置条件保留在任何递归调用上,并显示它也适用于当前调用。
想象一下Dafny如何推理addSeqToSet
。
在第一个分支中,| q | == 0
暗示q
为空,因此后置条件保持平凡,因为没有元素i in q
。
在第二个分支中,Dafny假定递归调用的后置条件:
forall i :: i in q[1..] ==> i in r
然后尝试证明当前调用的后置条件:
forall i :: i in q ==> i in r
请注意,由于addSeqToSet
直接返回递归调用的答案,因此r
在上述两个公式中都是相同的。
如果你想一下,你可以看到外部调用的后置条件不会跟随递归调用的后置条件,因为递归调用没有说明q[0]
。
你需要以某种方式加强后置条件,以便你知道q[0] in r
。
其中一个强化措施就是增加关于t
ensures forall i :: i in t ==> i in r
然后,Dafny验证了两个后置条件。