我在Coq工作并试图弄清楚如何做下一件事:如果我有一个自然数字列表和给定数字n
,我想打破我的列表每个n
之前和之后。为了更清楚,如果我有列表[1; 2; 0; 3; 4; 0; 9]
和数字n = 0
,那么我希望将三个列表作为输出:[1;2]
,[3;4]
和{{1 }}。我遇到的主要问题是我不知道如何在[9]
上输出多个元素。我想我需要筑巢Fixpoint
,但我不知道怎么做。作为一个非常原始的想法,我有一个太多的问题:
Fixpoint
我非常感谢您对如何执行此操作的输入,以及如何导出具有多个元素的输出。
答案 0 :(得分:4)
您可以通过组合一些固定点来完成此操作:
Require Import Coq.Arith.Arith.
Require Import Coq.Lists.List.
Import ListNotations.
Fixpoint prefix n l :=
match l with
| [] => []
| m :: l' => if beq_nat n m then []
else m :: prefix n l'
end.
Fixpoint suffix n l :=
match l with
| [] => l
| m :: l' => if beq_nat n m then l'
else suffix n l'
end.
Fixpoint split_at n l :=
match l with
| [] => []
| m :: l' => prefix n (m :: l') :: split_at n (suffix n (m :: l'))
end.
请注意,Coq的终止检查程序接受对split_at
的递归调用,即使它在语法上不是l
的子项。原因是它能够检测到该后缀仅输出其参数的子类。但为了实现这一点,我们必须在其第一个分支上返回l
而不是[]
(尝试更改它以查看会发生什么!)。
答案 1 :(得分:3)
除了Arthur的解决方案,您还可以使用累加器,这是典型的函数式编程风格:
Require Import Coq.Arith.Arith.
Require Import Coq.Lists.List.
Import ListNotations.
Definition add_acc m (s : list (list nat)) :=
match s with
| [] => [[m]]
| s :: ss => (m :: s) :: ss
end.
Fixpoint split_seq n l acc :=
match l with
| [] => map (@rev _) (rev acc)
| m :: l' => if beq_nat n m then
split_seq n l' ([] :: acc)
else
split_seq n l' (add_acc m acc)
end.
Compute (split_seq 0 [1; 2; 0; 3; 4; 0; 9] []).
请注意,结果是相反的,因此您需要使用rev
。奖金练习是为了改善这一点。
编辑:提供了第二个变体,它不会为重复的分隔符添加[]
。
Definition reset_acc (s : list (list nat)) :=
match s with
| [] :: ss => [] :: ss
| ss => [] :: ss
end.
Fixpoint split_seq_nodup n l acc :=
match l with
| [] => map (@rev _) (rev acc)
| m :: l' => if beq_nat n m then
split_seq_nodup n l' (reset_acc acc)
else
split_seq_nodup n l' (add_acc m acc)
end.
Compute (split_seq_nodup 0 [1; 2; 0; 3; 4; 0; 9] []).
答案 2 :(得分:1)
解决此问题的另一种方法是正式描述您要解决的问题,然后编写一个依赖类型的函数,证明这个问题确实可以解决,或者使用策略来慢慢建立您的证明。
如果我没有弄错的话,这是一个关系,用于描述您希望传递函数的输出n
和ns
与想要返回的输出mss
之间的关系
(* ------- *)
行是简单的注释,用于表明这些构造函数应该被视为inference rules:根据上面的假设,可以根据一条这样的线条得出结论。
Inductive SubListsRel (n : nat) : forall (ns : list nat)
(mss : list (list nat)), Prop :=
| base : SubListsRel n nil (nil :: nil)
| consEq : forall ns m mss,
n = m -> SubListsRel n ns mss ->
(* ----------------------------- *)
SubListsRel n (m :: ns) (nil :: mss)
| consNotEq : forall ns m ms mss,
(n <> m) -> SubListsRel n ns (ms :: mss) ->
(* ------------------------------------------------- *)
SubListsRel n (m :: ns) ((m :: ms) :: mss)
.
我们可以将Sublists
问题表示为输入n
和ns
,输出mss
的存在使SubListsRel n ns mss
成立:
Definition SubLists (n : nat) (ns : list nat) : Set :=
{ mss | SubListsRel n ns mss }.
使用策略,我们可以轻松地为具体示例生成Sublists
,以便对我们的规范进行健全性检查。例如,我们可以采用原始帖子中的示例:
Example example1 : SubLists 0 (1 :: 2 :: 0 :: 3 :: 4 :: 0 :: 9 :: nil).
Proof.
eexists ; repeat econstructor ; intro Hf; inversion Hf.
Defined.
并检查输出确实是您期望的列表:
Check (eq_refl : proj1_sig example1
= ((1 :: 2 :: nil) :: (3 :: 4 :: nil) :: (9 :: nil) :: nil)).
现在是这篇文章的主要部分:证明forall n ns, SubLists n ns
。鉴于consNotEq
假设mss
非空,我们实际上会证明一个强化的陈述,以使我们的生活更轻松:
Definition Strenghtened_SubLists (n : nat) (ns : list nat) : Set :=
{ mss | SubListsRel n ns mss /\ mss <> nil }.
鉴于我们经常会有形状something_absurd -> False
的目标,我定义了一个简单的策略来处理这些事情。它引入了荒谬的假设并立即反转它以使目标消失:
Ltac dismiss := intro Hf; inversion Hf.
我们现在可以通过归纳和推导证明强化版本来证明主要陈述。我想在这里你可以更好地在Coq中介绍它而不是我试图解释会发生什么。关键步骤是cut
(证明更强烈的陈述),induction
以及eq_nat_dec
的案例分析。
Lemma subLists : forall n ns, SubLists n ns.
Proof
intros n ns; cut (Strenghtened_SubLists n ns).
- intros [mss [Hmss _]]; eexists; eassumption.
- induction ns.
+ eexists; split; [econstructor | dismiss].
+ destruct IHns as [mss [Hmss mssNotNil]];
destruct (eq_nat_dec n a).
* eexists; split; [eapply consEq ; eassumption| dismiss].
* destruct mss; [apply False_rect, mssNotNil; reflexivity |].
eexists; split; [eapply consNotEq; eassumption| dismiss].
Defined.
一旦我们有了这个功能,我们可以回到我们的示例并且这次生成适当的Sublists
不是通过调用策略而是通过运行我们刚刚定义的函数subLists
。
Example example2 : SubLists 0 (1 :: 2 :: 0 :: 3 :: 4 :: 0 :: 9 :: nil) :=
subLists _ _.
我们可以Check
计算出的列表与example1
中获得的列表确实相同:
Check (eq_refl : proj1_sig example1 = proj1_sig example2).
Nota Bene :最重要的是,我们的校样以Defined
而不是Qed
结束,以便在使用它们进行计算时展开它们(这是什么我们想在这里做:他们给我们正在寻找的list (list nat)
。)。
A gist包含所有代码和正确的导入。
答案 3 :(得分:0)
这是基于标准库函数List.fold_left
的另一种观点。
它的工作原理是维护一个累加器,它是一对整体反向结果(列表列表)和一个当前子列表(在累积时也会反转)。一旦我们到达分隔符,我们就会反转当前的子列表并将其放入生成的子列表列表中。执行fold_left
后,我们在最外面的match
表达式中反转结果。
Require Import Coq.Arith.Arith.
Require Import Coq.Lists.List.
Import ListNotations.
Definition split_skip_dup_delims (m : nat) (xs : list nat) :=
match fold_left
(fun (acctup: _ * _) x => let (acc, rev_subl) := acctup in
if beq_nat x m
then match rev_subl with (* a delimiter found *)
| [] => (acc, []) (* do not insert empty sublist *)
| _ => (rev rev_subl :: acc, []) end
else (acc, x :: rev_subl)) (* keep adding to the current sublist *)
xs
([],[]) with
| (acc, []) => rev acc (* list ends with a delimiter *)
| (acc, rev_subl) => rev (rev rev_subl :: acc) (* no delimiter at the end *)
end.
Eval compute in split_skip_dup_delims 0 [1; 2; 0; 0; 0; 3; 4; 0; 9].
(* = [[1; 2]; [3; 4]; [9]]
: list (list nat) *)