如何在coq中将列表一分为二?

如何在coq中将列表一分为二?,coq,Coq,它看起来绝对是一项简单的任务,直到我真正尝试去做它。我的方法是使用双指针来避免提前询问列表的长度,但困难来自于我确信一个列表不会比另一个列表“更空”。具体而言,在伪coq中: Definition twin_ptr (heads, tail, rem : list nat) := match tail, rem with | _, [] => (rev heads, tail) | _, [_] => (rev heads, tail) | t :: tl, _ ::

它看起来绝对是一项简单的任务,直到我真正尝试去做它。我的方法是使用双指针来避免提前询问列表的长度,但困难来自于我确信一个列表不会比另一个列表“更空”。具体而言,在伪coq中:

Definition twin_ptr (heads, tail, rem : list nat) :=
  match tail, rem with
  | _, [] => (rev heads, tail)
  | _, [_] => (rev heads, tail)
  | t :: tl, _ :: _ :: rm => twin_ptr (t :: heads) tl rm
  end.

Definition split (l : list nat) := twin_ptr [] l l
但它肯定不会编译,因为匹配案例不完整。然而,建筑方面的缺失案例并不存在


您的实现方式是什么?

您不需要保持第二个列表大于第三个列表的不变量。以下是一个可能的解决方案:

Require Import Coq.Arith.PeanoNat.
Require Import Coq.Arith.Div2.
Require Import Coq.Lists.List.
Import ListNotations.

Section Split.

Variable A : Type.

Fixpoint split_aux (hs ts l : list A) {struct l} : list A * list A :=
  match l with
  | []  => (rev hs, ts)
  | [_] => (rev hs, ts)
  | _ :: _ :: l' =>
    match ts with
    | []      => (rev hs, [])
    | h :: ts => split_aux (h :: hs) ts l'
    end
  end.

Lemma split_aux_spec hs ts l n :
  n = div2 (length l) ->
  split_aux hs ts l = (rev (rev (firstn n ts) ++ hs), skipn n ts).
Proof.
revert hs ts l.
induction n as [|n IH].
- intros hs ts [|x [|y l]]; easy.
- intros hs ts [|x [|y l]]; simpl; try easy.
  intros Hn.
  destruct ts as [|h ts]; try easy.
  rewrite IH; try congruence.
  now simpl; rewrite <- app_assoc.
Qed.

Definition split l := split_aux [] l l.

Lemma split_spec l :
  split l = (firstn (div2 (length l)) l, skipn (div2 (length l)) l).
Proof.
unfold split.
rewrite (split_aux_spec [] l l (div2 (length l))); trivial.
now rewrite app_nil_r, rev_involutive.
Qed.

