Theory PQ
section ‹Abstract priority queues›
theory PQ
imports Main
begin
subsection ‹Generic Lemmas›
lemma tl_set:
  "distinct q ⟹ set (tl q) = set q - {hd q}"
  by (cases q) simp_all
subsection ‹Type of abstract priority queues›
typedef (overloaded) ('a, 'b::linorder) pq =
  "{xs :: ('a × 'b) list. distinct (map fst xs) ∧ sorted (map snd xs)}"
  morphisms alist_of Abs_pq
proof -
  have "[] ∈ ?pq" by simp
  then show ?thesis by blast
qed
lemma alist_of_Abs_pq:
  assumes "distinct (map fst xs)"
    and "sorted (map snd xs)"
  shows "alist_of (Abs_pq xs) = xs"
  by (rule Abs_pq_inverse) (simp add: assms)
lemma [code abstype]:
  "Abs_pq (alist_of q) = q"
  by (fact alist_of_inverse)
lemma distinct_fst_alist_of [simp]:
  "distinct (map fst (alist_of q))"
  using alist_of [of q] by simp
lemma distinct_alist_of [simp]:
  "distinct (alist_of q)"
  using distinct_fst_alist_of [of q] by (simp add: distinct_map)
lemma sorted_snd_alist_of [simp]:
  "sorted (map snd (alist_of q))"
  using alist_of [of q] by simp
lemma alist_of_eqI:
  "alist_of p = alist_of q ⟹ p = q"
proof -
  assume "alist_of p = alist_of q"
  then have "Abs_pq (alist_of p) = Abs_pq (alist_of q)" by simp
  thus "p = q" by (simp add: alist_of_inverse)
qed
definition "values" :: "('a, 'b::linorder) pq ⇒ 'a list" (‹|(_)|›) where
  "values q = map fst (alist_of q)"
definition priorities :: "('a, 'b::linorder) pq ⇒ 'b list" (‹∥(_)∥›) where
  "priorities q = map snd (alist_of q)"
lemma values_set:
  "set |q| = fst ` set (alist_of q)"
  by (simp add: values_def)
lemma priorities_set:
  "set ∥q∥ = snd ` set (alist_of q)"
  by (simp add: priorities_def)
definition is_empty :: "('a, 'b::linorder) pq ⇒ bool" where
  "is_empty q ⟷ alist_of q = []"
definition priority :: "('a, 'b::linorder) pq ⇒ 'a ⇒ 'b option" where
  "priority q = map_of (alist_of q)"
definition min :: "('a, 'b::linorder) pq ⇒ 'a" where
  "min q = fst (hd (alist_of q))"
definition empty :: "('a, 'b::linorder) pq" where 
  "empty = Abs_pq []"
lemma is_empty_alist_of [dest]:
  "is_empty q ⟹ alist_of q = []"
  by (simp add: is_empty_def)
lemma not_is_empty_alist_of [dest]:
  "¬ is_empty q ⟹ alist_of q ≠ []"
  by (simp add: is_empty_def)
lemma alist_of_empty [simp, code abstract]:
  "alist_of empty = []"
  by (simp add: empty_def Abs_pq_inverse)
lemma values_empty [simp]:
  "|empty| = []"
  by (simp add: values_def)
lemma priorities_empty [simp]:
  "∥empty∥ = []"
  by (simp add: priorities_def)
lemma values_empty_nothing [simp]:
  "∀k. k ∉ set |empty|"
  by (simp add: values_def)
lemma is_empty_empty:
  "is_empty q ⟷ q = empty"
proof (rule iffI)
  assume "is_empty q"
  then have "alist_of q = []" by (simp add: is_empty_alist_of)
  then have "Abs_pq (alist_of q) = Abs_pq []" by simp
  then show "q = empty" by (simp add: empty_def alist_of_inverse)
qed (simp add: is_empty_def)
lemma is_empty_empty_simp [simp]:
  "is_empty empty"
by (simp add: is_empty_empty)
lemma map_snd_alist_of:
  "map (the ∘ priority q) (values q) = map snd (alist_of q)"
  by (auto simp add: values_def priority_def)
lemma image_snd_alist_of:
  "the ` priority q ` set (values q) = snd ` set (alist_of q)"
proof -
  from map_snd_alist_of [of q]
    have "set (map (the ∘ priority q) (values q)) = set (map snd (alist_of q))"
      by (simp only:)
  then show ?thesis by (simp add: image_comp)
qed
lemma Min_snd_alist_of:
  assumes "¬ is_empty q"
  shows "Min (snd ` set (alist_of q)) = snd (hd (alist_of q))"
proof -
  from assms obtain ps p where q: "map snd (alist_of q) = p # ps"
    by (cases "map snd (alist_of q)") auto
  then have "hd (map snd (alist_of q)) = p" by simp
  with assms have p: "snd (hd (alist_of q)) = p" by (auto simp add: hd_map)
  have "sorted (map snd (alist_of q))" by simp
  with q have "sorted (p # ps)" by simp
  then have "∀p'∈set ps. p' ≥ p" by (simp)
  then have "Min (set (p # ps)) = p" by (auto intro: Min_eqI)
  with p q have "Min (set (map snd (alist_of q))) = snd (hd (alist_of q))"
    by simp
  then show ?thesis by simp
