sain.collections.hash_map

An extended version of built-in dict but with extra functionality.

This contains Rust's std::collections::HashMap methods.

  1# BSD 3-Clause License
  2#
  3# Copyright (c) 2022-Present, nxtlo
  4# All rights reserved.
  5#
  6# Redistribution and use in source and binary forms, with or without
  7# modification, are permitted provided that the following conditions are met:
  8#
  9# * Redistributions of source code must retain the above copyright notice, this
 10#   list of conditions and the following disclaimer.
 11#
 12# * Redistributions in binary form must reproduce the above copyright notice,
 13#   this list of conditions and the following disclaimer in the documentation
 14#   and/or other materials provided with the distribution.
 15#
 16# * Neither the name of the copyright holder nor the names of its
 17#   contributors may be used to endorse or promote products derived from
 18#   this software without specific prior written permission.
 19#
 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 30"""An extended version of built-in `dict` but with extra functionality.
 31
 32This contains Rust's `std::collections::HashMap` methods.
 33"""
 34
 35from __future__ import annotations
 36
 37__all__ = ("HashMap", "RefMut")
 38
 39import collections.abc as collections
 40import typing
 41
 42from sain import option as option
 43from sain.convert import From
 44from sain.convert import Into
 45from sain.default import Default
 46from sain.macros import rustc_diagnostic_item
 47from sain.result import Err
 48from sain.result import Ok
 49
 50from .base_iter import Drain
 51from .base_iter import ExtractIf
 52from .base_iter import Fn
 53from .base_iter import IntoIterator
 54from .base_iter import IntoKeys
 55from .base_iter import IntoValues
 56from .base_iter import Iter
 57
 58K = typing.TypeVar("K")
 59V = typing.TypeVar("V")
 60
 61if typing.TYPE_CHECKING:
 62    from typing_extensions import Never
 63    from typing_extensions import Self
 64
 65    from sain.option import Option
 66    from sain.result import Result
 67
 68
 69class _RawMap(
 70    typing.Generic[K, V],
 71    collections.Mapping[K, V],
 72    From[collections.Iterable[tuple[K, V]]],
 73    Into[list[tuple[K, V]]],
 74):
 75    __slots__ = ("_source",)
 76
 77    def __init__(self, source: dict[K, V] | None = None, /) -> None:
 78        self._source = source if source else {}
 79
 80    # constructors
 81
 82    @classmethod
 83    def from_keys(cls, *iterable: tuple[K, V]) -> HashMap[K, V]:
 84        """Create a new `HashMap` from a sequence of key-value pairs.
 85
 86        Example
 87        -------
 88        ```py
 89        tier_list = [
 90            ("S", "Top tier"),
 91            ("A", "Very Good"),
 92            ("B", "Good"),
 93            ("C", "Average"),
 94            ("D", "dodo")
 95        ]
 96        mapping = HashMap[str, str].from_keys(*tier_list)
 97        ```
 98        """
 99        return HashMap({k: v for k, v in iterable})
