blob: 3f72892965b6f2c135f8b0c3a066ea3612e4c90b [file]
/-
Copyright (c) 2025 Robin Arnez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robin Arnez
-/
prelude
import Std.Data.DHashMap.Lemmas
/-!
# Extensional dependent hash maps
This file develops the type `Std.ExtDHashMap` of extensional dependent hash maps.
Lemmas about the operations on `Std.ExtDHashMap` are available in the
module `Std.Data.ExtDHashMap.Lemmas`.
-/
set_option linter.missingDocs true
set_option autoImplicit false
attribute [local instance] Std.DHashMap.isSetoid
universe u v w
variable {α : Type u} {β : α → Type v} {γ : α → Type w}
variable {_ : BEq α} {_ : Hashable α}
open scoped Std.DHashMap
namespace Std
/--
Extensional dependent hash maps.
This is a simple separate-chaining hash table. The data of the hash map consists of a cached size
and an array of buckets, where each bucket is a linked list of key-value pais. The number of buckets
is always a power of two. The hash map doubles its size upon inserting an element such that the
number of elements is more than 75% of the number of buckets.
The hash table is backed by an `Array`. Users should make sure that the hash map is used linearly to
avoid expensive copies.
The hash map uses `==` (provided by the `BEq` typeclass) to compare keys and `hash` (provided by
the `Hashable` typeclass) to hash them. To ensure that the operations behave as expected, `==`
must be an equivalence relation and `a == b` must imply `hash a = hash b` (see also the
`EquivBEq` and `LawfulHashable` typeclasses). Both of these conditions are automatic if the BEq
instance is lawful, i.e., if `a == b` implies `a = b`.
In contrast to regular dependent hash maps, `Std.ExtDHashMap` offers several extensionality lemmas
and therefore has more lemmas about equality of hash maps. This however also makes it lose the
ability to iterate freely over the hash map.
These hash maps contain a bundled well-formedness invariant, which means that they cannot
be used in nested inductive types. For these use cases, `Std.DHashMap.Raw` and
`Std.DHashMap.Raw.WF` unbundle the invariant from the hash map. When in doubt, prefer
`DHashMap` over `DHashMap.Raw`.
-/
def ExtDHashMap (α : Type u) (β : α → Type v) [BEq α] [Hashable α] :=
Quotient (DHashMap.isSetoid α β)
namespace ExtDHashMap
@[inline, inherit_doc DHashMap.emptyWithCapacity]
def emptyWithCapacity [BEq α] [Hashable α]
(capacity := 8) : ExtDHashMap α β :=
Quotient.mk' (DHashMap.emptyWithCapacity capacity)
instance [BEq α] [Hashable α] : EmptyCollection (ExtDHashMap α β) where
emptyCollection := emptyWithCapacity
instance [BEq α] [Hashable α] : Inhabited (DHashMap α β) where
default := ∅
@[inline, inherit_doc DHashMap.insert]
def insert [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α)
(b : β a) : ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.insert a b))
(fun m m' (h : m ~m m') => Quotient.sound (h.insert a b))
instance [EquivBEq α] [LawfulHashable α] : Singleton ((a : α) × β a) (ExtDHashMap α β) where
singleton | ⟨a, b⟩ => (∅ : ExtDHashMap α β).insert a b
instance [EquivBEq α] [LawfulHashable α] : Insert ((a : α) × β a) (ExtDHashMap α β) where
insert | ⟨a, b⟩, s => s.insert a b
instance [EquivBEq α] [LawfulHashable α] : LawfulSingleton ((a : α) × β a) (ExtDHashMap α β) :=
⟨fun _ => rfl⟩
@[inline, inherit_doc DHashMap.insertIfNew]
def insertIfNew [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β)
(a : α) (b : β a) : ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.insertIfNew a b))
(fun m m' (h : m ~m m') => Quotient.sound (h.insertIfNew a b))
@[inline, inherit_doc DHashMap.containsThenInsert]
def containsThenInsert [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α β) (a : α) (b : β a) : Bool × ExtDHashMap α β :=
m.lift (fun m => let m' := m.containsThenInsert a b; ⟨m'.1, Quotient.mk' m'.2⟩)
(fun m m' (h : m ~m m') =>
Prod.ext
(m.containsThenInsert_fst.symm ▸ m'.containsThenInsert_fst.symm ▸ h.contains_eq)
(Quotient.sound <|
m.containsThenInsert_snd.symm ▸ m'.containsThenInsert_snd.symm ▸ h.insert a b))
@[inline, inherit_doc DHashMap.containsThenInsertIfNew]
def containsThenInsertIfNew [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α β) (a : α) (b : β a) : Bool × ExtDHashMap α β :=
m.lift (fun m => let m' := m.containsThenInsertIfNew a b; ⟨m'.1, Quotient.mk' m'.2⟩)
(fun m m' (h : m ~m m') =>
Prod.ext
(m.containsThenInsertIfNew_fst.symm ▸ m'.containsThenInsertIfNew_fst.symm ▸ h.contains_eq)
(Quotient.sound <|
m.containsThenInsertIfNew_snd.symm ▸ m'.containsThenInsertIfNew_snd.symm ▸ h.insertIfNew a b))
@[inline, inherit_doc DHashMap.getThenInsertIfNew?]
def getThenInsertIfNew? [LawfulBEq α]
(m : ExtDHashMap α β) (a : α) (b : β a) : Option (β a) × ExtDHashMap α β :=
m.lift (fun m => let m' := m.getThenInsertIfNew? a b; ⟨m'.1, Quotient.mk' m'.2⟩)
(fun m m' (h : m ~m m') =>
Prod.ext
(m.getThenInsertIfNew?_fst.symm ▸ m'.getThenInsertIfNew?_fst.symm ▸ h.get?_eq)
(Quotient.sound <|
m.getThenInsertIfNew?_snd.symm ▸ m'.getThenInsertIfNew?_snd.symm ▸ h.insertIfNew a b))
@[inline, inherit_doc DHashMap.get?]
def get? [LawfulBEq α] (m : ExtDHashMap α β)
(a : α) : Option (β a) :=
m.lift (fun m => m.get? a) (fun m m' (h : m ~m m') => h.get?_eq)
@[inline, inherit_doc DHashMap.contains]
def contains [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α) :
Bool :=
m.lift (fun m => m.contains a) (fun m m' (h : m ~m m') => h.contains_eq)
instance [EquivBEq α] [LawfulHashable α] : Membership α (ExtDHashMap α β) where
mem m a := m.contains a
instance [EquivBEq α] [LawfulHashable α] {m : ExtDHashMap α β} {a : α} : Decidable (a ∈ m) :=
inferInstanceAs <| Decidable (m.contains a)
@[inline, inherit_doc DHashMap.get]
def get [LawfulBEq α] (m : ExtDHashMap α β) (a : α)
(h : a ∈ m) : β a :=
m.pliftOn (fun m h' => m.get a (h' ▸ h :))
(fun m m' _ _ (h : m ~m m') => h.get_eq _)
@[inline, inherit_doc DHashMap.get!]
def get! [LawfulBEq α] (m : ExtDHashMap α β)
(a : α) [Inhabited (β a)] : β a :=
m.lift (fun m => m.get! a) (fun m m' (h : m ~m m') => h.get!_eq)
@[inline, inherit_doc DHashMap.getD]
def getD [LawfulBEq α] (m : ExtDHashMap α β)
(a : α) (fallback : β a) : β a :=
m.lift (fun m => m.getD a fallback) (fun m m' (h : m ~m m') => h.getD_eq)
@[inline, inherit_doc DHashMap.erase]
def erase [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α) :
ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.erase a))
(fun m m' (h : m ~m m') => Quotient.sound (h.erase a))
namespace Const
variable {β : Type v}
@[inline, inherit_doc DHashMap.Const.get?]
def get? [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α (fun _ => β)) (a : α) : Option β :=
m.lift (fun m => DHashMap.Const.get? m a)
(fun m m' (h : m ~m m') => h.constGet?_eq)
@[inline, inherit_doc DHashMap.Const.get]
def get [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α (fun _ => β)) (a : α) (h : a ∈ m) : β :=
m.pliftOn (fun m h' => DHashMap.Const.get m a (h' ▸ h :))
(fun m m' _ _ (h : m ~m m') => h.constGet_eq _)
@[inline, inherit_doc DHashMap.Const.getD]
def getD [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α (fun _ => β)) (a : α) (fallback : β) : β :=
m.lift (fun m => DHashMap.Const.getD m a fallback)
(fun m m' (h : m ~m m') => h.constGetD_eq)
@[inline, inherit_doc DHashMap.Const.get!]
def get! [EquivBEq α] [LawfulHashable α] [Inhabited β]
(m : ExtDHashMap α (fun _ => β)) (a : α) : β :=
m.lift (fun m => DHashMap.Const.get! m a)
(fun m m' (h : m ~m m') => h.constGet!_eq)
@[inline, inherit_doc DHashMap.Const.getThenInsertIfNew?]
def getThenInsertIfNew? [EquivBEq α] [LawfulHashable α]
(m : ExtDHashMap α (fun _ => β)) (a : α) (b : β) :
Option β × ExtDHashMap α (fun _ => β) :=
m.lift (fun m =>
let m' := DHashMap.Const.getThenInsertIfNew? m a b
⟨m'.1, Quotient.mk' m'.2⟩)
(fun m m' (h : m ~m m') =>
Prod.ext
(DHashMap.Const.getThenInsertIfNew?_fst.symm ▸
DHashMap.Const.getThenInsertIfNew?_fst.symm ▸ h.constGet?_eq)
(Quotient.sound <|
DHashMap.Const.getThenInsertIfNew?_snd.symm ▸
DHashMap.Const.getThenInsertIfNew?_snd.symm ▸ h.insertIfNew a b))
end Const
@[inline, inherit_doc DHashMap.getKey?]
def getKey? [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α) : Option α :=
m.lift (fun m => m.getKey? a) (fun m m' (h : m ~m m') => h.getKey?_eq)
@[inline, inherit_doc DHashMap.getKey]
def getKey [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α) (h : a ∈ m) : α :=
m.pliftOn (fun m h' => m.getKey a (h' ▸ h :))
(fun m m' _ _ (h : m ~m m') => h.getKey_eq _)
@[inline, inherit_doc DHashMap.getKey!]
def getKey! [EquivBEq α] [LawfulHashable α] [Inhabited α] (m : ExtDHashMap α β) (a : α) : α :=
m.lift (fun m => m.getKey! a) (fun m m' (h : m ~m m') => h.getKey!_eq)
@[inline, inherit_doc DHashMap.getKeyD]
def getKeyD [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) (a : α) (fallback : α) : α :=
m.lift (fun m => m.getKeyD a fallback)
(fun m m' (h : m ~m m') => h.getKeyD_eq)
@[inline, inherit_doc DHashMap.size]
def size [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) : Nat :=
m.lift (fun m => m.size) (fun m m' (h : m ~m m') => h.size_eq)
@[inline, inherit_doc DHashMap.isEmpty]
def isEmpty [EquivBEq α] [LawfulHashable α] (m : ExtDHashMap α β) : Bool :=
m.lift (fun m => m.isEmpty) (fun m m' (h : m ~m m') => h.isEmpty_eq)
-- TODO: add fold similar to `Finset.fold`
@[inline, inherit_doc DHashMap.filter]
def filter [EquivBEq α] [LawfulHashable α] (f : (a : α) → β a → Bool)
(m : ExtDHashMap α β) : ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.filter f))
(fun m m' (h : m ~m m') => Quotient.sound (h.filter f))
@[inline, inherit_doc DHashMap.map]
def map [EquivBEq α] [LawfulHashable α] (f : (a : α) → β a → γ a)
(m : ExtDHashMap α β) : ExtDHashMap α γ :=
m.lift (fun m => Quotient.mk' (m.map f))
(fun m m' (h : m ~m m') => Quotient.sound (h.map f))
@[inline, inherit_doc DHashMap.filterMap]
def filterMap [EquivBEq α] [LawfulHashable α] (f : (a : α) → β a → Option (γ a))
(m : ExtDHashMap α β) : ExtDHashMap α γ :=
m.lift (fun m => Quotient.mk' (m.filterMap f))
(fun m m' (h : m ~m m') => Quotient.sound (h.filterMap f))
@[inline, inherit_doc DHashMap.modify]
def modify [LawfulBEq α] (m : ExtDHashMap α β)
(a : α) (f : β a → β a) : ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.modify a f))
(fun m m' (h : m ~m m') => Quotient.sound (h.modify a f))
@[inline, inherit_doc DHashMap.Const.modify]
def Const.modify [EquivBEq α] [LawfulHashable α] {β : Type v} (m : ExtDHashMap α (fun _ => β))
(a : α) (f : β → β) : ExtDHashMap α (fun _ => β) :=
m.lift (fun m => Quotient.mk' (DHashMap.Const.modify m a f))
(fun m m' (h : m ~m m') => Quotient.sound (h.constModify a f))
@[inline, inherit_doc DHashMap.alter]
def alter [LawfulBEq α] (m : ExtDHashMap α β)
(a : α) (f : Option (β a) → Option (β a)) : ExtDHashMap α β :=
m.lift (fun m => Quotient.mk' (m.alter a f))
(fun m m' (h : m ~m m') => Quotient.sound (h.alter a f))
@[inline, inherit_doc DHashMap.Const.alter]
def Const.alter [EquivBEq α] [LawfulHashable α] {β : Type v} (m : ExtDHashMap α (fun _ => β))
(a : α) (f : Option β → Option β) : ExtDHashMap α (fun _ => β) :=
m.lift (fun m => Quotient.mk' (DHashMap.Const.alter m a f))
(fun m m' (h : m ~m m') => Quotient.sound (h.constAlter a f))
/-
Note: We can't use the existing functions because weird (noncomputable) `ForIn` instances
can break congruence. The subtype is still used to provide the `insertMany_ind` theorem.
-/
@[inline, inherit_doc DHashMap.insertMany]
def insertMany [EquivBEq α] [LawfulHashable α] {ρ : Type w}
[ForIn Id ρ ((a : α) × β a)] (m : ExtDHashMap α β) (l : ρ) : ExtDHashMap α β := Id.run do
let mut m : { x // ∀ P : ExtDHashMap α β → Prop,
P m → (∀ {m a b}, P m → P (m.insert a b)) → P x } := ⟨m, fun _ h _ => h⟩
for ⟨a, b⟩ in l do
m := ⟨m.1.insert a b, fun _ init step => step (m.2 _ init step)⟩
return m.1
@[inline, inherit_doc DHashMap.Const.insertMany]
def Const.insertMany [EquivBEq α] [LawfulHashable α] {β : Type v} {ρ : Type w}
[ForIn Id ρ (α × β)] (m : ExtDHashMap α (fun _ => β))
(l : ρ) : ExtDHashMap α (fun _ => β) := Id.run do
let mut m : { x // ∀ P : ExtDHashMap α (fun _ => β) → Prop,
P m → (∀ {m a b}, P m → P (m.insert a b)) → P x } := ⟨m, fun _ h _ => h⟩
for (a, b) in l do
m := ⟨m.1.insert a b, fun _ init step => step (m.2 _ init step)⟩
return m.1
@[inline, inherit_doc DHashMap.Const.insertManyIfNewUnit]
def Const.insertManyIfNewUnit [EquivBEq α] [LawfulHashable α] {ρ : Type w}
[ForIn Id ρ α] (m : ExtDHashMap α (fun _ => Unit))
(l : ρ) : ExtDHashMap α (fun _ => Unit) := Id.run do
let mut m : { x // ∀ P : ExtDHashMap α (fun _ => Unit) → Prop,
P m → (∀ {m a}, P m → P (m.insertIfNew a ())) → P x } := ⟨m, fun _ h _ => h⟩
for a in l do
m := ⟨m.1.insertIfNew a (), fun _ init step => step (m.2 _ init step)⟩
return m.1
-- TODO (after verification): partition, union
@[inline, inherit_doc DHashMap.Const.unitOfArray]
def Const.unitOfArray [BEq α] [Hashable α] (l : Array α) :
ExtDHashMap α (fun _ => Unit) :=
Quotient.mk' (DHashMap.Const.unitOfArray l)
@[inline, inherit_doc DHashMap.ofList]
def ofList [BEq α] [Hashable α] (l : List ((a : α) × β a)) :
ExtDHashMap α β :=
Quotient.mk' (DHashMap.ofList l)
@[inline, inherit_doc DHashMap.Const.ofList]
def Const.ofList {β : Type v} [BEq α] [Hashable α] (l : List (α × β)) :
ExtDHashMap α (fun _ => β) :=
Quotient.mk' (DHashMap.Const.ofList l)
@[inline, inherit_doc DHashMap.Const.unitOfList]
def Const.unitOfList [BEq α] [Hashable α] (l : List α) :
ExtDHashMap α (fun _ => Unit) :=
Quotient.mk' (DHashMap.Const.unitOfList l)
end ExtDHashMap
end Std