考虑解决100 prisoners and a lightbulb问题的标准策略。我试图在Dafny模拟它:
method strategy<T>(P: set<T>, Special: T) returns (count: int)
requires |P| > 1 && Special in P
ensures count == (|P| - 1)
decreases *
{
count := 0;
var I := {};
var S := {};
var switch := false;
while (count < (|P|-1))
invariant count <= (|P|-1)
invariant count > 0 ==> Special in I
invariant Special !in S && S < P && S <= I && I <= P
decreases *
{
var c :| c in P;
I := I + {c};
if c == Special {
if switch == true {
switch := false;
count := count + 1;
}
} else {
if c !in S && switch == false {
S := S + {c};
switch := true;
}
}
}
assert(I == P);
}
然而,它最终证明了I == P
。为什么?我可能需要进一步强化循环不变量,但不能想象从哪里开始......
答案 0 :(得分:3)
Here是一种方法。
我必须添加一个概念上的关键循环不变量和一个不那么概念上关键但有用的Dafny引理。
你需要一个循环不变量,以某种方式将count
连接到各种集合。否则,循环后count == |P| - 1
非常有用。我选择使用
if switch then |S| == count + 1 else |S| == count
将count
与S
的基数联系起来。 (我认为S
是计数器统计的囚犯集。)当灯灭了时,count
是最新的(即|S| == count
)。但是当灯亮起时,我们正在等待The Counter注意并更新计数,因此它落后于(即|S| == count + 1
)。
有了这个循环不变量,我们现在可以在循环之后争论I == P
。通过其他一个循环不变量,我们已经知道I <= P
。因此,证明P <= I
就足够了。假设I < P
。然后我们有S < I < P
。但是通过循环退出条件,我们也有|S| == |P| - 1
。这是不可能的。
唯一的缺点是Dafny不能直接将子集关系与基数关系联系起来,所以我们必须帮助它。我证明了一个引理CardinalitySubset
,其中A
和B
设置A < B
,|A| < |B|
。B
。证据来自于{{1}}的归纳,并且相对简单。用相关集调用它完成了主要证据。
顺便说一句,我看了一下为什么Dafny没有直接将子集关系连接到基数关系。在Dafny关于集合的公理中,我发现了commented out axiom与子集相关的基数。 (不可否认,这个公理是关于非严格的子集关系,但是通过取消注释这个公理,我能够获得一个版本的证明,而没有额外的引理,所以这就足够了。)追溯到{{ 3}},似乎解算器使用它太多了,即使它没有相关性,这会减慢速度。
它最终并不是一件大事,因为我们可以通过归纳来证明我们需要什么,但这是一个有趣的消息。