|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 | 3 | import collections
|
4 |
| -import functools |
5 | 4 | import itertools
|
6 |
| -import operator |
7 |
| -from collections.abc import Mapping |
8 |
| -from typing import Any |
9 | 5 |
|
10 | 6 |
|
11 | 7 | # from jaraco.collections 3.5.1
|
@@ -60,144 +56,3 @@ def __contains__(self, other):
|
60 | 56 |
|
61 | 57 | def __len__(self):
|
62 | 58 | return len(list(iter(self)))
|
63 |
| - |
64 |
| - |
65 |
| -# from jaraco.collections 5.0.1 |
66 |
| -class RangeMap(dict): |
67 |
| - """ |
68 |
| - A dictionary-like object that uses the keys as bounds for a range. |
69 |
| - Inclusion of the value for that range is determined by the |
70 |
| - key_match_comparator, which defaults to less-than-or-equal. |
71 |
| - A value is returned for a key if it is the first key that matches in |
72 |
| - the sorted list of keys. |
73 |
| -
|
74 |
| - One may supply keyword parameters to be passed to the sort function used |
75 |
| - to sort keys (i.e. key, reverse) as sort_params. |
76 |
| -
|
77 |
| - Create a map that maps 1-3 -> 'a', 4-6 -> 'b' |
78 |
| -
|
79 |
| - >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy |
80 |
| - >>> r[1], r[2], r[3], r[4], r[5], r[6] |
81 |
| - ('a', 'a', 'a', 'b', 'b', 'b') |
82 |
| -
|
83 |
| - Even float values should work so long as the comparison operator |
84 |
| - supports it. |
85 |
| -
|
86 |
| - >>> r[4.5] |
87 |
| - 'b' |
88 |
| -
|
89 |
| - Notice that the way rangemap is defined, it must be open-ended |
90 |
| - on one side. |
91 |
| -
|
92 |
| - >>> r[0] |
93 |
| - 'a' |
94 |
| - >>> r[-1] |
95 |
| - 'a' |
96 |
| -
|
97 |
| - One can close the open-end of the RangeMap by using undefined_value |
98 |
| -
|
99 |
| - >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'}) |
100 |
| - >>> r[0] |
101 |
| - Traceback (most recent call last): |
102 |
| - ... |
103 |
| - KeyError: 0 |
104 |
| -
|
105 |
| - One can get the first or last elements in the range by using RangeMap.Item |
106 |
| -
|
107 |
| - >>> last_item = RangeMap.Item(-1) |
108 |
| - >>> r[last_item] |
109 |
| - 'b' |
110 |
| -
|
111 |
| - .last_item is a shortcut for Item(-1) |
112 |
| -
|
113 |
| - >>> r[RangeMap.last_item] |
114 |
| - 'b' |
115 |
| -
|
116 |
| - Sometimes it's useful to find the bounds for a RangeMap |
117 |
| -
|
118 |
| - >>> r.bounds() |
119 |
| - (0, 6) |
120 |
| -
|
121 |
| - RangeMap supports .get(key, default) |
122 |
| -
|
123 |
| - >>> r.get(0, 'not found') |
124 |
| - 'not found' |
125 |
| -
|
126 |
| - >>> r.get(7, 'not found') |
127 |
| - 'not found' |
128 |
| -
|
129 |
| - One often wishes to define the ranges by their left-most values, |
130 |
| - which requires use of sort params and a key_match_comparator. |
131 |
| -
|
132 |
| - >>> r = RangeMap({1: 'a', 4: 'b'}, |
133 |
| - ... sort_params=dict(reverse=True), |
134 |
| - ... key_match_comparator=operator.ge) |
135 |
| - >>> r[1], r[2], r[3], r[4], r[5], r[6] |
136 |
| - ('a', 'a', 'a', 'b', 'b', 'b') |
137 |
| -
|
138 |
| - That wasn't nearly as easy as before, so an alternate constructor |
139 |
| - is provided: |
140 |
| -
|
141 |
| - >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value}) |
142 |
| - >>> r[1], r[2], r[3], r[4], r[5], r[6] |
143 |
| - ('a', 'a', 'a', 'b', 'b', 'b') |
144 |
| -
|
145 |
| - """ |
146 |
| - |
147 |
| - def __init__( |
148 |
| - self, |
149 |
| - source, |
150 |
| - sort_params: Mapping[str, Any] = {}, |
151 |
| - key_match_comparator=operator.le, |
152 |
| - ): |
153 |
| - dict.__init__(self, source) |
154 |
| - self.sort_params = sort_params |
155 |
| - self.match = key_match_comparator |
156 |
| - |
157 |
| - @classmethod |
158 |
| - def left(cls, source): |
159 |
| - return cls( |
160 |
| - source, sort_params=dict(reverse=True), key_match_comparator=operator.ge |
161 |
| - ) |
162 |
| - |
163 |
| - def __getitem__(self, item): |
164 |
| - sorted_keys = sorted(self.keys(), **self.sort_params) |
165 |
| - if isinstance(item, RangeMap.Item): |
166 |
| - result = self.__getitem__(sorted_keys[item]) |
167 |
| - else: |
168 |
| - key = self._find_first_match_(sorted_keys, item) |
169 |
| - result = dict.__getitem__(self, key) |
170 |
| - if result is RangeMap.undefined_value: |
171 |
| - raise KeyError(key) |
172 |
| - return result |
173 |
| - |
174 |
| - def get(self, key, default=None): |
175 |
| - """ |
176 |
| - Return the value for key if key is in the dictionary, else default. |
177 |
| - If default is not given, it defaults to None, so that this method |
178 |
| - never raises a KeyError. |
179 |
| - """ |
180 |
| - try: |
181 |
| - return self[key] |
182 |
| - except KeyError: |
183 |
| - return default |
184 |
| - |
185 |
| - def _find_first_match_(self, keys, item): |
186 |
| - is_match = functools.partial(self.match, item) |
187 |
| - matches = list(filter(is_match, keys)) |
188 |
| - if matches: |
189 |
| - return matches[0] |
190 |
| - raise KeyError(item) |
191 |
| - |
192 |
| - def bounds(self): |
193 |
| - sorted_keys = sorted(self.keys(), **self.sort_params) |
194 |
| - return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item]) |
195 |
| - |
196 |
| - # some special values for the RangeMap |
197 |
| - undefined_value = type('RangeValueUndefined', (), {})() |
198 |
| - |
199 |
| - class Item(int): |
200 |
| - "RangeMap Item" |
201 |
| - |
202 |
| - first_item = Item(0) |
203 |
| - last_item = Item(-1) |
0 commit comments