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.
248 lines
9.6 KiB
Text
248 lines
9.6 KiB
Text
/-
|
||
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
|