Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/cardinal_pythonlib/betweendict.py : 55%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# cardinal_pythonlib/betweendict.py
4"""
5===============================================================================
7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com).
9 This file is part of cardinal_pythonlib.
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 https://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
23===============================================================================
25**Implements BetweenDict.***
27"""
29from typing import Dict
32# =============================================================================
33# Range dictionary for comparisons
34# =============================================================================
36class BetweenDict(dict):
37 # noinspection HttpUrlsUsage
38 """
39 Range dictionary for comparisons.
41 - Create it with **keys** that are tuples/lists of length 2, such as
42 ``(lower, upper)``, and **values** that are the values
43 that this particular range should be mapped to.
44 - Access it using a specific test value as the key, and the result will be
45 the value for an appropriate range (or :exc:`KeyError` will be raised if
46 none of the ranges contain the test value).
47 - The test is ``lower <= testvalue < upper``, i.e. ranges of ``[lower,
48 upper)``.
50 Example:
52 .. code-block:: python
54 bd = BetweenDict({
55 # tests a <= x < b
56 (100, float("inf")): "very high", # from Python 3.5, can use math.inf
57 (75, 100): "high",
58 (50, 75): "medium",
59 (25, 50): "low",
60 (0, 25): "very low",
61 })
63 bd[500] # 'very high'
64 bd[80] # 'high'
65 bd[50] # 'medium'
66 bd[-5] # raises KeyError
68 Various implementation alternatives:
70 - http://joshuakugler.com/archives/30-BetweenDict,-a-Python-dict-for-value-ranges.html
71 ... NB has initialization default argument bug
72 - https://pypi.python.org/pypi/rangedict/0.1.5
73 - https://stackoverflow.com/questions/30254739/is-there-a-library-implemented-rangedict-in-python
74 """ # noqa
75 INVALID_MSG_TYPE = "Key must be an iterable with length 2"
76 INVALID_MSG_VALUE = "First element of key must be less than second element"
78 # noinspection PyMissingConstructor
79 def __init__(self, d: Dict = None) -> None:
80 d = d or {}
81 for k, v in d.items():
82 self[k] = v
84 def __getitem__(self, key):
85 for k, v in self.items():
86 if k[0] <= key < k[1]:
87 return v
88 raise KeyError(f"Key {key!r} is not in any ranges")
90 def __setitem__(self, key, value):
91 try:
92 if len(key) != 2:
93 raise ValueError(self.INVALID_MSG_TYPE)
94 except TypeError:
95 raise TypeError(self.INVALID_MSG_TYPE)
96 if key[0] < key[1]:
97 super().__setitem__((key[0], key[1]), value)
98 else:
99 raise RuntimeError(self.INVALID_MSG_VALUE)
101 def __contains__(self, key):
102 try:
103 # noinspection PyStatementEffect
104 self[key]
105 return True
106 except KeyError:
107 return False