Hide keyboard shortcuts

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 

3 

4""" 

5=============================================================================== 

6 

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

8 

9 This file is part of cardinal_pythonlib. 

10 

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 

14 

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

16 

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. 

22 

23=============================================================================== 

24 

25**Implements BetweenDict.*** 

26 

27""" 

28 

29from typing import Dict 

30 

31 

32# ============================================================================= 

33# Range dictionary for comparisons 

34# ============================================================================= 

35 

36class BetweenDict(dict): 

37 # noinspection HttpUrlsUsage 

38 """ 

39 Range dictionary for comparisons. 

40  

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)``. 

49  

50 Example: 

51  

52 .. code-block:: python 

53  

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 }) 

62  

63 bd[500] # 'very high' 

64 bd[80] # 'high' 

65 bd[50] # 'medium' 

66 bd[-5] # raises KeyError 

67  

68 Various implementation alternatives: 

69  

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" 

77 

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 

83 

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") 

89 

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) 

100 

101 def __contains__(self, key): 

102 try: 

103 # noinspection PyStatementEffect 

104 self[key] 

105 return True 

106 except KeyError: 

107 return False