_orderedbase.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 Joshua Bronson. All Rights Reserved.
  3. #
  4. # This Source Code Form is subject to the terms of the Mozilla Public
  5. # License, v. 2.0. If a copy of the MPL was not distributed with this
  6. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7. #==============================================================================
  8. # * Welcome to the bidict source code *
  9. #==============================================================================
  10. # Doing a code review? You'll find a "Code review nav" comment like the one
  11. # below at the top and bottom of the most important source files. This provides
  12. # a suggested initial path through the source when reviewing.
  13. #
  14. # Note: If you aren't reading this on https://github.com/jab/bidict, you may be
  15. # viewing an outdated version of the code. Please head to GitHub to review the
  16. # latest version, which contains important improvements over older versions.
  17. #
  18. # Thank you for reading and for any feedback you provide.
  19. # * Code review nav *
  20. #==============================================================================
  21. # ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
  22. #==============================================================================
  23. """Provides :class:`OrderedBidictBase`."""
  24. from weakref import ref
  25. from ._base import _WriteResult, BidictBase
  26. from ._miss import _MISS
  27. from .compat import KeysView, ItemsView, Mapping, PY2, iteritems, izip
  28. class _Node(object): # pylint: disable=too-few-public-methods
  29. """A node in a circular doubly-linked list
  30. that stores the items in an ordered bidict.
  31. Only weak references to the next and previous nodes
  32. are held to avoid creating strong reference cycles.
  33. Because an ordered bidict retains two strong references
  34. to each node instance (one from its backing `_fwdm` dict
  35. and one from its `_invm` dict), a node's refcount will not
  36. drop to zero (and so will not be garbage collected) as long as
  37. the ordered bidict that contains it is still alive.
  38. Because nodes don't have strong reference cycles,
  39. once their containing bidict is freed, they can be freed too.
  40. """
  41. __slots__ = ('_korv', '_vork', '_prv', '_nxt', '__weakref__')
  42. def __init__(self, korv, vork, prv=None, nxt=None):
  43. self._korv = korv
  44. self._vork = vork
  45. self._setprv(prv)
  46. self._setnxt(nxt)
  47. def _getdata(self):
  48. return (self._korv, self._vork)
  49. def _setdata(self, data):
  50. self._korv, self._vork = data
  51. data = property(_getdata, _setdata)
  52. def __repr__(self):
  53. clsname = self.__class__.__name__
  54. prv = self.prv and self.prv.data
  55. nxt = self.nxt and self.nxt.data
  56. return '%s(prv=%s, data=%s, nxt=%s)' % (clsname, prv, self.data, nxt)
  57. def _getprv(self):
  58. return self._prv() if isinstance(self._prv, ref) else self._prv
  59. def _setprv(self, prv):
  60. self._prv = prv and ref(prv)
  61. prv = property(_getprv, _setprv)
  62. def _getnxt(self):
  63. return self._nxt() if isinstance(self._nxt, ref) else self._nxt
  64. def _setnxt(self, nxt):
  65. self._nxt = nxt and ref(nxt)
  66. nxt = property(_getnxt, _setnxt)
  67. def __getitem__(self, key_or_val):
  68. """Node items are stored as (key, value) pairs
  69. from the perspective of the forward mapping,
  70. and (value, key) pairs from the perspective of the inverse.
  71. This method provides a convenient way
  72. to get the key of a node if you know its value
  73. or the value of a node if you know its key.
  74. :raises KeyError: if *key_or_val* is neither this node's key nor value.
  75. >>> node = _Node('key', 'val')
  76. >>> node['key']
  77. 'val'
  78. >>> node['val']
  79. 'key'
  80. >>> node['missing']
  81. Traceback (most recent call last):
  82. ...
  83. KeyError: 'missing'
  84. """
  85. korv = self._korv
  86. vork = self._vork
  87. if key_or_val == korv:
  88. return vork
  89. elif key_or_val == vork:
  90. return korv
  91. raise KeyError(key_or_val)
  92. def __getstate__(self):
  93. """Return the instance state dictionary
  94. but with weakrefs converted to strong refs
  95. so that it can be pickled.
  96. *See also* :meth:`object.__getstate__`
  97. """
  98. return dict(_korv=self._korv, _vork=self._vork, _prv=self.prv, _nxt=self.nxt)
  99. def __setstate__(self, state):
  100. """Set the instance state from *state*."""
  101. self._korv = state['_korv']
  102. self._vork = state['_vork']
  103. self._setprv(state['_prv'])
  104. self._setnxt(state['_nxt'])
  105. class _Sentinel(_Node): # pylint: disable=too-few-public-methods
  106. """Special node in a circular doubly-linked list
  107. that links the first node with the last node.
  108. When its next and previous references point back to itself
  109. it represents an empty list.
  110. """
  111. __slots__ = ()
  112. def __init__(self, prv=None, nxt=None):
  113. super(_Sentinel, self).__init__(_MISS, _MISS, prv or self, nxt or self)
  114. def __repr__(self):
  115. return '<SENTINEL>'
  116. def __bool__(self): # Useful for _Node.__repr__ (see above).
  117. return False
  118. if PY2:
  119. __nonzero__ = __bool__
  120. def _iter_nodes(sntl, reverse=False):
  121. """Given the sentinel node of a circular doubly-linked list,
  122. iterate over the remaining nodes in the order specified by *reverse*.
  123. >>> sntl = _Sentinel()
  124. >>> node = _Node('key', 'val', sntl, sntl)
  125. >>> sntl.nxt = sntl.prv = node
  126. >>> list(_iter_nodes(sntl))
  127. [_Node(prv=<SENTINEL>, data=('key', 'val'), nxt=<SENTINEL>)]
  128. """
  129. attr = 'prv' if reverse else 'nxt'
  130. node = getattr(sntl, attr)
  131. while node is not sntl: # lgtm [py/comparison-using-is]
  132. yield node
  133. node = getattr(node, attr)
  134. class OrderedBidictBase(BidictBase): # lgtm [py/missing-equals]
  135. """Base class implementing an ordered :class:`BidirectionalMapping`."""
  136. __slots__ = ('_sntl',)
  137. def __init__(self, *args, **kw):
  138. """Make a new ordered bidirectional mapping.
  139. The signature is the same as that of regular dictionaries.
  140. Items passed in are added in the order they are passed,
  141. respecting this bidict type's duplication policies along the way.
  142. The order in which items are inserted is remembered,
  143. similar to :class:`collections.OrderedDict`.
  144. """
  145. self._sntl = _Sentinel()
  146. # Like unordered bidicts, ordered bidicts also store two backing one-directional mappings
  147. # `_fwdm` and `_invm`. But rather than mapping `key` to `val` and `val` to `key`
  148. # (respectively), they map `key` to `nodefwd` and `val` to `nodeinv` (respectively), where
  149. # `nodefwd` is `nodeinv` when `key` and `val` are associated with one another.
  150. # To effect this difference, `_write_item` and `_undo_write` are overridden. But much of the
  151. # rest of BidictBase's implementation, including BidictBase.__init__ and BidictBase._update,
  152. # are inherited and are able to be reused without modification.
  153. super(OrderedBidictBase, self).__init__(*args, **kw)
  154. def _init_inv(self):
  155. super(OrderedBidictBase, self)._init_inv()
  156. self.inv._sntl = self._sntl # pylint: disable=protected-access
  157. # Can't reuse BidictBase.copy since ordered bidicts have different internal structure.
  158. def copy(self):
  159. """A shallow copy of this ordered bidict."""
  160. # Fast copy implementation bypassing __init__. See comments in :meth:`BidictBase.copy`.
  161. copy = self.__class__.__new__(self.__class__)
  162. sntl = _Sentinel()
  163. fwdm = dict.fromkeys(self._fwdm)
  164. invm = dict.fromkeys(self._invm)
  165. cur = sntl
  166. nxt = sntl.nxt
  167. for (key, val) in iteritems(self):
  168. nxt = _Node(key, val, cur, sntl)
  169. cur.nxt = fwdm[key] = invm[val] = nxt
  170. cur = nxt
  171. sntl.prv = nxt
  172. copy._sntl = sntl # pylint: disable=protected-access
  173. copy._fwdm = fwdm # pylint: disable=protected-access
  174. copy._invm = invm # pylint: disable=protected-access
  175. copy._init_inv() # pylint: disable=protected-access
  176. return copy
  177. def __getitem__(self, key):
  178. nodefwd = self._fwdm[key]
  179. val = nodefwd[key]
  180. nodeinv = self._invm[val]
  181. assert nodeinv is nodefwd
  182. return val
  183. def _pop(self, key):
  184. nodefwd = self._fwdm.pop(key)
  185. val = nodefwd[key]
  186. nodeinv = self._invm.pop(val)
  187. assert nodeinv is nodefwd
  188. nodefwd.prv.nxt = nodefwd.nxt
  189. nodefwd.nxt.prv = nodefwd.prv
  190. return val
  191. def _isdupitem(self, key, val, dedup_result):
  192. """Return whether (key, val) duplicates an existing item."""
  193. isdupkey, isdupval, nodeinv, nodefwd = dedup_result
  194. isdupitem = nodeinv is nodefwd
  195. if isdupitem:
  196. assert isdupkey
  197. assert isdupval
  198. assert nodefwd[key] == val
  199. assert nodefwd[val] == key
  200. return isdupitem
  201. def _write_item(self, key, val, dedup_result): # pylint: disable=too-many-locals
  202. fwdm = self._fwdm
  203. invm = self._invm
  204. # Order of (key, val) in node data is not significant but might as well try to keep uniform.
  205. nodedata = (val, key) if self._isinv else (key, val)
  206. isdupkey, isdupval, nodeinv, nodefwd = dedup_result
  207. if not isdupkey and not isdupval:
  208. # No key or value duplication -> create and append a new node.
  209. sntl = self._sntl
  210. last = sntl.prv
  211. node = _Node(nodedata[0], nodedata[1], last, sntl)
  212. last.nxt = sntl.prv = fwdm[key] = invm[val] = node
  213. oldkey = oldval = _MISS
  214. elif isdupkey and isdupval:
  215. # Key and value duplication across two different nodes.
  216. assert nodefwd is not nodeinv
  217. oldval = nodefwd[key]
  218. oldkey = nodeinv[val]
  219. assert oldkey != key
  220. assert oldval != val
  221. # We have to collapse nodefwd and nodeinv into a single node, i.e. drop one of them.
  222. # Drop nodeinv, so that the item with the same key is the one overwritten in place.
  223. nodeinv.prv.nxt = nodeinv.nxt
  224. nodeinv.nxt.prv = nodeinv.prv
  225. # Don't remove nodeinv's references to its neighbors since
  226. # if the update fails, we'll need them to undo this write.
  227. # Update fwdm and invm.
  228. tmp = fwdm.pop(oldkey)
  229. assert tmp is nodeinv
  230. tmp = invm.pop(oldval)
  231. assert tmp is nodefwd
  232. fwdm[key] = invm[val] = nodefwd
  233. # Update nodefwd with new item.
  234. nodefwd.data = nodedata
  235. elif isdupkey:
  236. oldval = nodefwd[key]
  237. oldkey = _MISS
  238. oldnodeinv = invm.pop(oldval)
  239. assert oldnodeinv is nodefwd
  240. invm[val] = nodefwd
  241. nodefwd.data = nodedata
  242. else: # isdupval
  243. oldkey = nodeinv[val]
  244. oldval = _MISS
  245. oldnodefwd = fwdm.pop(oldkey)
  246. assert oldnodefwd is nodeinv
  247. fwdm[key] = nodeinv
  248. nodeinv.data = nodedata
  249. return _WriteResult(key, val, oldkey, oldval)
  250. def _undo_write(self, dedup_result, write_result): # pylint: disable=too-many-locals
  251. fwdm = self._fwdm
  252. invm = self._invm
  253. isdupkey, isdupval, nodeinv, nodefwd = dedup_result
  254. key, val, oldkey, oldval = write_result
  255. if not isdupkey and not isdupval:
  256. self._pop(key)
  257. elif isdupkey and isdupval:
  258. # nodeinv.data was never changed, so it should still have its original item.
  259. assert nodeinv[oldkey] == val
  260. assert nodeinv[val] == oldkey
  261. # Restore original items.
  262. nodefwd.data = (key, oldval)
  263. nodeinv.prv.nxt = nodeinv.nxt.prv = nodeinv
  264. fwdm[oldkey] = invm[val] = nodeinv
  265. invm[oldval] = fwdm[key] = nodefwd
  266. elif isdupkey:
  267. nodefwd.data = (key, oldval)
  268. tmp = invm.pop(val)
  269. assert tmp is nodefwd
  270. invm[oldval] = nodefwd
  271. assert fwdm[key] is nodefwd
  272. else: # isdupval
  273. nodeinv.data = (oldkey, val)
  274. tmp = fwdm.pop(key)
  275. assert tmp is nodeinv
  276. fwdm[oldkey] = nodeinv
  277. assert invm[val] is nodeinv
  278. def __iter__(self, reverse=False):
  279. """An iterator over this bidict's items in order."""
  280. fwdm = self._fwdm
  281. for node in _iter_nodes(self._sntl, reverse=reverse):
  282. korv, vork = node.data
  283. key = korv if node is fwdm.get(korv) else vork
  284. yield key
  285. def __reversed__(self):
  286. """An iterator over this bidict's items in reverse order."""
  287. for key in self.__iter__(reverse=True):
  288. yield key
  289. def equals_order_sensitive(self, other):
  290. """Order-sensitive equality check.
  291. *See also* :ref:`eq-order-insensitive`
  292. """
  293. if not isinstance(other, Mapping) or len(self) != len(other):
  294. return False
  295. return all(i == j for (i, j) in izip(iteritems(self), iteritems(other)))
  296. def __repr_delegate__(self):
  297. """See :meth:`bidict.BidictBase.__repr_delegate__`."""
  298. return list(iteritems(self))
  299. # Override the `values` implementation inherited from `Mapping`. Implemented in terms of
  300. # self.inv.keys(), so that on Python 3 we end up returning a KeysView (dict_keys) object.
  301. def values(self):
  302. """A set-like object providing a view on the contained values.
  303. Note that because the values of a :class:`~bidict.BidirectionalMapping`
  304. are the keys of its inverse,
  305. this returns a :class:`~collections.abc.KeysView`
  306. rather than a :class:`~collections.abc.ValuesView`,
  307. which has the advantages of constant-time containment checks
  308. and supporting set operations.
  309. """
  310. return self.inv.keys()
  311. if PY2:
  312. def viewvalues(self): # noqa: D102; pylint: disable=missing-docstring
  313. return KeysView(self.inv)
  314. viewvalues.__doc__ = values.__doc__
  315. values.__doc__ = 'A list of the contained values.'
  316. def viewitems(self):
  317. """A set-like object providing a view on the contained items."""
  318. return ItemsView(self)
  319. def viewkeys(self):
  320. """A set-like object providing a view on the contained keys."""
  321. return KeysView(self)
  322. # * Code review nav *
  323. #==============================================================================
  324. # ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
  325. #==============================================================================