End Split.
需要导入Coq.Arith.PeanoNat。
需要输入Coq.Arith.Div2。
需要导入Coq.Lists.List。
导入列表符号。
分段拆分。
变量A:类型。
不动点拆分_aux(hs ts l:list A){struct l}:list A*list A:=
匹配
|[]=>(修订版hs,ts)
|[\u]=>(修订版hs,ts)
|_::::l'=>
搭配
|[]=>(修订版hs,[])
|h::ts=>分割辅助(h::hs)ts l'
结束
结束。
引理拆分辅助规格规格长度:
n=div2(长度l)->
拆分辅助序列l=(版本(版本(第一个序列)++hs),跳过序列)。
证明。
回复hs ts l。
诱导n为[| n IH]。
-介绍hs ts[|x[|y l];容易的。
-介绍hs ts[|x[|y l];单纯形;别着急。
介绍Hn。
将ts分解为[| h ts];别着急。
重写IH;尝试一致性。

现在简单;重写I如果你不怕依赖类型,你可以添加一个证明,证明
rem
tail
短,作为
twin_ptr
的参数。使用
Program
帮助管理这些依赖类型,这可能会产生以下结果

Require Import List. Import ListNotations.
Require Import Program.
Require Import Arith.
Require Import Omega.

Program Fixpoint twin_ptr
  (heads tail rem : list nat)
  (H:List.length rem <= List.length tail) :=
  match tail, rem with
  | a1, [] => (rev heads, tail)
  | a2, [a3] => (rev heads, tail)
  | t :: tl, _ :: _ :: rm => twin_ptr (t :: heads) tl rm _
  | [], _::_::_ => !
  end.
Next Obligation.
  simpl in H. omega.
Qed.
Next Obligation.
  simpl in H. omega.
Qed.

Definition split (l : list nat) := twin_ptr [] l l (le_n _).

就个人而言,我不喜欢在函数中使用依赖类型,因为生成的对象更难操作。相反,我更喜欢定义总函数,并在引理中给出正确的假设。

我可以建议使用更精确的类型吗?主要思想是定义一个函数,将一个
向量.t
拆分为一个
向量,其
nat
索引的形状为
m+n
,大小为
m
,大小为
n

Require Import Vector.

Definition split_vector : forall a m n,
  Vector.t a (m + n) -> (Vector.t a m * Vector.t a n).
Proof.
intros a m n; induction m; intro v.
- firstorder; constructor.
- destruct (IHm (tl v)) as [xs ys].
  firstorder; constructor; [exact (hd v)|assumption].
Defined.
Fixpoint div2_floor_ceil (n : nat) : (nat * nat) := match n with
  | O        => (O , O)
  | S O      => (O , S O)
  | S (S n') => let (p , q) := div2_floor_ceil n'
                in (S p, S q)
end.

Definition div2_floor (n : nat) := fst (div2_floor_ceil n).
Definition div2_ceil  (n : nat) := snd (div2_floor_ceil n).

Lemma plus_div2_floor_ceil : forall n, div2_floor n + div2_ceil n = n.
Proof.
refine
  (fix ih n := match n with
     | O => _
     | S O => _
     | S (S n') => _
   end); try reflexivity.
unfold div2_floor, div2_ceil in *; simpl.
destruct (div2_floor_ceil n') as [p q] eqn: eq.
simpl.
replace p with (div2_floor n') by (unfold div2_floor ; rewrite eq ; auto).
replace q with (div2_ceil n') by (unfold div2_ceil ; rewrite eq ; auto).
rewrite <- plus_n_Sm; do 2 f_equal.
apply ih.
Qed.
一旦你有了这一点,你就把你的问题简化为定义
n/2
floor
ceil
,并证明它们的总和是
n

Require Import Vector.

Definition split_vector : forall a m n,
  Vector.t a (m + n) -> (Vector.t a m * Vector.t a n).
Proof.
intros a m n; induction m; intro v.
- firstorder; constructor.
- destruct (IHm (tl v)) as [xs ys].
  firstorder; constructor; [exact (hd v)|assumption].
Defined.
Fixpoint div2_floor_ceil (n : nat) : (nat * nat) := match n with
  | O        => (O , O)
  | S O      => (O , S O)
  | S (S n') => let (p , q) := div2_floor_ceil n'
                in (S p, S q)
end.

Definition div2_floor (n : nat) := fst (div2_floor_ceil n).
Definition div2_ceil  (n : nat) := snd (div2_floor_ceil n).

Lemma plus_div2_floor_ceil : forall n, div2_floor n + div2_ceil n = n.
Proof.
refine
  (fix ih n := match n with
     | O => _
     | S O => _
     | S (S n') => _
   end); try reflexivity.
unfold div2_floor, div2_ceil in *; simpl.
destruct (div2_floor_ceil n') as [p q] eqn: eq.
simpl.
replace p with (div2_floor n') by (unfold div2_floor ; rewrite eq ; auto).
replace q with (div2_ceil n') by (unfold div2_ceil ; rewrite eq ; auto).
rewrite <- plus_n_Sm; do 2 f_equal.
apply ih.
Qed.

对于不可能的情况,为什么不返回
([],[])
?这可能是可行的,但我想知道coq是如何执行这种契约的。实际上,有一个很好的理由不转储随机结果,因为它可能会使依赖此函数的证明更加困难。实际上,单独考虑
twin_ptr
,这种情况并非不可能。只有在
split
中使用它时,这种情况才变得不可能。所以你的实现没有太多的选择。@AntonTrunov让我们改变我的措辞:我如何强制执行
rem
始终与
tail
保持正确的关系,以便它可以插入
split
?如果“双指针”不是拆分列表的好方法,那么您对如何更好地在coq中实现它有什么建议吗?我不确定这是否是coq中的正确方法。在许多算法中,我们必须将事情分解成若干个案例,其中一些要比另一个复杂得多。对于这些情况,我倾向于将它们提取为它们自己的函数。当陷入为特定输入设计的特定例程时,对一般情况的含义进行推理将是非常枯燥的。如果可以避免这样的问题,我们可以在类型中编码依赖类型。要么你试图给你写的函数一个一般意义,只考虑一个特殊情况,我同意这可能有点做作。或者你只是在引理中加入假设,如果你使用依赖类型,你会在函数中加入这些假设,而函数返回的无意义的值将永远不会出现。