100
101    # trait impls
102
103    @classmethod
104    def from_value(cls, value: collections.Iterable[tuple[K, V]]) -> HashMap[K, V]:
105        """Creates a `HashMap` from an iterable of `(K, V)` key-value paris.
106
107        Same as `HashMap.from_keys(*iter)`
108        """
109        return HashMap({k: v for k, v in value})
110
111    def into(self) -> list[tuple[K, V]]:
112        """Turn this `HashMap` into a `[(K, V); len(self)]` key-value paris.
113
114        `self` is consumed afterwards.
115
116        Example
117        -------
118        ```py
119        users = [(0, "user-0"), (1, "user-1")]
120        map1 = HashMap.from_keys(*users)
121        assert map1.into() == users
122        # map1 is dropped.
123        ```
124        """
125        return [(k, v) for k, v in self.leak().items()]
126
127    # default impls
128
129    def into_keys(self) -> IntoKeys[K]:
130        """Creates an iterator that consumes the map, yielding all of its keys.
131
132        `self` cannot be used after calling this.
133
134        Example
135        -------
136        ```py
137        map1 = HashMap({
138            "a": 1,
139            "b": 2,
140            "c": 3,
141        })
142        keys = map1.into_keys().collect()
143        assert keys == ["a", "b", "c"]
144        ```
145        """
146        return IntoKeys(self.leak().keys())
147
148    def into_values(self) -> IntoValues[V]:
149        """Creates an iterator that consumes the map, yielding all of its values.
150
151        `self` cannot be used after calling this.
152
153        Example
154        -------
155        ```py
156        map1 = HashMap({
157            "a": 1,
158            "b": 2,
159            "c": 3,
160        })
161        values = map1.into_values().collect()
162        assert values == [1, 2, 3]
163        ```
164        """
165        return IntoValues(self.leak().values())
166
167    def contains_key(self, k: K) -> bool:
168        """Check whether `k` is in `self` or not.
169
170        Example
171        -------
172        ```py
173        users = HashMap({0: "user-0"})
174        assert users.contains_key(0) is True
175        # similar to just doing
176        assert 0 in users
177        ```
178        """
179        return k in self
180
181    def is_empty(self) -> bool:
182        """Whether this `self` contains any items or not.
183
184        Example
185        -------
186        ```py
187        storage: HashMap[str, None] = HashMap()
188        if storage.is_empty():
189            ...
190        # Or just
191        if not storage:
192            ...
193        ```
194        """
195        return not self
196
197    def get(self, key: K) -> Option[V]:
198        """Get the value associated with `key`, returns `None` if not found.
199
200        Unlike `dict.get` which returns builtin `None`, This returns `Option[T]`.
201
202        Example
203        -------
204        ```py
205        users: HashMap[str, int] = HashMap()
206        assert users.get("jack").is_none()
207
208        users = HashMap({"jack": None})
209        assert users.get("jack").is_some()
210        ```
211        """
212        if key not in self:
213            return option.NOTHING
214
215        return option.Some(self._source[key])
216
217    def get_with(self, f: collections.Callable[[], K]) -> Option[V]:
218        """Get the value associated with `key` returned from a callable `f()`, returns `None` if not found.
219
220        Example
221        -------
222        ```py
223        def get_id() -> int:
224            return 0
225
226        map = HashMap({0: "buh", 1: "guh", 2: "luh"})
227        assert map.get_with(get_id).unwrap() == "a"
228        ```
229        """
230        key = f()
231        if key not in self:
232            return option.NOTHING
233
234        return option.Some(self._source[key])
235
236    def get_pairs(self, key: K) -> Option[tuple[K, V]]:
237        """Get both `key-value` pairs associated with `key`, returns `None` if not found.
238
239        Example
240        -------
241        ```py
242        users: HashMap[str, int] = HashMap()
243        assert users.get_pairs("jack").is_none()
244
245        users = HashMap({"jack": 0})
246        assert users.get("jack").unwrap() == ("jack", 0)
247        ```
248        """
249        if key not in self:
250            return option.NOTHING
251
252        return option.Some((key, self._source[key]))
253
254    def get_many(self, *keys: K) -> Option[collections.Collection[V]]:
255        """Attempts to get `len(keys)` values in the map at once.
256
257        Returns a collection of length `keys` with the results of each query.
258        None will be returned if any of the keys missing.
259
260        The time complexity is `O(N)`, where `N` is the length of `keys`.
261
262        Example
263        -------
264        ```py
265        urls = HashMap({
266            "google": "www.google.com",
267            "github": "www.github.com",
268            "facebook": "www.facebook.com",
269            "twitter": "www.twitter.com",
270        })
271        assert urls.get_many("google","github").unwrap() == ["www.google.com", "www.github.com"]
272
273        # missing keys results in `None`
274        assert urls.get_many("google", "linkedin").is_none()
275        # duplicate keys results in `None`
276        assert urls.get_many("google", "google").is_none()
277        ```
278        """
279        if not self._source:
280            return option.NOTHING
281
282        seen: set[K] = set()
283        result: list[V] = []
284
285        for key in keys:
286            if key not in self._source or key in seen:
287                return option.NOTHING
288
289            seen.add(key)
290            result.append(self._source[key])
291
292        return option.Some(result)
293
294    def iter(self) -> Iter[tuple[K, V]]:
295        """Creates an iterator over the key-value pairs of the map.
296
297        Example
298        -------
299        ```py
300        map = HashMap.from_keys([
301            ("a", 1),
302            ("b", 2),
303        ])
304        for k, v in map.iter():
305            print(f"{k=}: {v=}")
306        ```
307        """
308        return Iter(self._source.items())
309
310    def into_iter(self) -> IntoIterator[K, V]:
311        """Creates a consuming iterator, that is, one that moves each key-value pair out of the map.
312        The map cannot be used after calling this.
313
314        Example
315        -------
316        ```py
317        map = HashMap.from_keys([
318            ("a", 1),
319            ("b", 2),
320            ("c", 3),
321        ])
322        for k, v in map.into_iter():
323            print(f"{k=}: {v=}")
324        ```
325        """
326        return IntoIterator(self.leak())
327
328    def len(self) -> int:
329        """Get how many elements are in this map.
330
331        Example
332        -------
333        ```py
334        map: HashMap[str, int] = HashMap()
335        assert map.len() == 0
336        ```
337        """
338        return len(self._source)
339
340    # conversions
341
342    def leak(self) -> collections.MutableMapping[K, V]:
343        """Leaks and returns a mutable reference to the underlying map.
344
345        `self` becomes unusable after calling this.
346
347        Example
348        -------
349        ```py
350        map = HashMap({0: 1})
351        inner = map.leak()
352        assert inner == {0: 1}
353        ```
354        """
355        out = self._source
356        del self._source
357        return out
358
359    # built-ins
360
361    def copy(self) -> Self:
362        """Copy the contents of this hash map into a new one.
363
364        Example
365        -------
366        ```py
367        hashmap: HashMap[str, None] = HashMap({'1': None, '2': None})
368        copy = hashmap.copy()
369        assert hashmap == copy
370        ```
371        """
372        return self.__class__(self._source.copy())
373
374    def __repr__(self) -> str:
375        return self._source.__repr__()
376
377    def __iter__(self) -> collections.Iterator[K]:
378        return self._source.__iter__()
379
380    def __len__(self) -> int:
381        return self._source.__len__()
382
383    def __getitem__(self, key: K, /) -> V:
384        return self._source[key]
385
386    def __setitem__(self, _key: Never, _value: Never) -> Never:
387        raise NotImplementedError(
388            "`HashMap` is immutable, use `.as_mut()` to make it mutable instead"
389        )
390
391    def __delitem__(self, _key: Never) -> Never:
392        raise NotImplementedError(
393            "`HashMap` is immutable, use `.as_mut()` to make it mutable instead"
394        )
395
396
397@rustc_diagnostic_item("HashMap")
398class HashMap(_RawMap[K, V], Default["HashMap[K, V]"]):
399    """An immutable key-value dictionary.
400
401    But default, `HashMap` is immutable, it cannot change its values after initializing it. however,
402    you can return a mutable reference to this hashmap via `HashMap.as_mut` method, it returns a reference to the underlying map.
403
404    Example
405    -------
406    ```py
407    # initialize it with a source. after that, item insertion is not allowed.
408    books: HashMap[str, str] = HashMap({
409        "Adventures of Huckleberry Finn": "My favorite book."
410    })
411    # get a mutable reference to it to be able to.
412    books_mut = books.as_mut()
413    # You can either call `.insert`, which is similar to Rust's.
414    books_mut.insert(
415        "Grimms Fairy Tales",
416        "Masterpiece.",
417    )
418    # Or via item assignments
419    books_mut["Pride and Prejudice"] = "Very enjoyable."
420    print(books)
421
422    for book, review in books.items():
423        print(book, review)
424    ```
425
426    Parameters
427    ----------
428    source: `dict[K, V]`
429        A dictionary to point to. this will be used as the underlying source.
430    """
431
432    __slots__ = ("_source",)
433
434    def __init__(self, source: dict[K, V] | None = None, /) -> None:
435        super().__init__(source)
436
437    @staticmethod
438    def default() -> HashMap[K, V]:
439        """Creates an empty `HashMap<K, V>`."""
440        return HashMap()
441
442    @classmethod
443    def from_mut(cls, source: dict[K, V] | None = None, /) -> RefMut[K, V]:
444        """Create a new mutable `HashMap`, with the given source if available.
445
446        Example
447        -------
448        ```py
449        books = HashMap.from_mut()
450        books[0] = "Twilight"
451        ```
452        """
453        return RefMut(source or {})
454
455    @staticmethod
456    def from_keys_mut(*iterable: tuple[K, V]) -> RefMut[K, V]:
457        """Create a new mutable `HashMap` from a sequence of key-value pairs.
458
459        Example
460        -------
461        ```py
462        tier_list = [
463            ("S", "Top tier"),
464            ("A", "Very Good"),
465            ("B", "Good"),
466            ("C", "Average"),
467            ("D", "dodo")
468        ]
469        mapping: RefMut[str, str] = HashMap.from_keys_mut(*tier_list)
470        ```
471        """
472        return RefMut({k: v for k, v in iterable})
473
474    def as_mut(self) -> RefMut[K, V]:
475        """Get a mutable reference to this hash map.
476
477        Example
478        -------
479        ```py
480        map: HashMap[str, float] = HashMap()
481
482        # Get a reference to map
483        mut = map.as_mut()
484        mut.insert("sqrt", 1.0)
485        del mut # not needed anymore
486
487        assert map == {"sqrt": 1.0}
488        ```
489        """
490        return RefMut(self._source)
491
492
493@typing.final
494class RefMut(_RawMap[K, V], collections.MutableMapping[K, V], Default["RefMut[K, V]"]):
495    """A reference to a mutable dictionary / hashmap.
496
497    Ideally, you want to use `HashMap.as_mut()` / `HashMap.from_mut` methods to create this.
498    """
499
500    __slots__ = ("_source",)
501
502    def __init__(self, source: dict[K, V], /) -> None:
503        super().__init__(source)
504        self._source = source
505
506    @staticmethod
507    def default() -> RefMut[K, V]:
508        """Creates a new, mutable, empty `RefMut<K, V>`."""
509        return RefMut({})
510
511    def insert(self, key: K, value: V) -> Option[V]:
512        """Insert a key/value pair into the map.
513
514        if `key` is not present in the map, `None` is returned. otherwise, the value is updated, and the old value
515        is returned.
516
517        Example
518        -------
519        ```py
520        users = HashMap.from_mut({0: "admin"})
521        assert users.insert(1, "admin").is_none()
522        old = users.insert(0, "normal").unwrap()
523        assert old == "admin"
524        ```
525        """
526        if key not in self:
527            self[key] = value
528            return option.NOTHING
529
530        old = self[key]
531        self[key] = value
532        return option.Some(old)
533
534    def try_insert(self, key: K, value: V) -> Result[V, K]:
535        """Tries to insert `key` and `value` into the map, returning `Err(key)` if `key` is already present.
536
537        Example
538        -------
539        ```py
540        users = HashMap.from_mut({0: "admin"})
541        assert users.try_insert(1, "claudia").is_ok()
542        assert users.try_insert(0, "doppler").is_err()
543        ```
544        """
545        if key in self:
546            return Err(key)
547
548        self[key] = value
549        return Ok(value)
550
551    def remove(self, key: K) -> Option[V]:
552        """Remove a key from the map, returning the value of the key if it was previously in the map.
553
554        Example
555        -------
556        ```py
557        map = HashMap.from_mut()
558        map.insert(0, "a")
559        map.remove(0).unwrap() == "a"
560        map.remove(0).is_none()
561        ```
562        """
563        if key not in self:
564            return option.NOTHING
565
566        v = self[key]
567        del self[key]
568        return option.Some(v)
569
570    def remove_entry(self, key: K) -> Option[tuple[K, V]]:
571        """Remove a key from the map, returning the key and value if it was previously in the map.
572
573        Example
574        -------
575        ```py
576        map = HashMap.from_mut()
577        map[0] = "a"
578        assert map.remove_entry(0).unwrap() == (0, "a")
579        assert map.remove_entry(0).is_none()
580        ```
581        """
582        if key not in self:
583            return option.NOTHING
584
585        v = self[key]
586        del self[key]
587        return option.Some((key, v))
588
589    def retain(self, f: collections.Callable[[K, V], bool]) -> None:
590        """Remove items from this map based on `f(key, value)` returning `False`.
591
592        Example
593        -------
594        ```py
595        users = HashMap.from_mut({
596            "user1": "admin",
597            "user2": "admin",
598            "user3": "regular",
599            "jack": "admin"
600        })
601        users.retain(
602            lambda user, role: role == "admin" and
603            user.startswith("user")
604        )
605        for user, role in users.items():
606            print(user, role)
607
608        # user1 admin
609        # user2 admin
610        """
611        for k, v in self._source.copy().items():
612            if not f(k, v):
613                del self[k]
614
615    def drain(self) -> Drain[K, V]:
616        """Clears the map, returning all key-value pairs as an iterator. Keeps the hashmap for reuse.
617
618        Example
619        -------
620        ```py
621        map = HashMap.from_mut()
622        map.insert(0, "a")
623        map.drain().collect() == [(0, "a")]
624        assert map.is_empty()
625        ```
626        """
627        return Drain(self._source)
628
629    def extract_if(self, pred: Fn[K, V]) -> ExtractIf[K, V]:
630        """Creates an iterator which uses a closure to determine if an element should be removed.
631
632        If the closure returns true, the element is removed from the map and yielded. If the closure returns false,
633        the element remains in the map and will not be yielded.
634
635        If you don't need at iterator, use `retain` instead.
636
637        Example
638        -------
639        ```py
640        odds = HashMap.from_mut({0: 0, 1: 1, 2: 2, 3: 3, 4: 4})
641        evens = odds.extract_if(lambda k, _v: k % 2 == 0).collect()
642        assert odds == {1: 1, 3: 3}
643        assert evens == [(0, 0), (2, 2), (4, 4)]
644        ```
645        """
646        return ExtractIf(self._source, pred)
647
648    def __repr__(self) -> str:
649        return self._source.__repr__()
650
651    def __setitem__(self, key: K, value: V, /) -> None:
652        self._source[key] = value
653
654    def __delitem__(self, key: K, /) -> None:
655        del self._source[key]
656
657    def __iter__(self) -> collections.Iterator[K]:
658        return self._source.__iter__()
659
660    def __len__(self) -> int:
661        return self._source.__len__()
@rustc_diagnostic_item('HashMap')
class HashMap(sain.collections.hash_map._RawMap[~K, ~V], sain.default.Default[ForwardRef('HashMap[K, V]')]):
398@rustc_diagnostic_item("HashMap")
399class HashMap(_RawMap[K, V], Default["HashMap[K, V]"]):
400    """An immutable key-value dictionary.
401
402    But default, `HashMap` is immutable, it cannot change its values after initializing it. however,
403    you can return a mutable reference to this hashmap via `HashMap.as_mut` method, it returns a reference to the underlying map.
404
405    Example
406    -------
407    ```py
408    # initialize it with a source. after that, item insertion is not allowed.
409    books: HashMap[str, str] = HashMap({
410        "Adventures of Huckleberry Finn": "My favorite book."
411    })
412    # get a mutable reference to it to be able to.
413    books_mut = books.as_mut()
414    # You can either call `.insert`, which is similar to Rust's.
415    books_mut.insert(
416        "Grimms Fairy Tales",
417        "Masterpiece.",
418    )
419    # Or via item assignments
420    books_mut["Pride and Prejudice"] = "Very enjoyable."
421    print(books)
422
423    for book, review in books.items():
424        print(book, review)
425    ```
426
427    Parameters
428    ----------
429    source: `dict[K, V]`
430        A dictionary to point to. this will be used as the underlying source.
431    """
432
433    __slots__ = ("_source",)
434
435    def __init__(self, source: dict[K, V] | None = None, /) -> None:
436        super().__init__(source)
437
438    @staticmethod
439    def default() -> HashMap[K, V]:
440        """Creates an empty `HashMap<K, V>`."""
441        return HashMap()
442
443    @classmethod
444    def from_mut(cls, source: dict[K, V] | None = None, /) -> RefMut[K, V]:
445        """Create a new mutable `HashMap`, with the given source if available.
446
447        Example
448        -------
449        ```py
450        books = HashMap.from_mut()
451        books[0] = "Twilight"
452        ```
453        """
454        return RefMut(source or {})
455
456    @staticmethod
457    def from_keys_mut(*iterable: tuple[K, V]) -> RefMut[K, V]:
458        """Create a new mutable `HashMap` from a sequence of key-value pairs.
459
460        Example
461        -------
462        ```py
463        tier_list = [
464            ("S", "Top tier"),
465            ("A", "Very Good"),
466            ("B", "Good"),
467            ("C", "Average"),
468            ("D", "dodo")
469        ]
470        mapping: RefMut[str, str] = HashMap.from_keys_mut(*tier_list)
471        ```
472        """
473        return RefMut({k: v for k, v in iterable})
474
475    def as_mut(self) -> RefMut[K, V]:
476        """Get a mutable reference to this hash map.
477
478        Example
479        -------
480        ```py
481        map: HashMap[str, float] = HashMap()
482
483        # Get a reference to map
484        mut = map.as_mut()
485        mut.insert("sqrt", 1.0)
486        del mut # not needed anymore
487
488        assert map == {"sqrt": 1.0}
489        ```
490        """
491        return RefMut(self._source)

