Zulip Chat Archive

Stream: new members

Topic: Lynn's has_duplicate exercise


Lynn (Apr 07 2020 at 17:04):

Hey, I decided on a neat exercise for myself and figured I'd make a topic for all the questions I have along the way.

I defined some types:

import data.fin
import data.list
import data.vector
open fin
open vector

def duplicate {α:Type} {n:} (v:vector α n) (i:fin n) (j:fin n) : Prop := i  j  nth v i = nth v j
def has_duplicate {α:Type} {n:} (v:vector α n) := (i j:fin n), duplicate v i j

The exercise, for now, is: I want to prove that has_duplicate is a decidable property of vectors.

So, I started writing an instance for the decidable class. Here's my proof so far.

instance dec_duplicate {α:Type} {n:} (v:vector α n) : decidable (has_duplicate v) :=
begin
  -- I'll perform induction on the list inside the vector.
  cases v,
  cases v_val,

  -- Empty list case: there is no duplicate because i cannot even exist.
  suffices f : (¬ has_duplicate list.nil, v_property ), from (is_false f),
  intro p,
  cases p with i p,
  simp at v_property,
  rw v_property at i,
  exact i.elim0,

  -- TODO: recursive case:
  --   1. If head ∈ tail, there's a duplicate.
  --   2. Otherwise, decide if there's a duplicate in the tail.
  --     a. If there is, show it is also in this list.
  --     b. If not, show that head ∉ tail means the full list has no duplicate either.
end

This brings me to my first question:

For step 2, I figured I'll need to write something like let foo := dec_duplicate (tail v). But I can't actually use dec_duplicate like that; I guess instance names can't be used like that.

What should I write instead, to recurse and decide if the tail has a duplicate, and split on the is_false and is_true cases?

I found examples of expressions using decidable.cases_on (or is it rec_on?) but I don't know how to make it useful for my tactic proof.

Johan Commelin (Apr 07 2020 at 17:16):

Lynn said:

Hey, I decided on a neat exercise for myself and figured I'd make a topic for all the questions I have along the way.

That's a good idea! It seems to be one of the best ways to learn Lean.

Kevin Buzzard (Apr 07 2020 at 17:16):

This is definitely not my area of expertise, but I think you will need to use induction and not cases on your list.

Kevin Buzzard (Apr 07 2020 at 17:17):

begin
  -- I'll perform induction on the list inside the vector.
  cases v with L hL,
  induction L with a M h,

  { -- Empty list case: there is no duplicate because i cannot even exist.
    suffices f : (¬ has_duplicate list.nil, hL ), from (is_false f),
    intro p,
    cases p with i p,
    simp at hL,
    rw hL at i,
    exact i.elim0},

  { ... }
end

Kevin Buzzard (Apr 07 2020 at 17:18):

But actually I suspect that what you're trying to prove isn't true. If L is a list of length 2 then deciding whether L has duplicates is the same as deciding equality on alpha. So I think you'll need to add [decidable_eq \alpha]

Kevin Buzzard (Apr 07 2020 at 17:19):

No this won't work either because your inductive hypothesis is useless the way I suggested.

Kevin Buzzard (Apr 07 2020 at 17:21):

import data.fin
import data.list
import data.vector
open fin
open vector

def duplicate {α:Type} [decidable_eq α] {n:} (v:vector α n) (i:fin n) (j:fin n) : Prop := i  j  nth v i = nth v j
def has_duplicate {α:Type} [decidable_eq α] {n:} (v:vector α n) := (i j:fin n), duplicate v i j
instance dec_duplicate {α:Type} [decidable_eq α] {n:} (v:vector α n) : decidable (has_duplicate v) :=
begin
  -- I'll perform induction on the list inside the vector.
  cases v with L hL,
  revert n,
  induction L with a M h,

  { -- Empty list case: there is no duplicate because i cannot even exist.
    intros n hL,
    suffices f : (¬ has_duplicate list.nil, hL ), from (is_false f),
    intro p,
    cases p with i p,
    simp at hL,
    rw hL at i,
    exact i.elim0},

  -- TODO: recursive case:
  --   1. If head ∈ tail, there's a duplicate.
  --   2. Otherwise, decide if there's a duplicate in the tail.
  --     a. If there is, show it is also in this list.
  --     b. If not, show that head ∉ tail means the full list has no duplicate either.
end

This looks more promising

Kevin Buzzard (Apr 07 2020 at 17:26):

So now h is your dec_duplicate (tail v). Actually you might want to write replace h := h rfl to get the actual instance. decidable.cases_on is just cases again. You can do cases h to split into the two cases of a duplicate or not.

Lynn (Apr 07 2020 at 18:39):

Thank you! How would I split into cases on some other “decision”? For example for step 1 now I want to do something like cases (list.mem a M), but that's apparently not it.

Lynn (Apr 07 2020 at 18:40):

revert n, is an interesting trick.

Kevin Buzzard (Apr 07 2020 at 18:42):

Really n is a red herring. You can just do everything with lists rather than vectors. If you don't revert n then you end up with an induction hypothesis saying "if the length of L is n then [blah]" and then another hypothesis saying "the length of L isn't n", which is what makes the induction hypo useless. The only reason you can't get rid of n completely is that you have these fin n things in the defnition.

Kevin Buzzard (Apr 07 2020 at 18:45):

