import rpy2.rlike.indexing as rli
from typing import Any
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
[docs]class OrdDict(dict):
""" Implements the Ordered Dict API defined in PEP 372.
When `odict` becomes part of collections, this class
should inherit from it rather than from `dict`.
This class differs a little from the Ordered Dict
proposed in PEP 372 by the fact that:
not all elements have to be named. None as a key value means
an absence of name for the element.
"""
__l: List[Tuple[Optional[str], Any]]
def __init__(self, c: Iterable[Tuple[Optional[str], Any]]=[]):
if isinstance(c, TaggedList) or isinstance(c, OrdDict):
c = c.items()
elif isinstance(c, dict):
# FIXME: allow instance from OrdDict ?
raise TypeError('A regular dictionnary does not ' +
'conserve the order of its keys.')
super(OrdDict, self).__init__()
self.__l = []
for k, v in c:
self[k] = v
def __copy__(self):
cp = OrdDict(c=tuple(self.items()))
return cp
def __reduce__(self):
# We need to override the special-cased dict unpickling process in order
# to retain the attributes the `__l` attribute.
return (
self.__class__, # callable
(), # arguments to constructor
{'_OrdDict__l': self.__l}, # state
None, # list items
iter(self.items()), # dict items
)
def __cmp__(self, o):
return NotImplemented
def __eq__(self, o):
return NotImplemented
def __getitem__(self, key: str):
if key is None:
raise ValueError("Unnamed items cannot be retrieved by key.")
i = super(OrdDict, self).__getitem__(key)
return self.__l[i][1]
def __iter__(self):
seq = self.__l
for e in seq:
k = e[0]
if k is None:
continue
else:
yield k
def __len__(self):
return len(self.__l)
def __ne__(self, o):
return NotImplemented
def __repr__(self) -> str:
s = ['o{', ]
for k, v in self.items():
s.append("'%s': %s, " % (str(k), str(v)))
s.append('}')
return ''.join(s)
def __reversed__(self):
raise NotImplementedError("Not yet implemented.")
def __setitem__(self, key: Optional[str], value: Any):
""" Replace the element if the key is known,
and conserve its rank in the list, or append
it if unknown. """
if key is None:
self.__l.append((key, value))
return
if key in self:
i = self.index(key)
self.__l[i] = (key, value)
else:
self.__l.append((key, value))
super(OrdDict, self).__setitem__(key, len(self.__l)-1)
[docs] def byindex(self, i: int) -> Any:
""" Fetch a value by index (rank), rather than by key."""
return self.__l[i]
[docs] def index(self, k: str) -> int:
""" Return the index (rank) for the key 'k' """
return super(OrdDict, self).__getitem__(k)
[docs] def get(self, k: str, d: Any = None):
""" OD.get(k[,d]) -> OD[k] if k in OD, else d. d defaults to None """
try:
res = self[k]
except KeyError:
res = d
return res
[docs] def items(self):
""" OD.items() -> an iterator over the (key, value) items of D """
return iter(self.__l)
def keys(self):
""" """
return tuple([x[0] for x in self.__l])
[docs] def reverse(self):
""" Reverse the order of the elements in-place (no copy)."""
seq = self.__l
n = len(self.__l)
for i in range(n//2):
tmp = seq[i]
seq[i] = seq[n-i-1]
kv = seq[i]
if kv is not None:
super(OrdDict, self).__setitem__(kv[0], i)
seq[n-i-1] = tmp
kv = tmp
if kv is not None:
super(OrdDict, self).__setitem__(kv[0], n-i-1)
def sort(self, cmp=None, key=None, reverse=False):
raise NotImplementedError("Not yet implemented.")
[docs]class TaggedList(list):
""" A list for which each item has a 'tag'.
:param l: list
:param tag: optional sequence of tags
"""
__tags: List[Optional[str]]
def __init__(self, seq, tags=None):
super(TaggedList, self).__init__(seq)
if tags is None:
tags = [None, ] * len(seq)
if len(tags) != len(seq):
raise ValueError("There must be as many tags as seq")
self.__tags = list(tags)
def __add__(self, tl):
try:
tags = tl.tags
except AttributeError:
raise ValueError('Can only concatenate TaggedLists.')
res = TaggedList(list(self) + list(tl),
tags=self.tags + tags)
return res
def __delitem__(self, y):
super(TaggedList, self).__delitem__(y)
self.__tags.__delitem__(y)
def __delslice__(self, i, j):
super(TaggedList, self).__delslice__(i, j)
self.__tags.__delslice__(i, j)
def __iadd__(self, y):
super(TaggedList, self).__iadd__(y)
if isinstance(y, TaggedList):
self.__tags.__iadd__(y.tags)
else:
self.__tags.__iadd__([None, ] * len(y))
return self
def __imul__(self, y):
restags = self.__tags.__imul__(y)
resitems = super(TaggedList, self).__imul__(y)
return self
def __mul__(self, y):
restags = self.__tags__ * y.__tags__
resitems = super(TaggedList, self).__mul__(y)
return type(self)(tuple(resitems), tags=restags)
def __reduce__(self):
return super(TaggedList, self).__reduce__()
@staticmethod
def from_items(tagval):
res = TaggedList([])
for k, v in tagval.items():
res.append(v, tag=k)
return res
def __setslice__(self, i, j, y):
super(TaggedList, self).__setslice__(i, j, y)
# TODO: handle TaggedList ?
# self.__tags.__setslice__(i, j, [None, ])
[docs] def append(self, obj, tag=None):
""" Append an object to the list
:param obj: object
:param tag: object
"""
super(TaggedList, self).append(obj)
self.__tags.append(tag)
[docs] def extend(self, iterable):
""" Extend the list with an iterable object.
:param iterable: iterable object
"""
if isinstance(iterable, TaggedList):
itertags = iterable.itertags()
else:
itertags = [None, ] * len(iterable)
for tag, item in zip(itertags, iterable):
self.append(item, tag=tag)
[docs] def insert(self, index, obj, tag=None):
"""
Insert an object in the list
:param index: integer
:param obj: object
:param tag: object
"""
super(TaggedList, self).insert(index, obj)
self.__tags.insert(index, tag)
[docs] def iterontag(self, tag):
"""
iterate on items marked with one given tag.
:param tag: object
"""
i = 0
for onetag in self.__tags:
if tag == onetag:
yield self[i]
i += 1
[docs] def items(self):
""" OD.items() -> an iterator over the (key, value) items of D """
for tag, item in zip(self.__tags, self):
yield (tag, item)
[docs] def pop(self, index=None):
"""
Pop the item at a given index out of the list
:param index: integer
"""
if index is None:
index = len(self) - 1
res = super(TaggedList, self).pop(index)
self.__tags.pop(index)
return res
[docs] def remove(self, value):
"""
Remove a given value from the list.
:param value: object
"""
found = False
for i in range(len(self)):
if self[i] == value:
found = True
break
if found:
self.pop(i)
[docs] def reverse(self):
""" Reverse the order of the elements in the list. """
super(TaggedList, self).reverse()
self.__tags.reverse()
[docs] def sort(self, reverse=False):
"""
Sort in place
"""
o = rli.order(self, reverse=reverse)
super(TaggedList, self).sort(reverse=reverse)
self.__tags = [self.__tags[i] for i in o]
def __get_tags(self):
return tuple(self.__tags)
def __set_tags(self, tags):
if len(tags) == len(self.__tags):
self.__tags = tuple(tags)
else:
raise ValueError('The new list of tags should have the '
'same length as the old one.')
tags = property(__get_tags, __set_tags)
[docs] def settag(self, i, t):
"""
Set tag 't' for item 'i'.
:param i: integer (index)
:param t: object (tag)
"""
self.__tags[i] = t