如何在Coq中制作子列表?

时间:2016-04-27 17:01:12

标签: coq

我在Coq工作并试图弄清楚如何做下一件事:如果我有一个自然数字列表和给定数字n,我想打破我的列表每个n之前和之后。为了更清楚,如果我有列表[1; 2; 0; 3; 4; 0; 9]和数字n = 0,那么我希望将三个列表作为输出:[1;2][3;4]和{{1 }}。我遇到的主要问题是我不知道如何在[9]上输出多个元素。我想我需要筑巢Fixpoint,但我不知道怎么做。作为一个非常原始的想法,我有一个太多的问题:

Fixpoint

我非常感谢您对如何执行此操作的输入,以及如何导出具有多个元素的输出。

4 个答案:

答案 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)

解决此问题的另一种方法是正式描述您要解决的问题,然后编写一个依赖类型的函数,证明这个问题确实可以解决,或者使用策略来慢慢建立您的证明。

如果我没有弄错的话,这是一个关系,用于描述您希望传递函数的输出nns与想要返回的输出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问题表示为输入nns,输出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) *)