Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyrsistent/_plist.py: 36%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from collections.abc import Sequence, Hashable
2from numbers import Integral
3from functools import reduce
4from typing import Generic, TypeVar
6T_co = TypeVar('T_co', covariant=True)
9class _PListBuilder(object):
10 """
11 Helper class to allow construction of a list without
12 having to reverse it in the end.
13 """
14 __slots__ = ('_head', '_tail')
16 def __init__(self):
17 self._head = _EMPTY_PLIST
18 self._tail = _EMPTY_PLIST
20 def _append(self, elem, constructor):
21 if not self._tail:
22 self._head = constructor(elem)
23 self._tail = self._head
24 else:
25 self._tail.rest = constructor(elem)
26 self._tail = self._tail.rest
28 return self._head
30 def append_elem(self, elem):
31 return self._append(elem, lambda e: PList(e, _EMPTY_PLIST))
33 def append_plist(self, pl):
34 return self._append(pl, lambda l: l)
36 def build(self):
37 return self._head
40class _PListBase(object):
41 __slots__ = ('__weakref__',)
43 # Selected implementations can be taken straight from the Sequence
44 # class, other are less suitable. Especially those that work with
45 # index lookups.
46 count = Sequence.count
47 index = Sequence.index
49 def __reduce__(self):
50 # Pickling support
51 return plist, (list(self),)
53 def __len__(self):
54 """
55 Return the length of the list, computed by traversing it.
57 This is obviously O(n) but with the current implementation
58 where a list is also a node the overhead of storing the length
59 in every node would be quite significant.
60 """
61 return sum(1 for _ in self)
63 def __repr__(self):
64 return "plist({0})".format(list(self))
65 __str__ = __repr__
67 def cons(self, elem):
68 """
69 Return a new list with elem inserted as new head.
71 >>> plist([1, 2]).cons(3)
72 plist([3, 1, 2])
73 """
74 return PList(elem, self)
76 def mcons(self, iterable):
77 """
78 Return a new list with all elements of iterable repeatedly cons:ed to the current list.
79 NB! The elements will be inserted in the reverse order of the iterable.
80 Runs in O(len(iterable)).
82 >>> plist([1, 2]).mcons([3, 4])
83 plist([4, 3, 1, 2])
84 """
85 head = self
86 for elem in iterable:
87 head = head.cons(elem)
89 return head
91 def reverse(self):
92 """
93 Return a reversed version of list. Runs in O(n) where n is the length of the list.
95 >>> plist([1, 2, 3]).reverse()
96 plist([3, 2, 1])
98 Also supports the standard reversed function.
100 >>> reversed(plist([1, 2, 3]))
101 plist([3, 2, 1])
102 """
103 result = plist()
104 head = self
105 while head:
106 result = result.cons(head.first)
107 head = head.rest
109 return result
110 __reversed__ = reverse
112 def split(self, index):
113 """
114 Spilt the list at position specified by index. Returns a tuple containing the
115 list up until index and the list after the index. Runs in O(index).
117 >>> plist([1, 2, 3, 4]).split(2)
118 (plist([1, 2]), plist([3, 4]))
119 """
120 lb = _PListBuilder()
121 right_list = self
122 i = 0
123 while right_list and i < index:
124 lb.append_elem(right_list.first)
125 right_list = right_list.rest
126 i += 1
128 if not right_list:
129 # Just a small optimization in the cases where no split occurred
130 return self, _EMPTY_PLIST
132 return lb.build(), right_list
134 def __iter__(self):
135 li = self
136 while li:
137 yield li.first
138 li = li.rest
140 def __lt__(self, other):
141 if not isinstance(other, _PListBase):
142 return NotImplemented
144 return tuple(self) < tuple(other)
146 def __eq__(self, other):
147 """
148 Traverses the lists, checking equality of elements.
150 This is an O(n) operation, but preserves the standard semantics of list equality.
151 """
152 if not isinstance(other, _PListBase):
153 return NotImplemented
155 self_head = self
156 other_head = other
157 while self_head and other_head:
158 if not self_head.first == other_head.first:
159 return False
160 self_head = self_head.rest
161 other_head = other_head.rest
163 return not self_head and not other_head
165 def __getitem__(self, index):
166 # Don't use this this data structure if you plan to do a lot of indexing, it is
167 # very inefficient! Use a PVector instead!
169 if isinstance(index, slice):
170 if index.start is not None and index.stop is None and (index.step is None or index.step == 1):
171 return self._drop(index.start)
173 # Take the easy way out for all other slicing cases, not much structural reuse possible anyway
174 return plist(tuple(self)[index])
176 if not isinstance(index, Integral):
177 raise TypeError("'%s' object cannot be interpreted as an index" % type(index).__name__)
179 if index < 0:
180 # NB: O(n)!
181 index += len(self)
183 try:
184 return self._drop(index).first
185 except AttributeError as e:
186 raise IndexError("PList index out of range") from e
188 def _drop(self, count):
189 if count < 0:
190 raise IndexError("PList index out of range")
192 head = self
193 while count > 0:
194 head = head.rest
195 count -= 1
197 return head
199 def __hash__(self):
200 return hash(tuple(self))
202 def remove(self, elem):
203 """
204 Return new list with first element equal to elem removed. O(k) where k is the position
205 of the element that is removed.
207 Raises ValueError if no matching element is found.
209 >>> plist([1, 2, 1]).remove(1)
210 plist([2, 1])
211 """
213 builder = _PListBuilder()
214 head = self
215 while head:
216 if head.first == elem:
217 return builder.append_plist(head.rest)
219 builder.append_elem(head.first)
220 head = head.rest
222 raise ValueError('{0} not found in PList'.format(elem))
225class PList(Generic[T_co], _PListBase):
226 """
227 Classical Lisp style singly linked list. Adding elements to the head using cons is O(1).
228 Element access is O(k) where k is the position of the element in the list. Taking the
229 length of the list is O(n).
231 Fully supports the Sequence and Hashable protocols including indexing and slicing but
232 if you need fast random access go for the PVector instead.
234 Do not instantiate directly, instead use the factory functions :py:func:`l` or :py:func:`plist` to
235 create an instance.
237 Some examples:
239 >>> x = plist([1, 2])
240 >>> y = x.cons(3)
241 >>> x
242 plist([1, 2])
243 >>> y
244 plist([3, 1, 2])
245 >>> y.first
246 3
247 >>> y.rest == x
248 True
249 >>> y[:2]
250 plist([3, 1])
251 """
252 __slots__ = ('first', 'rest')
254 def __new__(cls, first, rest):
255 instance = super(PList, cls).__new__(cls)
256 instance.first = first
257 instance.rest = rest
258 return instance
260 def __bool__(self):
261 return True
262 __nonzero__ = __bool__
265Sequence.register(PList)
266Hashable.register(PList)
269class _EmptyPList(_PListBase):
270 __slots__ = ()
272 def __bool__(self):
273 return False
274 __nonzero__ = __bool__
276 @property
277 def first(self):
278 raise AttributeError("Empty PList has no first")
280 @property
281 def rest(self):
282 return self
285Sequence.register(_EmptyPList)
286Hashable.register(_EmptyPList)
288_EMPTY_PLIST = _EmptyPList()
291def plist(iterable=(), reverse=False):
292 """
293 Creates a new persistent list containing all elements of iterable.
294 Optional parameter reverse specifies if the elements should be inserted in
295 reverse order or not.
297 >>> plist([1, 2, 3])
298 plist([1, 2, 3])
299 >>> plist([1, 2, 3], reverse=True)
300 plist([3, 2, 1])
301 """
302 if not reverse:
303 iterable = list(iterable)
304 iterable.reverse()
306 return reduce(lambda pl, elem: pl.cons(elem), iterable, _EMPTY_PLIST)
309def l(*elements):
310 """
311 Creates a new persistent list containing all arguments.
313 >>> l(1, 2, 3)
314 plist([1, 2, 3])
315 """
316 return plist(elements)