qed
lemma priority_fst:
  assumes "xp ∈ set (alist_of q)"
  shows "priority q (fst xp) = Some (snd xp)"
  using assms by (simp add: priority_def)
lemma priority_Min:
  assumes "¬ is_empty q"
  shows "priority q (min q) = Some (Min (the ` priority q ` set (values q)))"
  using assms
    by (auto simp add: min_def image_snd_alist_of Min_snd_alist_of priority_fst)
lemma priority_Min_priorities:
  assumes "¬ is_empty q"
  shows "priority q (min q) = Some (Min (set ∥q∥))"
  using assms
    by (simp add: priority_Min image_snd_alist_of priorities_def)
definition push :: "'a ⇒ 'b::linorder ⇒ ('a, 'b) pq ⇒ ('a, 'b) pq" where
  "push k p q = Abs_pq (if k ∉ set (values q)
           then insort_key snd (k, p) (alist_of q)
           else alist_of q)"
lemma Min_snd_hd:
  "q ≠ [] ⟹ sorted (map snd q) ⟹ Min (snd ` set q) = snd (hd q)" 
proof (induct q)
  case (Cons x xs) then show ?case by (cases xs) (auto simp add: ord_class.min_def)
qed simp
lemma hd_construct:
  assumes "¬ is_empty q"
  shows "hd (alist_of q) = (min q, the (priority q (min q)))"
proof -
  from assms have "the (priority q (min q)) = snd (hd (alist_of q))"
    using Min_snd_hd [of "alist_of q"]
      by (auto simp add: priority_Min_priorities priorities_def)
  then show ?thesis by (simp add: min_def)
qed
lemma not_in_first_image:
  "x ∉ fst ` s ⟹ (x, p) ∉ s"
  by (auto simp add: image_def)
lemma alist_of_push [simp, code abstract]:
  "alist_of (push k p q) =
    (if k ∉ set (values q) then insort_key snd (k, p) (alist_of q) else alist_of q)"
  using distinct_fst_alist_of [of q]
    by (auto simp add: distinct_map set_insort_key distinct_insort not_in_first_image
      push_def values_def sorted_insort_key intro: alist_of_Abs_pq)
lemma push_values [simp]:
  "set |push k p q| = set |q| ∪ {k}"
  by (auto simp add: values_def set_insort_key)
lemma push_priorities [simp]:
  "k ∉ set |q| ⟹ set ∥push k p q∥ = set ∥q∥ ∪ {p}"
  "k ∈ set |q| ⟹ set ∥push k p q∥ = set ∥q∥"
  by (auto simp add: priorities_def set_insort_key)
lemma not_is_empty_push [simp]:
  "¬ is_empty (push k p q)"
  by (auto simp add: values_def is_empty_def)
lemma push_commute:
  assumes "a ≠ b" and "v ≠ w"
  shows "push w b (push v a q) = push v a (push w b q)"
  using assms by (auto intro!: alist_of_eqI insort_key_left_comm)
definition remove_min :: "('a, 'b::linorder) pq ⇒ ('a, 'b::linorder) pq" where
  "remove_min q = (if is_empty q then empty else Abs_pq (tl (alist_of q)))"
lemma alift_of_remove_min_if [code abstract]:
  "alist_of (remove_min q) = (if is_empty q then [] else tl (alist_of q))"
  by (auto simp add: remove_min_def map_tl sorted_tl distinct_tl alist_of_Abs_pq)
lemma remove_min_empty [simp]:
  "is_empty q ⟹ remove_min q = empty"
  by (simp add: remove_min_def)
lemma alist_of_remove_min [simp]:
  "¬ is_empty q ⟹ alist_of (remove_min q) = tl (alist_of q)"
  by (simp add: alift_of_remove_min_if)
lemma values_remove_min [simp]:
  "¬ is_empty q ⟹ values (remove_min q) = tl (values q)"
  by (simp add: values_def map_tl)
lemma set_alist_of_remove_min:
  "¬ is_empty q ⟹ set (alist_of (remove_min q)) =
    set (alist_of q) - {(min q, the (priority q (min q)))}"
  by (simp add: tl_set hd_construct)
definition pop :: "('a, 'b::linorder) pq ⇒ ('a × ('a, 'b) pq) option" where
  "pop q = (if is_empty q then None else Some (min q, remove_min q))"
lemma pop_simps [simp]:
  "is_empty q ⟹ pop q = None"
  "¬ is_empty q ⟹ pop q = Some (min q, remove_min q)"
  by (simp_all add: pop_def)
hide_const (open) Abs_pq alist_of "values" priority empty is_empty push min pop
no_notation
  "PQ.values" (‹|(_)|›)
  and "PQ.priorities" (‹∥(_)∥›)
end