1import rpy2.rlike.indexing as rli
2from typing import Any
3from typing import Iterable
4from typing import List
5from typing import Optional
6from typing import Tuple
7
8
9class OrdDict(dict):
10    """ Implements the Ordered Dict API defined in PEP 372.
11    When `odict` becomes part of collections, this class
12    should inherit from it rather than from `dict`.
13
14    This class differs a little from the Ordered Dict
15    proposed in PEP 372 by the fact that:
16    not all elements have to be named. None as a key value means
17    an absence of name for the element.
18
19    """
20
21    __l: List[Tuple[Optional[str], Any]]
22
23    def __init__(self, c: Iterable[Tuple[Optional[str], Any]]=[]):
24
25        if isinstance(c, TaggedList) or isinstance(c, OrdDict):
26            c = c.items()
27        elif isinstance(c, dict):
28            # FIXME: allow instance from OrdDict ?
29            raise TypeError('A regular dictionnary does not ' +
30                            'conserve the order of its keys.')
31
32        super(OrdDict, self).__init__()
33        self.__l = []
34
35        for k, v in c:
36            self[k] = v
37
38    def __copy__(self):
39        cp = OrdDict(c=tuple(self.items()))
40        return cp
41
42    def __cmp__(self, o):
43        raise(NotImplementedError("Not yet implemented."))
44
45    def __eq__(self, o):
46        raise(NotImplementedError("Not yet implemented."))
47
48    def __getitem__(self, key: str):
49        if key is None:
50            raise ValueError("Unnamed items cannot be retrieved by key.")
51        i = super(OrdDict, self).__getitem__(key)
52        return self.__l[i][1]
53
54    def __iter__(self):
55        seq = self.__l
56        for e in seq:
57            k = e[0]
58            if k is None:
59                continue
60            else:
61                yield k
62
63    def __len__(self):
64        return len(self.__l)
65
66    def __ne__(self, o):
67        raise(NotImplementedError('Not yet implemented.'))
68
69    def __repr__(self) -> str:
70        s = ['o{', ]
71        for k, v in self.items():
72            s.append("'%s': %s, " % (str(k), str(v)))
73        s.append('}')
74        return ''.join(s)
75
76    def __reversed__(self):
77        raise(NotImplementedError("Not yet implemented."))
78
79    def __setitem__(self, key: Optional[str], value: Any):
80        """ Replace the element if the key is known,
81        and conserve its rank in the list, or append
82        it if unknown. """
83
84        if key is None:
85            self.__l.append((key, value))
86            return
87
88        if key in self:
89            i = self.index(key)
90            self.__l[i] = (key, value)
91        else:
92            self.__l.append((key, value))
93            super(OrdDict, self).__setitem__(key, len(self.__l)-1)
94
95    def byindex(self, i: int) -> Any:
96        """ Fetch a value by index (rank), rather than by key."""
97        return self.__l[i]
98
99    def index(self, k: str) -> int:
100        """ Return the index (rank) for the key 'k' """
101        return super(OrdDict, self).__getitem__(k)
102
103    def get(self, k: str, d: Any = None):
104        """ OD.get(k[,d]) -> OD[k] if k in OD, else d.  d defaults to None """
105        try:
106            res = self[k]
107        except KeyError:
108            res = d
109        return res
110
111    def items(self):
112        """ OD.items() -> an iterator over the (key, value) items of D """
113        return iter(self.__l)
114
115    def keys(self):
116        """ """
117        return tuple([x[0] for x in self.__l])
118
119    def reverse(self):
120        """ Reverse the order of the elements in-place (no copy)."""
121        seq = self.__l
122        n = len(self.__l)
123        for i in range(n//2):
124            tmp = seq[i]
125            seq[i] = seq[n-i-1]
126            kv = seq[i]
127            if kv is not None:
128                super(OrdDict, self).__setitem__(kv[0], i)
129            seq[n-i-1] = tmp
130            kv = tmp
131            if kv is not None:
132                super(OrdDict, self).__setitem__(kv[0], n-i-1)
133
134    def sort(self, cmp=None, key=None, reverse=False):
135        raise NotImplementedError("Not yet implemented.")
136
137
138class TaggedList(list):
139    """ A list for which each item has a 'tag'.
140
141    :param l: list
142    :param tag: optional sequence of tags
143    """
144
145    __tags: List[Optional[str]]
146
147    def __init__(self, seq, tags=None):
148        super(TaggedList, self).__init__(seq)
149        if tags is None:
150            tags = [None, ] * len(seq)
151        if len(tags) != len(seq):
152            raise ValueError("There must be as many tags as seq")
153        self.__tags = list(tags)
154
155    def __add__(self, tl):
156        try:
157            tags = tl.tags
158        except AttributeError:
159            raise ValueError('Can only concatenate TaggedLists.')
160        res = TaggedList(list(self) + list(tl),
161                         tags=self.tags + tags)
162        return res
163
164    def __delitem__(self, y):
165        super(TaggedList, self).__delitem__(y)
166        self.__tags.__delitem__(y)
167
168    def __delslice__(self, i, j):
169        super(TaggedList, self).__delslice__(i, j)
170        self.__tags.__delslice__(i, j)
171
172    def __iadd__(self, y):
173        super(TaggedList, self).__iadd__(y)
174        if isinstance(y, TaggedList):
175            self.__tags.__iadd__(y.tags)
176        else:
177            self.__tags.__iadd__([None, ] * len(y))
178        return self
179
180    def __imul__(self, y):
181        restags = self.__tags.__imul__(y)
182        resitems = super(TaggedList, self).__imul__(y)
183        return self
184
185    @staticmethod
186    def from_items(tagval):
187        res = TaggedList([])
188        for k, v in tagval.items():
189            res.append(v, tag=k)
190        return res
191
192    def __setslice__(self, i, j, y):
193        super(TaggedList, self).__setslice__(i, j, y)
194        # TODO: handle TaggedList ?
195        # self.__tags.__setslice__(i, j, [None, ])
196
197    def append(self, obj, tag=None):
198        """ Append an object to the list
199        :param obj: object
200        :param tag: object
201        """
202        super(TaggedList, self).append(obj)
203        self.__tags.append(tag)
204
205    def extend(self, iterable):
206        """ Extend the list with an iterable object.
207
208        :param iterable: iterable object
209        """
210
211        if isinstance(iterable, TaggedList):
212            itertags = iterable.itertags()
213        else:
214            itertags = [None, ] * len(iterable)
215
216        for tag, item in zip(itertags, iterable):
217            self.append(item, tag=tag)
218
219    def insert(self, index, obj, tag=None):
220        """
221        Insert an object in the list
222
223        :param index: integer
224        :param obj: object
225        :param tag: object
226        """
227        super(TaggedList, self).insert(index, obj)
228        self.__tags.insert(index, tag)
229
230    def iterontag(self, tag):
231        """
232        iterate on items marked with one given tag.
233
234        :param tag: object
235        """
236
237        i = 0
238        for onetag in self.__tags:
239            if tag == onetag:
240                yield self[i]
241            i += 1
242
243    def items(self):
244        """ OD.items() -> an iterator over the (key, value) items of D """
245        for tag, item in zip(self.__tags, self):
246            yield (tag, item)
247
248    def itertags(self):
249        """
250        iterate on tags.
251
252        :rtype: iterator
253        """
254        for tag in self.__tags:
255            yield tag
256
257    def pop(self, index=None):
258        """
259        Pop the item at a given index out of the list
260
261        :param index: integer
262
263        """
264        if index is None:
265            index = len(self) - 1
266
267        res = super(TaggedList, self).pop(index)
268        self.__tags.pop(index)
269        return res
270
271    def remove(self, value):
272        """
273        Remove a given value from the list.
274
275        :param value: object
276
277        """
278        found = False
279        for i in range(len(self)):
280            if self[i] == value:
281                found = True
282                break
283        if found:
284            self.pop(i)
285
286    def reverse(self):
287        """ Reverse the order of the elements in the list. """
288        super(TaggedList, self).reverse()
289        self.__tags.reverse()
290
291    def sort(self, reverse=False):
292        """
293        Sort in place
294        """
295        o = rli.order(self, reverse=reverse)
296        super(TaggedList, self).sort(reverse=reverse)
297        self.__tags = [self.__tags[i] for i in o]
298
299    def __get_tags(self):
300        return tuple(self.__tags)
301
302    def __set_tags(self, tags):
303        if len(tags) == len(self.__tags):
304            self.__tags = tuple(tags)
305        else:
306            raise ValueError('The new list of tags should have the '
307                             'same length as the old one.')
308
309    tags = property(__get_tags, __set_tags)
310
311    def settag(self, i, t):
312        """
313        Set tag 't' for item 'i'.
314
315        :param i: integer (index)
316
317        :param t: object (tag)
318        """
319        self.__tags[i] = t
320