|
5 | 5 |
|
6 | 6 |
|
7 | 7 | class SortedCollection(object):
|
8 |
| - """Sequence sorted by a key function. |
9 |
| -
|
10 |
| - SortedCollection() is much easier to work with than using bisect() directly. |
11 |
| - It supports key functions like those use in sorted(), min(), and max(). |
12 |
| - The result of the key function call is saved so that keys can be searched |
13 |
| - efficiently. |
14 |
| -
|
15 |
| - Instead of returning an insertion-point which can be hard to interpret, the |
16 |
| - five find-methods return a specific item in the sequence. They can scan for |
17 |
| - exact matches, the last item less-than-or-equal to a key, or the first item |
18 |
| - greater-than-or-equal to a key. |
19 |
| -
|
20 |
| - Once found, an item's ordinal position can be located with the index() method. |
21 |
| - New items can be added with the insert() and insert_right() methods. |
22 |
| - Old items can be deleted with the remove() method. |
23 |
| -
|
24 |
| - The usual sequence methods are provided to support indexing, slicing, |
25 |
| - length lookup, clearing, copying, forward and reverse iteration, contains |
26 |
| - checking, item counts, item removal, and a nice looking repr. |
27 |
| -
|
28 |
| - Finding and indexing are O(log n) operations while iteration and insertion |
29 |
| - are O(n). The initial sort is O(n log n). |
30 |
| -
|
31 |
| - The key function is stored in the 'key' attribute for easy introspection or |
32 |
| - so that you can assign a new key function (triggering an automatic re-sort). |
33 |
| -
|
34 |
| - In short, the class was designed to handle all of the common use cases for |
35 |
| - bisect but with a simpler API and support for key functions. |
36 |
| -
|
37 |
| - >>> from pprint import pprint |
38 |
| - >>> from operator import itemgetter |
39 |
| -
|
40 |
| - >>> s = SortedCollection(key=itemgetter(2)) |
41 |
| - >>> for record in [ |
42 |
| - ... ('roger', 'young', 30), |
43 |
| - ... ('angela', 'jones', 28), |
44 |
| - ... ('bill', 'smith', 22), |
45 |
| - ... ('david', 'thomas', 32)]: |
46 |
| - ... s.insert(record) |
47 |
| -
|
48 |
| - >>> pprint(list(s)) # show records sorted by age |
49 |
| - [('bill', 'smith', 22), |
50 |
| - ('angela', 'jones', 28), |
51 |
| - ('roger', 'young', 30), |
52 |
| - ('david', 'thomas', 32)] |
53 |
| -
|
54 |
| - >>> s.find_le(29) # find oldest person aged 29 or younger |
55 |
| - ('angela', 'jones', 28) |
56 |
| - >>> s.find_lt(28) # find oldest person under 28 |
57 |
| - ('bill', 'smith', 22) |
58 |
| - >>> s.find_gt(28) # find youngest person over 28 |
59 |
| - ('roger', 'young', 30) |
60 |
| -
|
61 |
| - >>> r = s.find_ge(32) # find youngest person aged 32 or older |
62 |
| - >>> s.index(r) # get the index of their record |
63 |
| - 3 |
64 |
| - >>> s[3] # fetch the record at that index |
65 |
| - ('david', 'thomas', 32) |
66 |
| -
|
67 |
| - >>> s.key = itemgetter(0) # now sort by first name |
68 |
| - >>> pprint(list(s)) |
69 |
| - [('angela', 'jones', 28), |
70 |
| - ('bill', 'smith', 22), |
71 |
| - ('david', 'thomas', 32), |
72 |
| - ('roger', 'young', 30)] |
73 |
| -
|
74 |
| - """ |
75 |
| - |
76 | 8 | def __init__(self, key=None):
|
77 | 9 | self._given_key = key
|
78 | 10 | key = (lambda x: x) if key is None else key
|
@@ -178,38 +110,32 @@ def get_connection(self, relay, type_name, args):
|
178 | 110 | if not count:
|
179 | 111 | return self.empty_connection(relay, type_name)
|
180 | 112 |
|
181 |
| - begin_key = cursor.get_offset(after, None) or self._keys[0] |
182 |
| - end_key = cursor.get_offset(before, None) or self._keys[-1] |
183 |
| - |
184 |
| - begin = self.bisect_left(begin_key) |
185 |
| - end = self.bisect_right(end_key) |
| 113 | + begin_key = cursor.get_offset(after, None) |
| 114 | + end_key = cursor.get_offset(before, None) |
186 | 115 |
|
187 |
| - if begin >= count or begin >= end: |
188 |
| - return self.empty_connection(relay, type_name) |
| 116 | + lower_bound = begin = self.bisect_left(begin_key) + 1 if begin_key else 0 |
| 117 | + upper_bound = end = self.bisect_right(end_key) - 1 if end_key else count |
189 | 118 |
|
190 |
| - first_preslice_cursor = cursor.from_offset(self._keys[begin]) |
191 |
| - last_preslice_cursor = cursor.from_offset(self._keys[min(end, count) - 1]) |
| 119 | + if upper_bound < count and self._keys[upper_bound] != end_key: |
| 120 | + upper_bound = end = count |
192 | 121 |
|
193 | 122 | if first is not None:
|
194 | 123 | end = min(begin + first, end)
|
195 | 124 | if last is not None:
|
196 | 125 | begin = max(end - last, begin)
|
197 | 126 |
|
198 |
| - if begin >= count or begin >= end: |
199 |
| - return self.empty_connection(relay, type_name) |
200 |
| - |
201 | 127 | sliced_data = self._items[begin:end]
|
202 | 128 |
|
203 | 129 | edges = [Edge(node=node, cursor=cursor.from_offset(self._key(node))) for node in sliced_data]
|
204 |
| - first_edge = edges[0] |
205 |
| - last_edge = edges[-1] |
| 130 | + first_edge = edges[0] if edges else None |
| 131 | + last_edge = edges[-1] if edges else None |
206 | 132 |
|
207 | 133 | return Connection(
|
208 | 134 | edges=edges,
|
209 | 135 | page_info=relay.PageInfo(
|
210 |
| - start_cursor=first_edge.cursor, |
211 |
| - end_cursor=last_edge.cursor, |
212 |
| - has_previous_page=(first_edge.cursor != first_preslice_cursor), |
213 |
| - has_next_page=(last_edge.cursor != last_preslice_cursor) |
| 136 | + start_cursor=first_edge and first_edge.cursor, |
| 137 | + end_cursor=last_edge and last_edge.cursor, |
| 138 | + has_previous_page=begin > lower_bound, |
| 139 | + has_next_page=end < upper_bound |
214 | 140 | )
|
215 | 141 | )
|
0 commit comments