(* Some notation for array lookups *)
Notation "f '#' i " := (arrayLookup _ f i) (at level 70) : array_scope.(* A special case for array lookup when the range is lists -- returns nil instead of None when not found.  This is useful for multidimensional arrays. *)
Definition arrayLookupList(A B : Set)(eqd : EqDec A)(ls : list (A * (list B)))(a : A) :=
    match (arrayLookup _ ls a) with
      | None => nil
      | Some ls => ls
    end.Fixpoint arrayLookupOpt(A B : Set)(eqd : EqDec A)(ls : list (option (A * B)))(a : A) :=
  match ls with
    | nil => None
      | o :: ls' =>
        match o with
          | None => arrayLookupOpt _ ls' a
          | Some (a', b') =>
            if (eqb a a') then (Some b') else (arrayLookupOpt _ ls' a)
        end
  end.

(* Specifications: *)
Theorem list_pred_arrayLookupOpt : forall (A B C : Set)(P : B -> C -> Prop)(eqda : EqDec A)(ls1 : list (option (A * B)))(ls2 : list (option (A * C))), list_pred (fun o1 o2 => match o1 with | None => o2 = None | Some x => match o2 with | None => False | Some y => fst x = fst y /\ P (snd x) (snd y) end end) ls1 ls2 -> forall a, match (arrayLookupOpt _ ls1 a) with | None => (arrayLookupOpt _ ls2 a) = None | Some x => exists y, (arrayLookupOpt _ ls2 a) = Some y /\ P x y end.
Theorem arrayLookupOpt_getSomes_eq : forall (A B : Set)(eqda : EqDec A)(ls : list (option (A * B)))(a : A), arrayLookupOpt _ ls a = arrayLookup _ (getSomes ls) a.
Theorem arrayLookup_app_Some : forall (A B : Set)(eqd : EqDec A)(ls1 ls2 : list (A * B))(a : A)(b : B), arrayLookup eqd ls1 a = Some b -> arrayLookup eqd (ls1 ++ ls2) a = Some b.
Theorem arrayLookup_app_None : forall (A B : Set)(eqd : EqDec A)(ls1 ls2 : list (A * B))(a : A), arrayLookup eqd ls1 a = None -> arrayLookup eqd (ls1 ++ ls2) a = arrayLookup eqd ls2 a.
Theorem arrayLookupList_pred : forall (A B : Set) (eqda : EqDec A)P (ls : list (A * list B)) a, (forall x, In x ls -> P (snd x)) -> P nil -> P (arrayLookupList _ ls a).
Theorem arrayLookup_Some_impl_In : forall (A B : Set)(eqd : EqDec A)(ls : list (A * B)) a b, arrayLookup _ ls a = Some b -> In (a, b) ls.
Theorem list_pred_impl_arrayLookup : forall (A B C : Set) P (eqda : EqDec A)(ls1 : list (A * B))(ls2 : list (A * C)), list_pred (fun x y => fst x = fst y /\ P (snd x) (snd y)) ls1 ls2 -> (forall v z, arrayLookup _ ls1 v = Some z -> exists z', arrayLookup _ ls2 v = Some z' /\ P z z') /\ (forall v, arrayLookup _ ls1 v = None -> arrayLookup _ ls2 v = None).
Theorem arrayLookup_In_NoDup : forall (A B : Set)(eqda : EqDec A)(ls : list (A * B)) a b, In (a, b) ls -> NoDup (fst (split ls)) -> arrayLookup _ ls a = Some b.
Theorem arrayLookup_None_not_In : forall (A B : Set)(eqda : EqDec A)(ls : list (A * B)) a, arrayLookup _ ls a = None -> In a (fst (split ls)) -> False.
Theorem arrayLookup_pair_None : forall (A B C : Set)(eqda : EqDec A)(eqdb : EqDec B) (ls : list (A * B * C)) a b, (forall x, In x ls -> fst (fst x) <> a) -> arrayLookup _ ls (a, b) = None.
Theorem arrayLookup_pair_flatten_none : forall (A B C : Set)(eqda : EqDec A)(eqdb : EqDec B) keys (ls : list (list (A * B * C))) a b, list_pred (fun a b => forall x, In x b -> fst (fst x) = a) keys ls -> (~ In a keys) -> arrayLookup (pair_EqDec eqda eqdb) (flatten ls) (a, b) = None.
Theorem arrayLookup_flatten_eq : forall (A B C : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls : list (list (A * B * C))) keys a b, list_pred (fun a b => forall x, In x b -> fst (fst x) = a) keys ls -> NoDup keys -> arrayLookup _ (flatten ls) (a, b) = arrayLookup _ (arrayLookupList _ (combine keys ls) a) (a, b).
Theorem arrayLookup_pair_snd : forall (A B C : Set)(eqda : EqDec A)(eqdb : EqDec B)(ls1 : list (A * B * C))(ls2 : list (B * C)) a b, list_pred (fun x y => fst (fst x) = a /\ snd (fst x) = fst y /\ snd x = snd y) ls1 ls2 -> arrayLookup _ ls1 (a, b) = arrayLookup _ ls2 b.
Theorem arrayLookup_pred_2 : forall (A B C: Set)(eqda : EqDec A) (P : list B -> list C -> Prop) (ls1 : list (A * list B))(ls2 : list (A * list C)) a, list_pred (fun x y => (fst x = a \/ fst y = a) -> (fst x = a /\ fst y = a /\ P (snd x) (snd y))) ls1 ls2 -> P nil nil -> P (arrayLookupList _ ls1 a) (arrayLookupList _ ls2 a).
Theorem In_arrayLookupList : forall (A B : Set)(eqda : EqDec A)(a : A)(b : B) ls, In b (arrayLookupList _ ls a) -> exists ls', In (a, ls') ls /\ In b ls'.
Theorem arrayLookupList_pred' : forall (A B : Set) (eqda : EqDec A) (P : list B -> Type) (ls : list (A * list B)) (a : A), (forall x : list B, In (a, x) ls -> P x) -> (arrayLookup _ ls a = None -> P nil) -> P (arrayLookupList eqda ls a).
Theorem arrayLookup_allNats_eq : forall (B : Set)(ls : list (nat * B)) i, fst (split ls) = allNatsLt (length ls) -> arrayLookup _ ls i = match nth_option ls i with | Some (_, v) => Some v | None => None end.