coreviews.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. """Views of core data structures such as nested Mappings (e.g. dict-of-dicts).
  2. These ``Views`` often restrict element access, with either the entire view or
  3. layers of nested mappings being read-only.
  4. """
  5. from collections.abc import Mapping
  6. __all__ = [
  7. "AtlasView",
  8. "AdjacencyView",
  9. "MultiAdjacencyView",
  10. "UnionAtlas",
  11. "UnionAdjacency",
  12. "UnionMultiInner",
  13. "UnionMultiAdjacency",
  14. "FilterAtlas",
  15. "FilterAdjacency",
  16. "FilterMultiInner",
  17. "FilterMultiAdjacency",
  18. ]
  19. class AtlasView(Mapping):
  20. """An AtlasView is a Read-only Mapping of Mappings.
  21. It is a View into a dict-of-dict data structure.
  22. The inner level of dict is read-write. But the
  23. outer level is read-only.
  24. See Also
  25. ========
  26. AdjacencyView: View into dict-of-dict-of-dict
  27. MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
  28. """
  29. __slots__ = ("_atlas",)
  30. def __getstate__(self):
  31. return {"_atlas": self._atlas}
  32. def __setstate__(self, state):
  33. self._atlas = state["_atlas"]
  34. def __init__(self, d):
  35. self._atlas = d
  36. def __len__(self):
  37. return len(self._atlas)
  38. def __iter__(self):
  39. return iter(self._atlas)
  40. def __getitem__(self, key):
  41. return self._atlas[key]
  42. def copy(self):
  43. return {n: self[n].copy() for n in self._atlas}
  44. def __str__(self):
  45. return str(self._atlas) # {nbr: self[nbr] for nbr in self})
  46. def __repr__(self):
  47. return f"{self.__class__.__name__}({self._atlas!r})"
  48. class AdjacencyView(AtlasView):
  49. """An AdjacencyView is a Read-only Map of Maps of Maps.
  50. It is a View into a dict-of-dict-of-dict data structure.
  51. The inner level of dict is read-write. But the
  52. outer levels are read-only.
  53. See Also
  54. ========
  55. AtlasView: View into dict-of-dict
  56. MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
  57. """
  58. __slots__ = () # Still uses AtlasView slots names _atlas
  59. def __getitem__(self, name):
  60. return AtlasView(self._atlas[name])
  61. def copy(self):
  62. return {n: self[n].copy() for n in self._atlas}
  63. class MultiAdjacencyView(AdjacencyView):
  64. """An MultiAdjacencyView is a Read-only Map of Maps of Maps of Maps.
  65. It is a View into a dict-of-dict-of-dict-of-dict data structure.
  66. The inner level of dict is read-write. But the
  67. outer levels are read-only.
  68. See Also
  69. ========
  70. AtlasView: View into dict-of-dict
  71. AdjacencyView: View into dict-of-dict-of-dict
  72. """
  73. __slots__ = () # Still uses AtlasView slots names _atlas
  74. def __getitem__(self, name):
  75. return AdjacencyView(self._atlas[name])
  76. def copy(self):
  77. return {n: self[n].copy() for n in self._atlas}
  78. class UnionAtlas(Mapping):
  79. """A read-only union of two atlases (dict-of-dict).
  80. The two dict-of-dicts represent the inner dict of
  81. an Adjacency: `G.succ[node]` and `G.pred[node]`.
  82. The inner level of dict of both hold attribute key:value
  83. pairs and is read-write. But the outer level is read-only.
  84. See Also
  85. ========
  86. UnionAdjacency: View into dict-of-dict-of-dict
  87. UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
  88. """
  89. __slots__ = ("_succ", "_pred")
  90. def __getstate__(self):
  91. return {"_succ": self._succ, "_pred": self._pred}
  92. def __setstate__(self, state):
  93. self._succ = state["_succ"]
  94. self._pred = state["_pred"]
  95. def __init__(self, succ, pred):
  96. self._succ = succ
  97. self._pred = pred
  98. def __len__(self):
  99. return len(self._succ.keys() | self._pred.keys())
  100. def __iter__(self):
  101. return iter(set(self._succ.keys()) | set(self._pred.keys()))
  102. def __getitem__(self, key):
  103. try:
  104. return self._succ[key]
  105. except KeyError:
  106. return self._pred[key]
  107. def copy(self):
  108. result = {nbr: dd.copy() for nbr, dd in self._succ.items()}
  109. for nbr, dd in self._pred.items():
  110. if nbr in result:
  111. result[nbr].update(dd)
  112. else:
  113. result[nbr] = dd.copy()
  114. return result
  115. def __str__(self):
  116. return str({nbr: self[nbr] for nbr in self})
  117. def __repr__(self):
  118. return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
  119. class UnionAdjacency(Mapping):
  120. """A read-only union of dict Adjacencies as a Map of Maps of Maps.
  121. The two input dict-of-dict-of-dicts represent the union of
  122. `G.succ` and `G.pred`. Return values are UnionAtlas
  123. The inner level of dict is read-write. But the
  124. middle and outer levels are read-only.
  125. succ : a dict-of-dict-of-dict {node: nbrdict}
  126. pred : a dict-of-dict-of-dict {node: nbrdict}
  127. The keys for the two dicts should be the same
  128. See Also
  129. ========
  130. UnionAtlas: View into dict-of-dict
  131. UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
  132. """
  133. __slots__ = ("_succ", "_pred")
  134. def __getstate__(self):
  135. return {"_succ": self._succ, "_pred": self._pred}
  136. def __setstate__(self, state):
  137. self._succ = state["_succ"]
  138. self._pred = state["_pred"]
  139. def __init__(self, succ, pred):
  140. # keys must be the same for two input dicts
  141. assert len(set(succ.keys()) ^ set(pred.keys())) == 0
  142. self._succ = succ
  143. self._pred = pred
  144. def __len__(self):
  145. return len(self._succ) # length of each dict should be the same
  146. def __iter__(self):
  147. return iter(self._succ)
  148. def __getitem__(self, nbr):
  149. return UnionAtlas(self._succ[nbr], self._pred[nbr])
  150. def copy(self):
  151. return {n: self[n].copy() for n in self._succ}
  152. def __str__(self):
  153. return str({nbr: self[nbr] for nbr in self})
  154. def __repr__(self):
  155. return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
  156. class UnionMultiInner(UnionAtlas):
  157. """A read-only union of two inner dicts of MultiAdjacencies.
  158. The two input dict-of-dict-of-dicts represent the union of
  159. `G.succ[node]` and `G.pred[node]` for MultiDiGraphs.
  160. Return values are UnionAtlas.
  161. The inner level of dict is read-write. But the outer levels are read-only.
  162. See Also
  163. ========
  164. UnionAtlas: View into dict-of-dict
  165. UnionAdjacency: View into dict-of-dict-of-dict
  166. UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
  167. """
  168. __slots__ = () # Still uses UnionAtlas slots names _succ, _pred
  169. def __getitem__(self, node):
  170. in_succ = node in self._succ
  171. in_pred = node in self._pred
  172. if in_succ:
  173. if in_pred:
  174. return UnionAtlas(self._succ[node], self._pred[node])
  175. return UnionAtlas(self._succ[node], {})
  176. return UnionAtlas({}, self._pred[node])
  177. def copy(self):
  178. nodes = set(self._succ.keys()) | set(self._pred.keys())
  179. return {n: self[n].copy() for n in nodes}
  180. class UnionMultiAdjacency(UnionAdjacency):
  181. """A read-only union of two dict MultiAdjacencies.
  182. The two input dict-of-dict-of-dict-of-dicts represent the union of
  183. `G.succ` and `G.pred` for MultiDiGraphs. Return values are UnionAdjacency.
  184. The inner level of dict is read-write. But the outer levels are read-only.
  185. See Also
  186. ========
  187. UnionAtlas: View into dict-of-dict
  188. UnionMultiInner: View into dict-of-dict-of-dict
  189. """
  190. __slots__ = () # Still uses UnionAdjacency slots names _succ, _pred
  191. def __getitem__(self, node):
  192. return UnionMultiInner(self._succ[node], self._pred[node])
  193. class FilterAtlas(Mapping): # nodedict, nbrdict, keydict
  194. def __init__(self, d, NODE_OK):
  195. self._atlas = d
  196. self.NODE_OK = NODE_OK
  197. def __len__(self):
  198. return sum(1 for n in self)
  199. def __iter__(self):
  200. try: # check that NODE_OK has attr 'nodes'
  201. node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
  202. except AttributeError:
  203. node_ok_shorter = False
  204. if node_ok_shorter:
  205. return (n for n in self.NODE_OK.nodes if n in self._atlas)
  206. return (n for n in self._atlas if self.NODE_OK(n))
  207. def __getitem__(self, key):
  208. if key in self._atlas and self.NODE_OK(key):
  209. return self._atlas[key]
  210. raise KeyError(f"Key {key} not found")
  211. def __str__(self):
  212. return str({nbr: self[nbr] for nbr in self})
  213. def __repr__(self):
  214. return f"{self.__class__.__name__}({self._atlas!r}, {self.NODE_OK!r})"
  215. class FilterAdjacency(Mapping): # edgedict
  216. def __init__(self, d, NODE_OK, EDGE_OK):
  217. self._atlas = d
  218. self.NODE_OK = NODE_OK
  219. self.EDGE_OK = EDGE_OK
  220. def __len__(self):
  221. return sum(1 for n in self)
  222. def __iter__(self):
  223. try: # check that NODE_OK has attr 'nodes'
  224. node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
  225. except AttributeError:
  226. node_ok_shorter = False
  227. if node_ok_shorter:
  228. return (n for n in self.NODE_OK.nodes if n in self._atlas)
  229. return (n for n in self._atlas if self.NODE_OK(n))
  230. def __getitem__(self, node):
  231. if node in self._atlas and self.NODE_OK(node):
  232. def new_node_ok(nbr):
  233. return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr)
  234. return FilterAtlas(self._atlas[node], new_node_ok)
  235. raise KeyError(f"Key {node} not found")
  236. def __str__(self):
  237. return str({nbr: self[nbr] for nbr in self})
  238. def __repr__(self):
  239. name = self.__class__.__name__
  240. return f"{name}({self._atlas!r}, {self.NODE_OK!r}, {self.EDGE_OK!r})"
  241. class FilterMultiInner(FilterAdjacency): # muliedge_seconddict
  242. def __iter__(self):
  243. try: # check that NODE_OK has attr 'nodes'
  244. node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
  245. except AttributeError:
  246. node_ok_shorter = False
  247. if node_ok_shorter:
  248. my_nodes = (n for n in self.NODE_OK.nodes if n in self._atlas)
  249. else:
  250. my_nodes = (n for n in self._atlas if self.NODE_OK(n))
  251. for n in my_nodes:
  252. some_keys_ok = False
  253. for key in self._atlas[n]:
  254. if self.EDGE_OK(n, key):
  255. some_keys_ok = True
  256. break
  257. if some_keys_ok is True:
  258. yield n
  259. def __getitem__(self, nbr):
  260. if nbr in self._atlas and self.NODE_OK(nbr):
  261. def new_node_ok(key):
  262. return self.EDGE_OK(nbr, key)
  263. return FilterAtlas(self._atlas[nbr], new_node_ok)
  264. raise KeyError(f"Key {nbr} not found")
  265. class FilterMultiAdjacency(FilterAdjacency): # multiedgedict
  266. def __getitem__(self, node):
  267. if node in self._atlas and self.NODE_OK(node):
  268. def edge_ok(nbr, key):
  269. return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr, key)
  270. return FilterMultiInner(self._atlas[node], self.NODE_OK, edge_ok)
  271. raise KeyError(f"Key {node} not found")