AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 76924944
Accepted
Carl
Carl
Asked: 2023-08-18 04:33:43 +0800 CST2023-08-18 04:33:43 +0800 CST 2023-08-18 04:33:43 +0800 CST

使用 Dafny,验证函数来计算小于阈值的整数集元素

  • 772

我对任何可验证的 CountLessThan 编写方式持开放态度。这是我所拥有的:

function SetLessThan(numbers: set<int>, threshold: int): set<int>
{
  set i | i in numbers && i < threshold
}
method CountLessThan(numbers: set<int>, threshold: int) returns (count: int)
  ensures count == |SetLessThan(numbers, threshold)|
{
  count := 0;
  var shrink := numbers;
  var grow := {};
  while |shrink | > 0
    decreases shrink
    invariant shrink + grow == numbers
    invariant count == |SetLessThan(grow, threshold)|
  {
    var i: int :| i in shrink;
    shrink := shrink - {i};
    grow := grow + {i};
    if i < threshold {
      count := count + 1;
    }
  }
}

method Main()
{
  var s: set<int> := {1, 2, 3, 4, 5};
  var c: int := CountLessThan(s, 4);
  print c;
  // assert c == 3;

}

这是定义排序集、排序映射、范围集的一小步。

dafny
  • 1 1 个回答
  • 14 Views

