isabelle - 非平凡列表函数的归纳
问题描述
这是一个数学练习(取自第 2 页- 俄语):
共有 100 枚在视觉上无法区分的硬币,分为三种类型:金、银和铜(每种类型至少出现一次)。众所周知,金各重3克,银各重2克,铜各重1克。如何在不带砝码的两盘秤上称量不超过 101 次的所有硬币的类型?
(注:我猜这个练习是错误的,最多需要102个称重。不过没关系)
解决方法如下:
- 从硬币列表中逐个取出硬币并将每个硬币与前一个硬币进行比较
- 如果硬币的重量相同,那么我们将它们分配到一组并继续进一步称重
- 如果我们发现比前一个更重的硬币 c j,则转到步骤 2
- 如果我们找到一个比前一个更轻的硬币 c i,那么继续称量硬币,试图找到一个比 c i重的硬币 c j
- 如果我们找到更轻的硬币,那么 c 0 > c i > c j并且我们知道这些硬币的重量:3 > 2 > 1。转到步骤 3
- 继续比较硬币
- 如果我们发现硬币 c k比 c j重,则 c i < c j < c k(权重为 1 < 2 < 3)
- 如果我们发现 c k比 c j更轻,那么比较 c i和 c k
- 如果 c i < c k,则 c i , c j , c k的权重为1, 3, 2
- 如果 c i > c k,则 c i , c j , c k的权重为2, 3, 1
- 如果 c i = c k,则将 c i + c k与 c j进行比较
- 如果 c i + c k < c j,则 c i , c j , c k的权重是 1, 3, 1 (在这种情况下我们没有银币,所以我们将在台阶上使用两个铜币代替3 和 4)
- 如果 c i + c k > c j,则 c i , c j , c k的权重为2, 3, 2
- 如果 c i + c k = c j,则 c i , c j , c k的权重为1, 2, 1
- 将其余硬币与银币(或两个铜币)进行比较
- 较轻的硬币是铜的
- 相同的硬币是银
- 较重的硬币是黄金
- 如果在第 1 步中我们首先找到了较轻的硬币而不是较重的硬币,那么我们需要将第一枚较重的硬币与银币进行比较以确定它们的重量(根据硬币组,它可能是第 102 次重量)
这是一个硬币列表的例子:
c 0 c i c j c k 3 3 2 2 2 3 3 1 1 2 1 3 |_| |___| |_| 伊克
这是 Isabelle HOL 中的一个解决方案:
datatype coin = GC | SC | CC
datatype comp = LT | EQ | GT
primrec coin_weight :: "coin ⇒ nat" where
"coin_weight CC = 1"
| "coin_weight SC = 2"
| "coin_weight GC = 3"
primrec sum_list where
"sum_list f [] = 0"
| "sum_list f (x # xs) = f x + sum_list f xs"
definition weigh :: "coin list ⇒ coin list ⇒ comp" where
"weigh xs ys = (
let xw = sum_list coin_weight xs in
let yw = sum_list coin_weight ys in
if xw < yw then LT else
if xw > yw then GT else EQ)"
definition std_weigh :: "coin list ⇒ coin ⇒ nat" where
"std_weigh xs ys ≡ (case weigh xs [ys] of LT ⇒ 3 | GT ⇒ 1 | EQ ⇒ 2)"
definition gen_weights :: "coin list ⇒ coin ⇒ coin list ⇒ nat ⇒ nat ⇒ nat ⇒ nat ⇒ nat ⇒ nat ⇒ nat list" where
"gen_weights cs c⇩0 std i j k w⇩j w⇩k w ≡
― ‹Optional heavy coins (\<^term>‹c⇩0›...)›
replicate i (std_weigh std c⇩0) @
― ‹Light coins (\<^term>‹c⇩i›...)›
replicate j w⇩j @
― ‹Heavy coins (\<^term>‹c⇩j›...)›
replicate k w⇩k @
― ‹A light coin (\<^term>‹c⇩k›)›
[w] @
― ‹Rest coins›
map (std_weigh std) cs"
primrec determine_weights where
"determine_weights [] c⇩0 c⇩i c⇩j i j k = None"
| "determine_weights (c⇩k # cs) c⇩0 c⇩i c⇩j i j k = (
case weigh [c⇩j] [c⇩k]
of LT ⇒ Some (gen_weights cs c⇩0 [c⇩j] i j (Suc k) 1 2 3)
| GT ⇒ Some (
case weigh [c⇩i] [c⇩k]
of LT ⇒ gen_weights cs c⇩0 [c⇩k] i j (Suc k) 1 3 2
| GT ⇒ gen_weights cs c⇩0 [c⇩i] i j (Suc k) 2 3 1
| EQ ⇒ (
case weigh [c⇩i, c⇩k] [c⇩j]
of LT ⇒ gen_weights cs c⇩0 [c⇩i, c⇩k] i j (Suc k) 1 3 1
| GT ⇒ gen_weights cs c⇩0 [c⇩k] i j (Suc k) 2 3 2
| EQ ⇒ gen_weights cs c⇩0 [c⇩j] i j (Suc k) 1 2 1))
| EQ ⇒ determine_weights cs c⇩0 c⇩i c⇩j i j (Suc k))"
primrec find_heavier where
"find_heavier [] c⇩0 c⇩i i j alt = None"
| "find_heavier (c⇩j # cs) c⇩0 c⇩i i j alt = (
case weigh [c⇩i] [c⇩j]
of LT ⇒ determine_weights cs c⇩0 c⇩i c⇩j i (Suc j) 0
| GT ⇒ alt cs c⇩j (Suc j)
| EQ ⇒ find_heavier cs c⇩0 c⇩i i (Suc j) alt)"
primrec weigh_coins where
"weigh_coins [] = Some []"
| "weigh_coins (c⇩0 # cs) =
find_heavier cs c⇩0 c⇩0 0 0
(λcs c⇩i i. find_heavier cs c⇩0 c⇩i i 0
(λcs c⇩j j. Some (gen_weights cs c⇩0 [c⇩i] 0 i j 3 2 1)))"
我可以证明该解决方案对于具体案例是有效的:
definition "coins ≡ [GC, GC, SC, SC, SC, GC, GC, CC, CC, SC, CC, GC]"
value "weigh_coins coins"
lemma weigh_coins_ok:
"cs = coins ⟹
weigh_coins cs = Some ws ⟹
ws = map coin_weight cs"
by (induct cs; auto simp: coins_def weigh_def gen_weights_def std_weigh_def)
lemma weigh_coins_length_ok:
"cs = coins ⟹
weigh_coins cs = Some ws ⟹
length cs = length ws"
by (induct cs; auto simp: coins_def weigh_def gen_weights_def std_weigh_def)
但是我不知道如何证明它的一般情况:
lemma weigh_coins_ok:
"weigh_coins cs = Some ws ⟹
ws = map coin_weight cs"
proof (induct cs)
case Nil
then show ?case by simp
next
case (Cons c cs)
then show ?case
qed
我不能归纳,cs
因为我需要证明
weigh_coins (c # cs) = Some ws ⟹ ∃ws. weigh_coins cs = Some ws
它不成立。我可以为 确定权重[CC, SC, GC]
,但不能为 确定权重[SC, GC]
。
另一种方法是针对特殊情况证明这些引理:
[CC, CC, ...] @ [SC, SC, ...] @ [GC, GC, ...] @ ...
[CC, CC, ...] @ [GC, GC, ...] @ [SC, SC, ...] @ ...
[SC, SC, ...] @ [GC, GC, ...] @ [CC, CC, ...] @ ...
...
然后证明案例清单是详尽的。
例如:
lemma weigh_coins_length:
"cs = [CC] @ replicate n CC @ [SC, GC] ⟹
weigh_coins cs = Some ws ⟹
length cs = length ws"
apply (induct n arbitrary: cs ws)
apply (auto simp: weigh_def gen_weights_def std_weigh_def)[1]
但是我什至无法证明这个引理。
问题是:
- 您能否建议如何证明这样的引理或如何重新构造函数以使引理可证明?
- 如何制定算法中
weigh
使用最多的函数的引理,硬币的数量在哪里?n + 2
n
解决方案
一些一般提示:
您有三个递归函数:determine_weights
, find_heavier
, weigh_coins
.
对于每个递归函数,尝试在不使用递归的情况下表达输入和结果之间的关系(而不是使用量词)。您为早期函数证明的属性必须足够强大以证明后面函数的属性。此外,该属性不应修复任何参数。例如find_heavier
,最初总是用 调用j = 0
,但是该属性应该适用于所有值,j
以便在归纳期间可以使用它。
还尝试制定和证明您描述中的高级步骤:例如显示此功能找到一个银币或两个铜币。
关于问题2:
我会尝试以不可能作弊的方式陈述问题。例如:
datatype strategy =
Return "coin list"
| Weigh "nat list" "nat list" "comp ⇒ strategy" ― ‹Weigh coins based on positions›
definition "select indexes coins ≡ map (nth coins) indexes"
fun runStrategy where
"runStrategy coins _ (Return r) = Some r"
| "runStrategy coins 0 _ = None"
| "runStrategy coins (Suc n) (Weigh xs ys cont) = (
if distinct xs ∧ distinct ys ∧ set xs ∩ set ys = {} then
runStrategy coins n (cont (weigh (select xs coins) (select ys coins)))
else None)"
lemma "∃strategy. ∀coins.
length coins = 100 ∧ (∀c. c ∈ set coins)
⟶ runStrategy coins 101 strategy = Some coins"
这里 runStrategy 最多调用weigh
101 次,并且该策略无法了解有关硬币的任何信息,除了传递给 continuation 的比较结果Weigh
。
推荐阅读
- swift - 如何使用 SwiftUI 实现打印机选择器?
- javascript - 如何更改计算属性vuejs中的数组元素
- c# - 比较两个复杂对象列表
- sql - Postgres,使用列中的值将结果聚合到单行
- powershell - 需要帮助创建 PowerShell 脚本以清除 Active Directory 属性
- ios - Swift:如何正确解码 unicode 字符?
- performance - Powershell:使用“Out-File”写入较小的文件需要 30 倍的时间
- java - 以最快、最有效的方式获取二维矩阵的子矩阵
- swift - 为什么我不能将 subView 添加到我的 stackView 而不使 stackView 成为惰性变量?
- node.js - nodejs命令行应用程序的快速日志记录解决方案