Source code for cardinal_pythonlib.betweendict

#!/usr/bin/env python
# cardinal_pythonlib/betweendict.py

"""
===============================================================================

    Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).

    This file is part of cardinal_pythonlib.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        https://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

===============================================================================

**Implements BetweenDict.***

"""

from typing import Dict


# =============================================================================
# Range dictionary for comparisons
# =============================================================================


[docs]class BetweenDict(dict): # noinspection HttpUrlsUsage """ Range dictionary for comparisons. - Create it with **keys** that are tuples/lists of length 2, such as ``(lower, upper)``, and **values** that are the values that this particular range should be mapped to. - Access it using a specific test value as the key, and the result will be the value for an appropriate range (or :exc:`KeyError` will be raised if none of the ranges contain the test value). - The test is ``lower <= testvalue < upper``, i.e. ranges of ``[lower, upper)``. Example: .. code-block:: python bd = BetweenDict({ # tests a <= x < b (100, float("inf")): "very high", # from Python 3.5, can use math.inf (75, 100): "high", (50, 75): "medium", (25, 50): "low", (0, 25): "very low", }) bd[500] # 'very high' bd[80] # 'high' bd[50] # 'medium' bd[-5] # raises KeyError Various implementation alternatives: - http://joshuakugler.com/archives/30-BetweenDict,-a-Python-dict-for-value-ranges.html ... NB has initialization default argument bug - https://pypi.python.org/pypi/rangedict/0.1.5 - https://stackoverflow.com/questions/30254739/is-there-a-library-implemented-rangedict-in-python """ # noqa INVALID_MSG_TYPE = "Key must be an iterable with length 2" INVALID_MSG_VALUE = "First element of key must be less than second element" # noinspection PyMissingConstructor def __init__(self, d: Dict = None) -> None: d = d or {} for k, v in d.items(): self[k] = v def __getitem__(self, key): for k, v in self.items(): if k[0] <= key < k[1]: return v raise KeyError(f"Key {key!r} is not in any ranges") def __setitem__(self, key, value): try: if len(key) != 2: raise ValueError(self.INVALID_MSG_TYPE) except TypeError: raise TypeError(self.INVALID_MSG_TYPE) if key[0] < key[1]: super().__setitem__((key[0], key[1]), value) else: raise RuntimeError(self.INVALID_MSG_VALUE) def __contains__(self, key): try: # noinspection PyStatementEffect self[key] return True except KeyError: return False