1 个回答

  • Voted
  1. Best Answer
    James Wilcox
    2023-08-18T05:35:11+08:002023-08-18T05:35:11+08:00

    您面前有一个经典的验证调试练习:)

    验证调试的基本方法是添加assert语句来缩小问题所在的范围。

    我写下了理想化调试会话的完整记录。如果您只想要工作代码,请随意跳到最后。

    您帖子中的代码报告了循环不变量的以下错误:

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
    
      invariant count == |SetLessThan(grow, threshold)|
        // Error: might not be maintained
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      grow := grow + {i};
      if i < threshold {
        count := count + 1;
      }
    }
    

    这意味着 Dafny 无法在循环底部重新建立不变量。让我们将其转换为断言。

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      grow := grow + {i};
      if i < threshold {
        count := count + 1;
      }
      assert count == |SetLessThan(grow, threshold)|;  // Error: might not hold
    }
    

    我们实际上并没有做任何事情,但请注意,不再报告不变量的错误,因此总共只有一个错误。达夫尼说的是“如果你能证明这个断言,那么其余的代码也会验证”。

    现在我们开始将断言向后移动到循环中。将其移至单侧 if 语句的最简单方法是人为地引入一个空else分支,然后在两侧复制断言。

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      grow := grow + {i};
      if i < threshold {
        count := count + 1;
        assert count == |SetLessThan(grow, threshold)|;  // Error: might not hold
      } else {
        assert count == |SetLessThan(grow, threshold)|;  // Error: might not hold
      }
      assert count == |SetLessThan(grow, threshold)|;
    }
    

    请注意,最底部的错误assert消失了。我们有两个错误,但如果我们能修复这些错误,我们就完成了。我们正在取得进展。

    在第一个分支中,我们现在将assert作业向后移动。为此,我们将出现的替换count为:count + 1assert

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      grow := grow + {i};
      if i < threshold {
        assert count + 1 == |SetLessThan(grow, threshold)|; // Error: might not hold
        count := count + 1;
        assert count == |SetLessThan(grow, threshold)|;
      } else {
        assert count == |SetLessThan(grow, threshold)|;  // Error: might not hold
      }
      assert count == |SetLessThan(grow, threshold)|;
    }
    

    请注意,仍然只有两个错误。

    现在我们需要将断言从if. 最简单的方法是使用 -if表达式。

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      grow := grow + {i};
      assert if i < threshold
        then (count + 1 == |SetLessThan(grow, threshold)|)  // Error
        else count == |SetLessThan(grow, threshold)|;  // Error
      if i < threshold {
        assert count + 1 == |SetLessThan(grow, threshold)|;
        count := count + 1;
        assert count == |SetLessThan(grow, threshold)|;
      } else {
        assert count == |SetLessThan(grow, threshold)|;
      }
      assert count == |SetLessThan(grow, threshold)|;
    }
    

    Dafny 显示仍然显示两个分支的单独错误,但它们现在再次成为一个assert语句的一部分,取得了更多进展。

    接下来我们将作业移回grow。我们在断言中替换grow + {i}为。grow

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
    
      assert if i < threshold
        then (count + 1 == |SetLessThan(grow + {i}, threshold)|)
        else count == |SetLessThan(grow + {i}, threshold)|;
      // Error: might not hold
    
      grow := grow + {i};
      assert if i < threshold
        then (count + 1 == |SetLessThan(grow, threshold)|)
        else count == |SetLessThan(grow, threshold)|;
      if i < threshold {
        assert count + 1 == |SetLessThan(grow, threshold)|;
        count := count + 1;
        assert count == |SetLessThan(grow, threshold)|;
      } else {
        assert count == |SetLessThan(grow, threshold)|;
      }
      assert count == |SetLessThan(grow, threshold)|;
    }
    

    我们已经成功地再次转移了错误。

    我们可以继续回顾这个shrink作业,但因为它与这个断言无关,所以没关系,所以让我们留在这里。

    至此,我们已经消除了验证问题中的所有“命令性的东西”,只剩下“逻辑性的东西”需要担心了。我们来证明一下这个断言。

    首先,我们可以重写断言以将其==从if-then-else. 将其放在assert上一个之上。

    assert (if i < threshold then count + 1 else count) ==
      |SetLessThan(grow + {i}, threshold)|;
    

    这是我们现在唯一的错误,所以我们仍在进步。如果我们能证明这一点,达夫尼将证明其余的事情。

    count我们也可以找出 的发生。将此断言放在前一个断言之上。

    assert count + (if i < threshold then 1 else 0) == 
      |SetLessThan(grow + {i}, threshold)|;
    

    我们将用一个calc计算块来证明这一点。将此块放在前一个断言之上。

    calc {
      count + (if i < threshold then 1 else 0);
      |SetLessThan(grow + {i}, threshold)|;  // Error: might not equal previous line
    }
    

    我们有一个关于 的循环不变式count,所以让我们将其代入。

    calc {
      count + (if i < threshold then 1 else 0);
      |SetLessThan(grow, threshold)| + (if i < threshold then 1 else 0);
      |SetLessThan(grow + {i}, threshold)|;  // Error
    }
    

    仍然只有一个错误,因此达夫尼同意我们计算的第一步。

    为了填补第二行和第三行之间的空白,我们需要连接集合SetLessThan(grow + {i}, threshold)和SetLessThan(grow, threshold)。它们是如何连接的?那么如果i < threshold前者是后者的加i,否则它们就相等。这么说吧。

    assert SetLessThan(grow + {i}, threshold) ==
      SetLessThan(grow, threshold) + (if i < threshold then {i} else {});
    calc {
      count + (if i < threshold then 1 else 0);
      |SetLessThan(grow, threshold)| + (if i < threshold then 1 else 0);
      |SetLessThan(grow + {i}, threshold)|;  // Error
    }
    

    请注意,顶部的断言没有错误!所以达夫尼喜欢我们设定的相等性,但仍然无法证明基数事实。

    让我们尝试通过将基数运算符拉到加号之外来填补第二行和第三行之间的空白。

    calc {
      count + (if i < threshold then 1 else 0);
      |SetLessThan(grow, threshold)| + (if i < threshold then 1 else 0);
      |SetLessThan(grow, threshold) + (if i < threshold then {i} else {})|;  // Error: might not equal previous line
      |SetLessThan(grow + {i}, threshold)|;
    }
    

    啊哈!错误移动了。现在最后一行和倒数第二行相等,但中间两行不同。是什么赋予了?

    嗯,并集的基数并不总是基数之和。仅当它们不相交时才是正确的。这些集合是不相交的吗?是的!为什么?因为我们退出i了收缩,并且grow和shrink是脱节的。但达夫尼无法判断,因为我们需要另一个循环不变量。

    在 Dafny 中,A !! BmeansA和B是脱节的。

    我们添加这个循环不变量:

    invariant grow !! shrink
    

    所有的错误都消失了!!!呜呜。我们庆祝。

    现在我们清理干净。我们添加了大量不必要的断言,现在我们可以删除这些断言并找出真正重要的内容。只需尝试删除一些内容并查看仍然可以验证的内容即可。

    事实证明,唯一重要的是循环不变式和 1 assert。这是最后的循环

    while |shrink | > 0
      decreases shrink
      invariant shrink + grow == numbers
      invariant grow !! shrink
      invariant count == |SetLessThan(grow, threshold)|
    {
      var i: int :| i in shrink;
      shrink := shrink - {i};
      assert SetLessThan(grow + {i}, threshold) ==
        SetLessThan(grow, threshold) + if i < threshold then {i} else {};
      grow := grow + {i};
      if i < threshold {
        count := count + 1;
      } 
    }
    
    • 3

相关问题

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    使用 <font color="#xxx"> 突出显示 html 中的代码

    • 2 个回答
  • Marko Smith

    为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类?

    • 1 个回答
  • Marko Smith

    您可以使用花括号初始化列表作为(默认)模板参数吗?

    • 2 个回答
  • Marko Smith

    为什么列表推导式在内部创建一个函数?

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 个回答
  • Marko Smith

    为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)?

    • 4 个回答
  • Marko Smith

    为什么库中不调用全局变量的构造函数?

    • 1 个回答
  • Marko Smith

    std::common_reference_with 在元组上的行为不一致。哪个是对的?

    • 1 个回答
  • Marko Smith

    C++17 中 std::byte 只能按位运算?

    • 1 个回答
  • Martin Hope
    fbrereto 为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 您可以使用花括号初始化列表作为(默认)模板参数吗? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi 为什么列表推导式在内部创建一个函数? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A fmt 格式 %H:%M:%S 不带小数 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python C++20 的 std::views::filter 未正确过滤视图 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute 为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa 为什么库中不调用全局变量的构造函数? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis std::common_reference_with 在元组上的行为不一致。哪个是对的? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev 为什么编译器在这里错过矢量化? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan C++17 中 std::byte 只能按位运算? 2023-08-17 17:13:58 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve