lean4-htt/src/Std/Data/ExtHashMap/Basic.lean
Rob23oba 747ea853b5
feat: extensional hash maps (#8004)
This PR adds extensional hash maps and hash sets under the names
`Std.ExtDHashMap`, `Std.ExtHashMap` and `Std.ExtHashSet`. Extensional
hash maps work like regular hash maps, except that they have
extensionality lemmas which make them easier to use in proofs. This
however makes it also impossible to regularly iterate over its entries.
2025-04-28 06:48:25 +00:00

248 lines
9.6 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
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.ExtDHashMap.Basic
set_option linter.missingDocs true
set_option autoImplicit false
/-!
# Extensional hash maps
This module develops the type `Std.ExtHashMap` of extensional hash maps. Dependent hash maps
are defined in `Std.Data.ExtDHashMap`.
Lemmas about the operations on `Std.ExtHashMap` are available in the
module `Std.Data.ExtHashMap.Lemmas`.
-/
universe u v w
variable {α : Type u} {β : Type v} {γ : Type w} {_ : BEq α} {_ : Hashable α}
namespace Std
/--
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, `==`
should be an equivalence relation and `a == b` should 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 hash maps, `Std.ExtHashMap` 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 hash maps.
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.HashMap.Raw` and
`Std.HashMap.Raw.WF` unbundle the invariant from the hash map. When in doubt, prefer
`HashMap` or `ExtHashMap` over `HashMap.Raw`.
Dependent hash maps, in which keys may occur in their values' types, are available as
`Std.ExtDHashMap` in the module `Std.Data.ExtDHashMap`.
-/
structure ExtHashMap (α : Type u) (β : Type v) [BEq α] [Hashable α] where
/-- Internal implementation detail of the hash map -/
inner : ExtDHashMap α (fun _ => β)
namespace ExtHashMap
@[inline, inherit_doc ExtDHashMap.emptyWithCapacity]
def emptyWithCapacity [BEq α] [Hashable α] (capacity := 8) :
ExtHashMap α β :=
⟨ExtDHashMap.emptyWithCapacity capacity⟩
instance [BEq α] [Hashable α] : EmptyCollection (ExtHashMap α β) where
emptyCollection := emptyWithCapacity
instance [BEq α] [Hashable α] : Inhabited (ExtHashMap α β) where
default := ∅
@[inline, inherit_doc ExtDHashMap.insert]
def insert [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α)
(b : β) : ExtHashMap α β :=
⟨m.inner.insert a b⟩
instance [EquivBEq α] [LawfulHashable α] : Singleton (α × β) (ExtHashMap α β) where
singleton | ⟨a, b⟩ => (∅ : ExtHashMap α β).insert a b
instance [EquivBEq α] [LawfulHashable α] : Insert (α × β) (ExtHashMap α β) where
insert | ⟨a, b⟩, s => s.insert a b
instance [EquivBEq α] [LawfulHashable α] : LawfulSingleton (α × β) (ExtHashMap α β) :=
⟨fun _ => rfl⟩
@[inline, inherit_doc ExtDHashMap.insertIfNew]
def insertIfNew [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β)
(a : α) (b : β) : ExtHashMap α β :=
⟨m.inner.insertIfNew a b⟩
@[inline, inherit_doc ExtDHashMap.containsThenInsert]
def containsThenInsert [EquivBEq α] [LawfulHashable α]
(m : ExtHashMap α β) (a : α) (b : β) : Bool × ExtHashMap α β :=
let ⟨replaced, r⟩ := m.inner.containsThenInsert a b
⟨replaced, ⟨r⟩⟩
@[inline, inherit_doc ExtDHashMap.containsThenInsertIfNew]
def containsThenInsertIfNew [EquivBEq α] [LawfulHashable α]
(m : ExtHashMap α β) (a : α) (b : β) : Bool × ExtHashMap α β :=
let ⟨replaced, r⟩ := m.inner.containsThenInsertIfNew a b
⟨replaced, ⟨r⟩⟩
/--
Checks whether a key is present in a map, returning the associate value, and inserts a value for
the key if it was not found.
If the returned value is `some v`, then the returned map is unaltered. If it is `none`, then the
returned map has a new value inserted.
Equivalent to (but potentially faster than) calling `get?` followed by `insertIfNew`.
-/
@[inline]
def getThenInsertIfNew? [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) (b : β) :
Option β × ExtHashMap α β :=
let ⟨previous, r⟩ := ExtDHashMap.Const.getThenInsertIfNew? m.inner a b
⟨previous, ⟨r⟩⟩
/--
The notation `m[a]?` is preferred over calling this function directly.
Tries to retrieve the mapping for the given key, returning `none` if no such mapping is present.
-/
@[inline] def get? [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) : Option β :=
ExtDHashMap.Const.get? m.inner a
@[inline, inherit_doc ExtDHashMap.contains]
def contains [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β)
(a : α) : Bool :=
m.inner.contains a
instance [BEq α] [Hashable α] [EquivBEq α] [LawfulHashable α] : Membership α (ExtHashMap α β) where
mem m a := a ∈ m.inner
instance [BEq α] [Hashable α] [EquivBEq α] [LawfulHashable α]
{m : ExtHashMap α β} {a : α} : Decidable (a ∈ m) :=
inferInstanceAs (Decidable (a ∈ m.inner))
/--
The notation `m[a]` or `m[a]'h` is preferred over calling this function directly.
Retrieves the mapping for the given key. Ensures that such a mapping exists by requiring a proof of
`a ∈ m`.
-/
@[inline] def get [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) (h : a ∈ m) : β :=
ExtDHashMap.Const.get m.inner a h
@[inline, inherit_doc ExtDHashMap.Const.getD]
def getD [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α)
(fallback : β) : β :=
ExtDHashMap.Const.getD m.inner a fallback
/--
The notation `m[a]!` is preferred over calling this function directly.
Tries to retrieve the mapping for the given key, panicking if no such mapping is present.
-/
@[inline]
def get! [EquivBEq α] [LawfulHashable α] [Inhabited β] (m : ExtHashMap α β) (a : α) : β :=
ExtDHashMap.Const.get! m.inner a
instance [BEq α] [Hashable α] [EquivBEq α] [LawfulHashable α] :
GetElem? (ExtHashMap α β) α β (fun m a => a ∈ m) where
getElem m a h := m.get a h
getElem? m a := m.get? a
getElem! m a := m.get! a
@[inline, inherit_doc ExtDHashMap.getKey?]
def getKey? [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) : Option α :=
ExtDHashMap.getKey? m.inner a
@[inline, inherit_doc ExtDHashMap.getKey]
def getKey [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) (h : a ∈ m) : α :=
ExtDHashMap.getKey m.inner a h
@[inline, inherit_doc ExtDHashMap.getKeyD]
def getKeyD [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) (fallback : α) : α :=
ExtDHashMap.getKeyD m.inner a fallback
@[inline, inherit_doc ExtDHashMap.getKey!]
def getKey! [EquivBEq α] [LawfulHashable α] [Inhabited α] (m : ExtHashMap α β) (a : α) : α :=
ExtDHashMap.getKey! m.inner a
@[inline, inherit_doc ExtDHashMap.erase]
def erase [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) :
ExtHashMap α β :=
⟨m.inner.erase a⟩
@[inline, inherit_doc ExtDHashMap.size]
def size [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) : Nat :=
m.inner.size
@[inline, inherit_doc ExtDHashMap.isEmpty]
def isEmpty [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) : Bool :=
m.inner.isEmpty
@[inline, inherit_doc ExtDHashMap.Const.ofList]
def ofList [BEq α] [Hashable α] (l : List (α × β)) :
ExtHashMap α β :=
⟨ExtDHashMap.Const.ofList l⟩
@[inline, inherit_doc ExtDHashMap.Const.unitOfList]
def unitOfList [BEq α] [Hashable α] (l : List α) :
ExtHashMap α Unit :=
⟨ExtDHashMap.Const.unitOfList l⟩
@[inline, inherit_doc ExtDHashMap.filter]
def filter [EquivBEq α] [LawfulHashable α] (f : α → β → Bool)
(m : ExtHashMap α β) : ExtHashMap α β :=
⟨m.inner.filter f⟩
@[inline, inherit_doc ExtDHashMap.map]
def map [EquivBEq α] [LawfulHashable α] (f : α → β → γ)
(m : ExtHashMap α β) : ExtHashMap α γ :=
⟨m.inner.map f⟩
@[inline, inherit_doc ExtDHashMap.filterMap]
def filterMap [EquivBEq α] [LawfulHashable α] (f : α → β → Option γ)
(m : ExtHashMap α β) : ExtHashMap α γ :=
⟨m.inner.filterMap f⟩
@[inline, inherit_doc ExtDHashMap.modify]
def modify [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α) (f : β → β) :
ExtHashMap α β :=
⟨ExtDHashMap.Const.modify m.inner a f⟩
@[inline, inherit_doc ExtDHashMap.alter]
def alter [EquivBEq α] [LawfulHashable α] (m : ExtHashMap α β) (a : α)
(f : Option β → Option β) : ExtHashMap α β :=
⟨ExtDHashMap.Const.alter m.inner a f⟩
@[inline, inherit_doc ExtDHashMap.Const.insertMany]
def insertMany [EquivBEq α] [LawfulHashable α] {ρ : Type w}
[ForIn Id ρ (α × β)] (m : ExtHashMap α β) (l : ρ) : ExtHashMap α β :=
⟨ExtDHashMap.Const.insertMany m.inner l⟩
@[inline, inherit_doc ExtDHashMap.Const.insertManyIfNewUnit]
def insertManyIfNewUnit [EquivBEq α] [LawfulHashable α]
{ρ : Type w} [ForIn Id ρ α] (m : ExtHashMap α Unit) (l : ρ) : ExtHashMap α Unit :=
⟨ExtDHashMap.Const.insertManyIfNewUnit m.inner l⟩
@[inline, inherit_doc ExtDHashMap.Const.unitOfArray]
def unitOfArray [BEq α] [Hashable α] (l : Array α) :
ExtHashMap α Unit :=
⟨ExtDHashMap.Const.unitOfArray l⟩
end Std.ExtHashMap