| /- | |
| 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.ExtHashMap.Basic | |
| /-! | |
| # Extensional hash sets | |
| This module develops the type `Std.ExtHashSet` of extensional hash sets. | |
| Lemmas about the operations on `Std.ExtHashSet` are available in the | |
| module `Std.Data.ExtHashSet.Lemmas`. | |
| -/ | |
| set_option linter.missingDocs true | |
| set_option autoImplicit false | |
| universe u v | |
| variable {α : Type u} {_ : BEq α} {_ : Hashable α} | |
| namespace Std | |
| /-- | |
| Hash sets. | |
| This is a simple separate-chaining hash table. The data of the hash set consists of a cached size | |
| and an array of buckets, where each bucket is a linked list of keys. The number of buckets | |
| is always a power of two. The hash set 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 set is used linearly to | |
| avoid expensive copies. | |
| The hash set uses `==` (provided by the `BEq` typeclass) to compare elements 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 sets, `Std.ExtHashSet` 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 sets. | |
| These hash sets contain a bundled well-formedness invariant, which means that they cannot | |
| be used in nested inductive types. For these use cases, `Std.HashSet.Raw` and | |
| `Std.HashSet.Raw.WF` unbundle the invariant from the hash set. When in doubt, prefer | |
| `HashSet` or `ExtHashSet` over `HashSet.Raw`. | |
| -/ | |
| structure ExtHashSet (α : Type u) [BEq α] [Hashable α] where | |
| /-- Internal implementation detail of the hash set. -/ | |
| inner : ExtHashMap α Unit | |
| namespace ExtHashSet | |
| /-- | |
| Creates a new empty hash set. The optional parameter `capacity` can be supplied to presize the | |
| set so that it can hold the given number of elements without reallocating. It is also possible to | |
| use the empty collection notations `∅` and `{}` to create an empty hash set with the default | |
| capacity. | |
| -/ | |
| @[inline] def emptyWithCapacity [BEq α] [Hashable α] (capacity := 8) : ExtHashSet α := | |
| ⟨ExtHashMap.emptyWithCapacity capacity⟩ | |
| instance [BEq α] [Hashable α] : EmptyCollection (ExtHashSet α) where | |
| emptyCollection := emptyWithCapacity | |
| instance [BEq α] [Hashable α] : Inhabited (ExtHashSet α) where | |
| default := ∅ | |
| /-- | |
| Inserts the given element into the set. If the hash set already contains an element that is | |
| equal (with regard to `==`) to the given element, then the hash set is returned unchanged. | |
| Note: this non-replacement behavior is true for `ExtHashSet` and `ExtHashSet.Raw`. | |
| The `insert` function on `ExtHashMap`, `DExtHashMap`, `ExtHashMap.Raw` and `DExtHashMap.Raw` behaves | |
| differently: it will overwrite an existing mapping. | |
| -/ | |
| @[inline] def insert [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) (a : α) : ExtHashSet α := | |
| ⟨m.inner.insertIfNew a ()⟩ | |
| instance [EquivBEq α] [LawfulHashable α] : Singleton α (ExtHashSet α) where | |
| singleton a := (∅ : ExtHashSet α).insert a | |
| instance [EquivBEq α] [LawfulHashable α] : Insert α (ExtHashSet α) where | |
| insert a s := s.insert a | |
| /-- | |
| Checks whether an element is present in a set and inserts the element if it was not found. | |
| If the hash set already contains an element that is equal (with regard to `==`) to the given | |
| element, then the hash set is returned unchanged. | |
| Equivalent to (but potentially faster than) calling `contains` followed by `insert`. | |
| -/ | |
| @[inline] | |
| def containsThenInsert [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) | |
| (a : α) : Bool × ExtHashSet α := | |
| let ⟨replaced, r⟩ := m.inner.containsThenInsertIfNew a () | |
| ⟨replaced, ⟨r⟩⟩ | |
| /-- | |
| Returns `true` if the given key is present in the set. There is also a `Prop`-valued version of | |
| this: `a ∈ m` is equivalent to `m.contains a = true`. | |
| Observe that this is different behavior than for lists: for lists, `∈` uses `=` and `contains` use | |
| `==` for comparisons, while for hash sets, both use `==`. | |
| -/ | |
| @[inline] def contains [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) (a : α) : Bool := | |
| m.inner.contains a | |
| instance [BEq α] [Hashable α] [EquivBEq α] [LawfulHashable α] : Membership α (ExtHashSet α) where | |
| mem m a := a ∈ m.inner | |
| instance [BEq α] [Hashable α] [EquivBEq α] [LawfulHashable α] | |
| {m : ExtHashSet α} {a : α} : Decidable (a ∈ m) := | |
| inferInstanceAs (Decidable (a ∈ m.inner)) | |
| /-- Removes the element if it exists. -/ | |
| @[inline] def erase [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) (a : α) : ExtHashSet α := | |
| ⟨m.inner.erase a⟩ | |
| /-- The number of elements present in the set -/ | |
| @[inline] def size [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) : Nat := | |
| m.inner.size | |
| /-- | |
| Checks if given key is contained and returns the key if it is, otherwise `none`. | |
| The result in the `some` case is guaranteed to be pointer equal to the key in the set. | |
| -/ | |
| @[inline] def get? [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) (a : α) : Option α := | |
| m.inner.getKey? a | |
| /-- | |
| Retrieves the key from the set that matches `a`. Ensures that such a key exists by requiring a proof | |
| of `a ∈ m`. The result is guaranteed to be pointer equal to the key in the set. | |
| -/ | |
| @[inline] def get [EquivBEq α] [LawfulHashable α] | |
| (m : ExtHashSet α) (a : α) (h : a ∈ m) : α := | |
| m.inner.getKey a h | |
| /-- | |
| Checks if given key is contained and returns the key if it is, otherwise `fallback`. | |
| If they key is contained the result is guaranteed to be pointer equal to the key in the set. | |
| -/ | |
| @[inline] def getD [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) (a : α) (fallback : α) : α := | |
| m.inner.getKeyD a fallback | |
| /-- | |
| Checks if given key is contained and returns the key if it is, otherwise panics. | |
| If no panic occurs the result is guaranteed to be pointer equal to the key in the set. | |
| -/ | |
| @[inline] def get! [EquivBEq α] [LawfulHashable α] [Inhabited α] (m : ExtHashSet α) (a : α) : α := | |
| m.inner.getKey! a | |
| /-- | |
| Returns `true` if the hash set contains no elements. | |
| Note that if your `BEq` instance is not reflexive or your `Hashable` instance is not | |
| lawful, then it is possible that this function returns `false` even though `m.contains a = false` | |
| for all `a`. | |
| -/ | |
| @[inline] def isEmpty [EquivBEq α] [LawfulHashable α] (m : ExtHashSet α) : Bool := | |
| m.inner.isEmpty | |
| /-- | |
| Creates a hash set from a list of elements. Note that unlike repeatedly calling `insert`, if the | |
| collection contains multiple elements that are equal (with regard to `==`), then the last element | |
| in the collection will be present in the returned hash set. | |
| -/ | |
| @[inline] def ofList [BEq α] [Hashable α] (l : List α) : ExtHashSet α := | |
| ⟨ExtHashMap.unitOfList l⟩ | |
| /-- Removes all elements from the hash set for which the given function returns `false`. -/ | |
| @[inline] def filter [EquivBEq α] [LawfulHashable α] (f : α → Bool) (m : ExtHashSet α) : ExtHashSet α := | |
| ⟨m.inner.filter fun a _ => f a⟩ | |
| /-- | |
| Inserts multiple mappings into the hash set by iterating over the given collection and calling | |
| `insert`. If the same key appears multiple times, the first occurrence takes precedence. | |
| Note: this precedence behavior is true for `ExtHashSet` and `ExtHashSet.Raw`. The `insertMany` function on | |
| `ExtHashMap`, `DExtHashMap`, `ExtHashMap.Raw` and `DExtHashMap.Raw` behaves differently: it will prefer the last | |
| appearance. | |
| -/ | |
| @[inline] def insertMany [EquivBEq α] [LawfulHashable α] {ρ : Type v} [ForIn Id ρ α] | |
| (m : ExtHashSet α) (l : ρ) : ExtHashSet α := | |
| ⟨m.inner.insertManyIfNewUnit l⟩ | |
| /-- | |
| Creates a hash set from an array of elements. Note that unlike repeatedly calling `insert`, if the | |
| collection contains multiple elements that are equal (with regard to `==`), then the last element | |
| in the collection will be present in the returned hash set. | |
| -/ | |
| @[inline] def ofArray [BEq α] [Hashable α] (l : Array α) : ExtHashSet α := | |
| ⟨ExtHashMap.unitOfArray l⟩ | |
| end ExtHashSet | |
| end Std |