An immutable key-value dictionary.

But default, HashMap is immutable, it cannot change its values after initializing it. however, you can return a mutable reference to this hashmap via HashMap.as_mut method, it returns a reference to the underlying map.

Example
# initialize it with a source. after that, item insertion is not allowed.
books: HashMap[str, str] = HashMap({
    "Adventures of Huckleberry Finn": "My favorite book."
})
# get a mutable reference to it to be able to.
books_mut = books.as_mut()
# You can either call `.insert`, which is similar to Rust's.
books_mut.insert(
    "Grimms Fairy Tales",
    "Masterpiece.",
)
# Or via item assignments
books_mut["Pride and Prejudice"] = "Very enjoyable."
print(books)

for book, review in books.items():
    print(book, review)
Parameters
  • source (dict[K, V]): A dictionary to point to. this will be used as the underlying source.
  • # Implementations
  • **This class implements HashMap:
HashMap(source: dict[~K, ~V] | None = None, /)
435    def __init__(self, source: dict[K, V] | None = None, /) -> None:
436        super().__init__(source)
@staticmethod
def default() -> HashMap[~K, ~V]:
438    @staticmethod
439    def default() -> HashMap[K, V]:
440        """Creates an empty `HashMap<K, V>`."""
441        return HashMap()

Creates an empty HashMap<K, V>.

@classmethod
def from_mut( cls, source: dict[~K, ~V] | None = None, /) -> RefMut[~K, ~V]:
443    @classmethod
444    def from_mut(cls, source: dict[K, V] | None = None, /) -> RefMut[K, V]:
445        """Create a new mutable `HashMap`, with the given source if available.
446
447        Example
448        -------
449        ```py
450        books = HashMap.from_mut()
451        books[0] = "Twilight"
452        ```
453        """
454        return RefMut(source or {})

Create a new mutable HashMap, with the given source if available.

Example
books = HashMap.from_mut()
books[0] = "Twilight"
@staticmethod
def from_keys_mut(*iterable: tuple[~K, ~V]) -> RefMut[~K, ~V]:
456    @staticmethod
457    def from_keys_mut(*iterable: tuple[K, V]) -> RefMut[K, V]:
458        """Create a new mutable `HashMap` from a sequence of key-value pairs.
459
460        Example
461        -------
462        ```py
463        tier_list = [
464            ("S", "Top tier"),
465            ("A", "Very Good"),
466            ("B", "Good"),
467            ("C", "Average"),
468            ("D", "dodo")
469        ]
470        mapping: RefMut[str, str] = HashMap.from_keys_mut(*tier_list)
471        ```
472        """
473        return RefMut({k: v for k, v in iterable})

Create a new mutable HashMap from a sequence of key-value pairs.

Example
tier_list = [
    ("S", "Top tier"),
    ("A", "Very Good"),
    ("B", "Good"),
    ("C", "Average"),
    ("D", "dodo")
]
mapping: RefMut[str, str] = HashMap.from_keys_mut(*tier_list)
def as_mut(self) -> RefMut[~K, ~V]:
475    def as_mut(self) -> RefMut[K, V]:
476        """Get a mutable reference to this hash map.
477
478        Example
479        -------
480        ```py
481        map: HashMap[str, float] = HashMap()
482
483        # Get a reference to map
484        mut = map.as_mut()
485        mut.insert("sqrt", 1.0)
486        del mut # not needed anymore
487
488        assert map == {"sqrt": 1.0}
489        ```
490        """
491        return RefMut(self._source)

Get a mutable reference to this hash map.

Example
map: HashMap[str, float] = HashMap()

# Get a reference to map
mut = map.as_mut()
mut.insert("sqrt", 1.0)
del mut # not needed anymore

assert map == {"sqrt": 1.0}
@typing.final
class RefMut(sain.collections.hash_map._RawMap[~K, ~V], collections.abc.MutableMapping[~K, ~V], sain.default.Default[ForwardRef('RefMut[K, V]')]):
494@typing.final
495class RefMut(_RawMap[K, V], collections.MutableMapping[K, V], Default["RefMut[K, V]"]):
496    """A reference to a mutable dictionary / hashmap.
497
498    Ideally, you want to use `HashMap.as_mut()` / `HashMap.from_mut` methods to create this.
499    """
500
501    __slots__ = ("_source",)
502
503    def __init__(self, source: dict[K, V], /) -> None:
504        super().__init__(source)
505        self._source = source
506
507    @staticmethod
508    def default() -> RefMut[K, V]:
509        """Creates a new, mutable, empty `RefMut<K, V>`."""
510        return RefMut({})
511
512    def insert(self, key: K, value: V) -> Option[V]:
513        """Insert a key/value pair into the map.
514
515        if `key` is not present in the map, `None` is returned. otherwise, the value is updated, and the old value
516        is returned.
517
518        Example
519        -------
520        ```py
521        users = HashMap.from_mut({0: "admin"})
522        assert users.insert(1, "admin").is_none()
523        old = users.insert(0, "normal").unwrap()
524        assert old == "admin"
525        ```
526        """
527        if key not in self:
528            self[key] = value
529            return option.NOTHING
530
531        old = self[key]
532        self[key] = value
533        return option.Some(old)
534
535    def try_insert(self, key: K, value: V) -> Result[V, K]:
536        """Tries to insert `key` and `value` into the map, returning `Err(key)` if `key` is already present.
537
538        Example
539        -------
540        ```py
541        users = HashMap.from_mut({0: "admin"})
542        assert users.try_insert(1, "claudia").is_ok()
543        assert users.try_insert(0, "doppler").is_err()
544        ```
545        """
546        if key in self:
547            return Err(key)
548
549        self[key] = value
550        return Ok(value)
551
552    def remove(self, key: K) -> Option[V]:
553        """Remove a key from the map, returning the value of the key if it was previously in the map.
554
555        Example
556        -------
557        ```py
558        map = HashMap.from_mut()
559        map.insert(0, "a")
560        map.remove(0).unwrap() == "a"
561        map.remove(0).is_none()
562        ```
563        """
564        if key not in self:
565            return option.NOTHING
566
567        v = self[key]
568        del self[key]
569        return option.Some(v)
570
571    def remove_entry(self, key: K) -> Option[tuple[K, V]]:
572        """Remove a key from the map, returning the key and value if it was previously in the map.
573
574        Example
575        -------
576        ```py
577        map = HashMap.from_mut()
578        map[0] = "a"
579        assert map.remove_entry(0).unwrap() == (0, "a")
580        assert map.remove_entry(0).is_none()
581        ```
582        """
583        if key not in self:
584            return option.NOTHING
585
586        v = self[key]
587        del self[key]
588        return option.Some((key, v))
589
590    def retain(self, f: collections.Callable[[K, V], bool]) -> None:
591        """Remove items from this map based on `f(key, value)` returning `False`.
592
593        Example
594        -------
595        ```py
596        users = HashMap.from_mut({
597            "user1": "admin",
598            "user2": "admin",
599            "user3": "regular",
600            "jack": "admin"
601        })
602        users.retain(
603            lambda user, role: role == "admin" and
604            user.startswith("user")
605        )
606        for user, role in users.items():
607            print(user, role)
608
609        # user1 admin
610        # user2 admin
611        """
612        for k, v in self._source.copy().items():
613            if not f(k, v):
614                del self[k]
615
616    def drain(self) -> Drain[K, V]:
617        """Clears the map, returning all key-value pairs as an iterator. Keeps the hashmap for reuse.
618
619        Example
620        -------
621        ```py
622        map = HashMap.from_mut()
623        map.insert(0, "a")
624        map.drain().collect() == [(0, "a")]
625        assert map.is_empty()
626        ```
627        """
628        return Drain(self._source)
629
630    def extract_if(self, pred: Fn[K, V]) -> ExtractIf[K, V]:
631        """Creates an iterator which uses a closure to determine if an element should be removed.
632
633        If the closure returns true, the element is removed from the map and yielded. If the closure returns false,
634        the element remains in the map and will not be yielded.
635
636        If you don't need at iterator, use `retain` instead.
637
638        Example
639        -------
640        ```py
641        odds = HashMap.from_mut({0: 0, 1: 1, 2: 2, 3: 3, 4: 4})
642        evens = odds.extract_if(lambda k, _v: k % 2 == 0).collect()
643        assert odds == {1: 1, 3: 3}
644        assert evens == [(0, 0), (2, 2), (4, 4)]
645        ```
646        """
647        return ExtractIf(self._source, pred)
648
649    def __repr__(self) -> str:
650        return self._source.__repr__()
651
652    def __setitem__(self, key: K, value: V, /) -> None:
653        self._source[key] = value
654
655    def __delitem__(self, key: K, /) -> None:
656        del self._source[key]
657
658    def __iter__(self) -> collections.Iterator[K]:
659        return self._source.__iter__()
660
661    def __len__(self) -> int:
662        return self._source.__len__()

