Skip to content

Commit 855a7a2

Browse files
authored
Add files via upload
1 parent ecaaf84 commit 855a7a2

1 file changed

Lines changed: 382 additions & 0 deletions

File tree

jdr_lib/container.py

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
"""
2+
Container classes.
3+
"""
4+
5+
from bisect import bisect_left, bisect_right
6+
from operator import itemgetter
7+
from collections.abc import Sequence, MutableSequence, MutableMapping
8+
9+
class SortedList(Sequence):
10+
"""Sequence sorted by a key function.
11+
12+
SortedList() is much easier to work with than using bisect() directly.
13+
It supports key functions like those use in sorted(), min(), and max().
14+
The result of the key function call is saved so that keys can be searched
15+
efficiently.
16+
17+
Instead of returning an insertion-point which can be hard to interpret, the
18+
five find-methods return a specific item in the sequence. They can scan for
19+
exact matches, the last item less-than-or-equal to a key, or the first item
20+
greater-than-or-equal to a key.
21+
22+
Once found, an item's ordinal position can be located with the index()
23+
method. New items can be added with the insert() and insert_right()
24+
methods. Old items can be deleted with the remove() method.
25+
26+
The usual sequence methods are provided to support indexing, slicing,
27+
length lookup, clearing, copying, forward and reverse iteration, contains
28+
checking, item counts, item removal, and a nice looking repr.
29+
30+
Finding and indexing are O(log n) operations while iteration and insertion
31+
are O(n). The initial sort is O(n log n).
32+
33+
The key function is stored in the 'key' attibute for easy introspection or
34+
so that you can assign a new key function (triggering an automatic re-sort).
35+
36+
In short, the class was designed to handle all of the common use cases for
37+
bisect but with a simpler API and support for key functions.
38+
39+
Attributes:
40+
key: the key function used for sorting
41+
42+
Usage:
43+
>>> from pprint import pprint
44+
>>> from operator import itemgetter
45+
46+
>>> s = SortedList(key=itemgetter(2))
47+
>>> for record in [
48+
... ('roger', 'young', 30),
49+
... ('angela', 'jones', 28),
50+
... ('bill', 'smith', 22),
51+
... ('david', 'thomas', 32)]:
52+
... s.insert(record)
53+
54+
>>> pprint(list(s)) # show records sorted by age
55+
[('bill', 'smith', 22),
56+
('angela', 'jones', 28),
57+
('roger', 'young', 30),
58+
('david', 'thomas', 32)]
59+
60+
>>> s.find_le(29) # find oldest person aged 29 or younger
61+
('angela', 'jones', 28)
62+
>>> s.find_lt(28) # find oldest person under 28
63+
('bill', 'smith', 22)
64+
>>> s.find_gt(28) # find youngest person over 28
65+
('roger', 'young', 30)
66+
67+
>>> r = s.find_ge(32) # find youngest person aged 32 or older
68+
>>> s.index(r) # get the index of their record
69+
3
70+
>>> s[3] # fetch the record at that index
71+
('david', 'thomas', 32)
72+
73+
>>> s.key = itemgetter(0) # now sort by first name
74+
>>> pprint(list(s))
75+
[('angela', 'jones', 28),
76+
('bill', 'smith', 22),
77+
('david', 'thomas', 32),
78+
('roger', 'young', 30)]
79+
80+
From http://code.activestate.com/recipes/577197-sortedcollection/
81+
"""
82+
83+
def __init__(self, iterable=(), key=None):
84+
"""Creates a new SortedList object.
85+
86+
Creates a SortedList object from an iterable. The iterable is sorted
87+
using the key function, if given. The default key function is the
88+
identity function, i.e. an object is its own key.
89+
90+
Args:
91+
iterable: Add the values of this iterable to the SortedList.
92+
key: A key function like those used in sorted(), min(), etc.
93+
Returns:
94+
A new SortedList object.
95+
"""
96+
self._given_key = None
97+
self._keys = None
98+
self._items = None
99+
self._key = None
100+
self._sortedlist_init(iterable, key)
101+
102+
def _sortedlist_init(self, iterable=(), key=None):
103+
"""Actually initialize the object.
104+
105+
This is here in case a subclass redefines the __init__() signature.
106+
"""
107+
self._given_key = key
108+
key = (lambda x: x) if key is None else key
109+
decorated = sorted(((key(item), item) for item in iterable),
110+
key=itemgetter(0))
111+
self._keys = [k for k, item in decorated]
112+
self._items = [item for k, item in decorated]
113+
self._key = key
114+
115+
@property
116+
def key(self):
117+
"""Returns the current key function"""
118+
return self._key
119+
@key.setter
120+
def key(self, key):
121+
"""Update self.key, triggering a resort."""
122+
if key is not self._key:
123+
self._sortedlist_init(self._items, key)
124+
@key.deleter
125+
def key(self):
126+
"""Reset to the default key function, triggering a resort."""
127+
self.key = None
128+
129+
def re_sort(self):
130+
"""Update keys and re-sort the list."""
131+
self._sortedlist_init(self._items, self.key)
132+
133+
def clear(self):
134+
"""Delete all contained objects."""
135+
self._sortedlist_init([], self._key)
136+
137+
def copy(self):
138+
"""Performs a shallow copy of this object."""
139+
return self.__class__(self, self._key)
140+
141+
def __eq__(self, other):
142+
#pylint: disable=protected-access
143+
return self._items == other._items
144+
145+
def __ne__(self, other):
146+
#pylint: disable=protected-access
147+
return self._items != other._items
148+
149+
def __len__(self):
150+
return len(self._items)
151+
152+
def __getitem__(self, i):
153+
return self._items[i]
154+
155+
def __delitem__(self, i):
156+
del self._keys[i]
157+
del self._items[i]
158+
159+
def __iter__(self):
160+
return iter(self._items)
161+
162+
def __reversed__(self):
163+
return reversed(self._items)
164+
165+
def __repr__(self):
166+
return '{}({!r}, key={})' \
167+
.format(self.__class__.__name__, self._items,
168+
getattr(self._given_key, '__name__', repr(self._given_key)))
169+
170+
def __reduce__(self):
171+
return self.__class__, (self._items, self._given_key)
172+
173+
def __contains__(self, item):
174+
k = self._key(item)
175+
i = bisect_left(self._keys, k)
176+
j = bisect_right(self._keys, k)
177+
return item in self._items[i:j]
178+
179+
def index(self, item):
180+
"""Find the position of an item. Raise ValueError if not found."""
181+
k = self._key(item)
182+
i = bisect_left(self._keys, k)
183+
j = bisect_right(self._keys, k)
184+
return self._items.index(item, i, j)
185+
186+
def count(self, item):
187+
"""Return number of occurrences of item"""
188+
k = self._key(item)
189+
i = bisect_left(self._keys, k)
190+
j = bisect_right(self._keys, k)
191+
return self._items[i:j].count(item)
192+
193+
def insert(self, item):
194+
"""Insert a new item. If equal keys are found, add to the left"""
195+
k = self._key(item)
196+
i = bisect_left(self._keys, k)
197+
self._keys.insert(i, k)
198+
self._items.insert(i, item)
199+
200+
def insert_right(self, item):
201+
"""Insert a new item. If equal keys are found, add to the right"""
202+
k = self._key(item)
203+
i = bisect_right(self._keys, k)
204+
self._keys.insert(i, k)
205+
self._items.insert(i, item)
206+
207+
def remove(self, item):
208+
"""Remove first occurence of item. Raise ValueError if not found"""
209+
i = self.index(item)
210+
del self._keys[i]
211+
del self._items[i]
212+
213+
def find(self, k):
214+
"""Return first item with a key == k. Raise ValueError if not found."""
215+
i = bisect_left(self._keys, k)
216+
if i != len(self) and self._keys[i] == k:
217+
return self._items[i]
218+
raise ValueError('No item found with key equal to: {!r}'.format(k))
219+
220+
def index_le(self, k):
221+
"""Return the index of the last item with key <= k"""
222+
i = bisect_right(self._keys, k)
223+
if i:
224+
return i-1
225+
raise ValueError('No item found with key at or below: {!r}'.format(k))
226+
227+
def find_le(self, k):
228+
"""Return last item with a key <= k. Raise ValueError if not found."""
229+
return self._items[self.index_le(k)]
230+
231+
def index_lt(self, k):
232+
"""Return index of the last item with key < k"""
233+
i = bisect_left(self._keys, k)
234+
if i:
235+
return i-1
236+
raise ValueError('No item found with key below: {!r}'.format(k))
237+
238+
def find_lt(self, k):
239+
"""Return last item with a key < k. Raise ValueError if not found."""
240+
return self._items[self.index_lt(k)]
241+
242+
def index_ge(self, k):
243+
"""Return index of the first item with key >= k"""
244+
i = bisect_left(self._keys, k)
245+
if i != len(self):
246+
return i
247+
raise ValueError('No item found with key at or above: {!r}'.format(k))
248+
249+
def find_ge(self, k):
250+
"""Return first item with a key >= k. Raise ValueError if not found"""
251+
return self._items[self.index_ge(k)]
252+
253+
def index_gt(self, k):
254+
"""Return the index of the first item with key > k"""
255+
i = bisect_right(self._keys, k)
256+
if i != len(self):
257+
return i
258+
raise ValueError('No item found with key above: {!r}'.format(k))
259+
260+
def find_gt(self, k):
261+
"""Return first item with a key > k. Raise ValueError if not found"""
262+
return self._items[self.index_gt(k)]
263+
264+
265+
class ProtectedDict(MutableMapping):
266+
"""A dict-like class supporting custom a set item method.
267+
268+
Inheriting this class should behave exactly like inheriting dict, except all
269+
item mutator access (not including deleting items) is guaranteed to go
270+
through the __setitem__() method.
271+
272+
Attributes:
273+
_dict: the underlying dictionary
274+
"""
275+
276+
def __init__(self, **kwargs):
277+
self._dict = {}
278+
for key, val in kwargs.items():
279+
self[key] = val
280+
281+
# Override this
282+
def __setitem__(self, key, val):
283+
self._dict[key] = val
284+
285+
# Pure virtual
286+
def __getitem__(self, item):
287+
return self._dict[item]
288+
def __len__(self):
289+
return len(self._dict)
290+
def __iter__(self):
291+
return iter(self._dict)
292+
def __delitem__(self, item):
293+
del self._dict[item]
294+
295+
# Reimplemented
296+
def __contains__(self, item):
297+
return self._dict.__contains__(item)
298+
def __eq__(self, other):
299+
return self._dict.__eq__(other)
300+
def __ne__(self, other):
301+
return self._dict.__ne__(other)
302+
def keys(self):
303+
return self._dict.keys()
304+
def items(self):
305+
return self._dict.items()
306+
def values(self):
307+
return self._dict.values()
308+
def get(self, key, default=None):
309+
return self._dict.get(key, default)
310+
def clear(self):
311+
self._dict.clear()
312+
def setdefault(self, key, default=None):
313+
self._dict.setdefault(key, default)
314+
315+
# New
316+
def __repr__(self):
317+
return "{}({!r})".format(self.__class__.__name__, self._dict)
318+
def __str__(self):
319+
return repr(self)
320+
321+
322+
class ProtectedList(MutableSequence):
323+
"""A list-like class supporting a custom set item method.
324+
325+
Inheriting this class should behave like inheriting list, except all item
326+
mutator access (not including reordering or deleting items) is guaranteed to
327+
go through the __setitem__() or insert() methods.
328+
329+
Attributes:
330+
_list: the underlying list
331+
"""
332+
333+
def __init__(self, iterable=None):
334+
self._list = []
335+
if iterable:
336+
for thing in iterable:
337+
self.append(thing)
338+
339+
# Override these
340+
def __setitem__(self, i, val):
341+
# Default behavior
342+
self._list[i] = val
343+
def insert(self, i, item):
344+
"Insert item before index i"
345+
# Default behavior
346+
self._list.insert(i, item)
347+
348+
# Pure virtual
349+
def __getitem__(self, item):
350+
return self._list[item]
351+
def __len__(self):
352+
return len(self._list)
353+
def __delitem__(self, i):
354+
del self._list[i]
355+
356+
# Reimplemented
357+
def __contains__(self, item):
358+
return item in self._list
359+
def __iter__(self):
360+
return iter(self._list)
361+
def __reversed__(self):
362+
return reversed(self._list)
363+
def index(self, value):
364+
return self._list.index(value)
365+
def count(self, value):
366+
return self._list.count(value)
367+
def reverse(self):
368+
self._list.reverse()
369+
def pop(self, index=-1):
370+
return self._list.pop(index)
371+
def remove(self, value):
372+
self._list.remove(value)
373+
374+
# New
375+
def __repr__(self):
376+
return "{}({!r})".format(self.__class__.__name__, self._list)
377+
def __str__(self):
378+
return repr(self)
379+
380+
381+
__all__ = ['SortedList', 'ProtectedDict', 'ProtectedList']
382+

0 commit comments

Comments
 (0)