|
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