Well, I'm a mathematician so I would just write classical and then by_cases h : a \in M but then again if I'm assuming classical then I can just prove the entire theorem by the law of the excluded middle. You can case on an inductive type but I think that if you're being constructive then you can't case on a Prop.

Kevin Buzzard (Apr 07 2020 at 18:46):

To be quite frank I don't even know if what you're doing is possible -- I don't ever think about this kind of question.

Kevin Buzzard (Apr 07 2020 at 18:47):

Maybe first you should prove a \in M is decidable? Then you'll be able to do cases on it.

Lynn (Apr 07 2020 at 18:47):

mem has a "decidable" instance in https://github.com/leanprover-community/lean/blob/master/library/init/data/list/basic.lean

Kevin Buzzard (Apr 07 2020 at 18:48):

oh that's great, then you should definitely be able to case on it. It's just that I would always use LEM at this point because I am uncultured :-)

Lynn (Apr 07 2020 at 18:48):

I think I just syntactically don't know how to make use of that fact. cases (list.mem a M) gives me cases tactic failed, it is not applicable to the given hypothesis

Lynn (Apr 07 2020 at 18:48):

I guess cases should be followed by the name of a hypothesis and not an expression

Lynn (Apr 07 2020 at 18:49):

and, ha, I like to be constructive when I can so I'm gonna try to stick to it here :slight_smile:

Kevin Buzzard (Apr 07 2020 at 18:50):

  have haM : decidable (a  M) := by apply_instance,
  cases haM with haMf haMt,

works but there will be a more elegant way of doing it I'm sure. You need someone who actually has a clue about constructive stuff.

Kevin Buzzard (Apr 07 2020 at 18:51):

Oh -- by_cases haM : a ∈ M, works fine.

Lynn (Apr 07 2020 at 18:53):

awesome, that tactic is exactly what I was looking for!

Lynn (Apr 07 2020 at 19:34):

maybe it'll be easier to work with something like this?

def duplicate {α:Type} [decidable_eq α] (l:list α) (i:) (j:) {il:i<l.length} {jl:j<l.length} : Prop := i  j  (nth_le l i il = nth_le l j jl)

Kevin Buzzard (Apr 07 2020 at 19:35):

That would be my gut feeling (but my gut is not good at this stuff). Now you won't have to do the revert n dance.

Kevin Buzzard (Apr 07 2020 at 19:36):

There is still some way to go however -- this is a neat little puzzle.

Lynn (Apr 07 2020 at 19:36):

I'm a little surprised lean can't make placeholders in

def has_duplicate {α:Type} [decidable_eq α] (l:list α) := (i j:), (il:i<l.length), (jl:j<l.length), duplicate l i j

image.png

Yury G. Kudryashov (Apr 07 2020 at 19:36):

You don't need decidable_eq to define this Prop

Yury G. Kudryashov (Apr 07 2020 at 19:37):

Probably you should make il and jl explicit.

Kevin Buzzard (Apr 07 2020 at 19:40):

If you're using the old web editor, this one is better (it has some bugfixes)

Kevin Buzzard (Apr 07 2020 at 19:40):

You'll need decidable_eq to prove anything about it but you don't need it for the prop itself (my bad)

Kevin Buzzard (Apr 07 2020 at 19:41):

As for the placeholders, it all depends on which bit of Lean is being asked to fill them.

Lynn (Apr 07 2020 at 21:29):

What's cong : {f : t -> u} -> (a = b) -> (f a = f b) called in Lean?

Kenny Lau (Apr 07 2020 at 21:36):

congr_arg

Yury G. Kudryashov (Apr 07 2020 at 21:38):

And f is an explicit argument of congr_arg.

Lynn (Apr 07 2020 at 21:40):

hooray, I did it! https://gist.github.com/lynn/18aafb4f04b5e9289693e8a58bec532f

Lynn (Apr 07 2020 at 21:46):

It's a bit of a mess. I'll think about how to clean it up and what I struggled with. If anything looks totally silly about the way I used (or didn't use) my tactics then let me know. :slight_smile:

One thing I wondered is: sometimes I want to treat the hole that existsi is going to fill as a subgoal, and apply tactics to it. I used have temp : write_out_the_whole_type_here, (tactics for the goal), existsi temp a few times. Is there a better way (ideally one that doesn't make me write out the type)?

Lynn (Apr 07 2020 at 21:46):

another is: is there a better way to get all the variables out of a ∃(i:ℕ)(il:i<l.length) (j:ℕ)(jl:j<l.length), … hypothesis than my

  cases p with i p,
  cases p with il p,
  cases p with j p,
  cases p with jl p,

Marc Huisinga (Apr 07 2020 at 21:50):

you might find the mathlib tactic docs interesting, e.g. https://leanprover-community.github.io/mathlib_docs/tactics.html#rcases

Marc Huisinga (Apr 07 2020 at 21:55):

for your existsi question, i sometimes use refine, e.g.

example :  n : , true :=
begin
  refine ⟨_, _⟩,
  -- two goals!
end

Mario Carneiro (Apr 07 2020 at 22:05):

The way I would prove that this predicate is decidable is by proving it is equivalent to (the negation of)list.pairwise (\ne)

Kevin Buzzard (Apr 07 2020 at 22:55):

rcases p with ⟨i, i1, j, j1⟩


Last updated: Dec 20 2023 at 11:08 UTC