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