将整数数组的负元素复制到另一个数组的方法具有以下属性:结果中的元素集是原始数组中元素的子集,在复制过程中保持不变。
下面代码中的问题是,只要我们在结果数组中写入一些内容,Dafny 就会不知何故忘记原始集合没有改变。如何解决这个问题?
method copy_neg (a: array<int>, b: array<int>)
requires a != null && b != null && a != b
requires a.Length == b.Length
modifies b
{
var i := 0;
var r := 0;
ghost var sa := set j | 0 <= j < a.Length :: a[j];
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant sa == set j | 0 <= j < a.Length :: a[j]
{
if a[i] < 0 {
assert sa == set j | 0 <= j < a.Length :: a[j]; // OK
b[r] := a[i];
assert sa == set j | 0 <= j < a.Length :: a[j]; // KO!
r := r + 1;
}
i := i + 1;
}
}
编辑
按照James Wilcox 的回答,用序列上的谓词替换集合的包含是最有效的。
这是完整的规范(对于具有不同元素的数组)。后置条件必须在循环不变量中进行一些详细说明,并且在循环中间保留一个愚蠢的断言,但是所有幽灵变量都消失了,这很棒。
method copy_neg (a: array<int>, b: array<int>)
returns (r: nat)
requires a != null && b != null && a != b
requires a.Length <= b.Length
modifies b
ensures 0 <= r <= a.Length
ensures forall x | x in a[..] :: x < 0 <==> x in b[..r]
{
r := 0;
var i := 0;
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant forall x | x in b[..r] :: x < 0
invariant forall x | x in a[..i] && x < 0 :: x in b[..r]
{
if a[i] < 0 {
b[r] := a[i];
assert forall x | x in b[..r] :: x < 0;
r := r + 1;
}
i := i + 1;
}
}