A reference to a mutable dictionary / hashmap.

Ideally, you want to use HashMap.as_mut() / HashMap.from_mut methods to create this.

RefMut(source: dict[~K, ~V], /)
503    def __init__(self, source: dict[K, V], /) -> None:
504        super().__init__(source)
505        self._source = source
@staticmethod
def default() -> RefMut[~K, ~V]:
507    @staticmethod
508    def default() -> RefMut[K, V]:
509        """Creates a new, mutable, empty `RefMut<K, V>`."""
510        return RefMut({})

Creates a new, mutable, empty RefMut<K, V>.

def insert(self, key: ~K, value: ~V) -> 'Option[V]':
512    def insert(self, key: K, value: V) -> Option[V]:
513        """Insert a key/value pair into the map.
514
515        if `key` is not present in the map, `None` is returned. otherwise, the value is updated, and the old value
516        is returned.
517
518        Example
519        -------
520        ```py
521        users = HashMap.from_mut({0: "admin"})
522        assert users.insert(1, "admin").is_none()
523        old = users.insert(0, "normal").unwrap()
524        assert old == "admin"
525        ```
526        """
527        if key not in self:
528            self[key] = value
529            return option.NOTHING
530
531        old = self[key]
532        self[key] = value
533        return option.Some(old)

Insert a key/value pair into the map.

if key is not present in the map, None is returned. otherwise, the value is updated, and the old value is returned.

Example
users = HashMap.from_mut({0: "admin"})
assert users.insert(1, "admin").is_none()
old = users.insert(0, "normal").unwrap()
assert old == "admin"
def try_insert(self, key: ~K, value: ~V) -> 'Result[V, K]':
535    def try_insert(self, key: K, value: V) -> Result[V, K]:
536        """Tries to insert `key` and `value` into the map, returning `Err(key)` if `key` is already present.
537
538        Example
539        -------
540        ```py
541        users = HashMap.from_mut({0: "admin"})
542        assert users.try_insert(1, "claudia").is_ok()
543        assert users.try_insert(0, "doppler").is_err()
544        ```
545        """
546        if key in self:
547            return Err(key)
548
549        self[key] = value
550        return Ok(value)

Tries to insert key and value into the map, returning Err(key) if key is already present.

Example
users = HashMap.from_mut({0: "admin"})
assert users.try_insert(1, "claudia").is_ok()
assert users.try_insert(0, "doppler").is_err()
def remove(self, key: ~K) -> 'Option[V]':
552    def remove(self, key: K) -> Option[V]:
553        """Remove a key from the map, returning the value of the key if it was previously in the map.
554
555        Example
556        -------
557        ```py
558        map = HashMap.from_mut()
559        map.insert(0, "a")
560        map.remove(0).unwrap() == "a"
561        map.remove(0).is_none()
562        ```
563        """
564        if key not in self:
565            return option.NOTHING
566
567        v = self[key]
568        del self[key]
569        return option.Some(v)

Remove a key from the map, returning the value of the key if it was previously in the map.

Example
map = HashMap.from_mut()
map.insert(0, "a")
map.remove(0).unwrap() == "a"
map.remove(0).is_none()
def remove_entry(self, key: ~K) -> 'Option[tuple[K, V]]':
571    def remove_entry(self, key: K) -> Option[tuple[K, V]]:
572        """Remove a key from the map, returning the key and value if it was previously in the map.
573
574        Example
575        -------
576        ```py
577        map = HashMap.from_mut()
578        map[0] = "a"
579        assert map.remove_entry(0).unwrap() == (0, "a")
580        assert map.remove_entry(0).is_none()
581        ```
582        """
583        if key not in self:
584            return option.NOTHING
585
586        v = self[key]
587        del self[key]
588        return option.Some((key, v))

Remove a key from the map, returning the key and value if it was previously in the map.

Example
map = HashMap.from_mut()
map[0] = "a"
assert map.remove_entry(0).unwrap() == (0, "a")
assert map.remove_entry(0).is_none()
def retain(self, f: Callable[[~K, ~V], bool]) -> None:
590    def retain(self, f: collections.Callable[[K, V], bool]) -> None:
591        """Remove items from this map based on `f(key, value)` returning `False`.
592
593        Example
594        -------
595        ```py
596        users = HashMap.from_mut({
597            "user1": "admin",
598            "user2": "admin",
599            "user3": "regular",
600            "jack": "admin"
601        })
602        users.retain(
603            lambda user, role: role == "admin" and
604            user.startswith("user")
605        )
606        for user, role in users.items():
607            print(user, role)
608
609        # user1 admin
610        # user2 admin
611        """
612        for k, v in self._source.copy().items():
613            if not f(k, v):
614                del self[k]

Remove items from this map based on f(key, value) returning False.

Example

```py users = HashMap.from_mut({ "user1": "admin", "user2": "admin", "user3": "regular", "jack": "admin" }) users.retain( lambda user, role: role == "admin" and user.startswith("user") ) for user, role in users.items(): print(user, role)

user1 admin

user2 admin

def drain(self) -> sain.collections.hash_map.base_iter.Drain[~K, ~V]:
616    def drain(self) -> Drain[K, V]:
617        """Clears the map, returning all key-value pairs as an iterator. Keeps the hashmap for reuse.
618
619        Example
620        -------
621        ```py
622        map = HashMap.from_mut()
623        map.insert(0, "a")
624        map.drain().collect() == [(0, "a")]
625        assert map.is_empty()
626        ```
627        """
628        return Drain(self._source)

Clears the map, returning all key-value pairs as an iterator. Keeps the hashmap for reuse.

Example
map = HashMap.from_mut()
map.insert(0, "a")
map.drain().collect() == [(0, "a")]
assert map.is_empty()
def extract_if( self, pred: Callable[[~K, ~V], bool]) -> sain.collections.hash_map.base_iter.ExtractIf[~K, ~V]:
630    def extract_if(self, pred: Fn[K, V]) -> ExtractIf[K, V]:
631        """Creates an iterator which uses a closure to determine if an element should be removed.
632
633        If the closure returns true, the element is removed from the map and yielded. If the closure returns false,
634        the element remains in the map and will not be yielded.
635
636        If you don't need at iterator, use `retain` instead.
637
638        Example
639        -------
640        ```py
641        odds = HashMap.from_mut({0: 0, 1: 1, 2: 2, 3: 3, 4: 4})
642        evens = odds.extract_if(lambda k, _v: k % 2 == 0).collect()
643        assert odds == {1: 1, 3: 3}
644        assert evens == [(0, 0), (2, 2), (4, 4)]
645        ```
646        """
647        return ExtractIf(self._source, pred)

Creates an iterator which uses a closure to determine if an element should be removed.

If the closure returns true, the element is removed from the map and yielded. If the closure returns false, the element remains in the map and will not be yielded.

If you don't need at iterator, use retain instead.

Example
odds = HashMap.from_mut({0: 0, 1: 1, 2: 2, 3: 3, 4: 4})
evens = odds.extract_if(lambda k, _v: k % 2 == 0).collect()
assert odds == {1: 1, 3: 3}
assert evens == [(0, 0), (2, 2), (4, 4)]