From b4dfd407bf29e70ea4d0bdd9339880287a999531 Mon Sep 17 00:00:00 2001 From: jab Date: Fri, 6 May 2016 23:27:02 +0000 Subject: [PATCH] try to avoid malloc in _update --- bidict/_common.py | 32 +++++++++++++++++++++++++++++++- bidict/compat.py | 4 +++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/bidict/_common.py b/bidict/_common.py index 0800a39c..f9291ed2 100644 --- a/bidict/_common.py +++ b/bidict/_common.py @@ -4,9 +4,10 @@ Also provides related exception classes and collision behaviors. """ -from .compat import PY2, iteritems, viewkeys +from .compat import PY2, ifilter, iterkeys, iteritems, itervalues, viewkeys, viewitems from .util import pairs from collections import Mapping +from itertools import chain def _proxied(methodname, ivarname='_fwd', doc=None): @@ -131,6 +132,35 @@ def _update(self, on_key_coll, on_val_coll, *args, **kw): _fwd = self._fwd _inv = self._inv missing = object() + + # Optimization: Try to detect duplicate keys and values early + # before doing any mallocs. + arg0 = args[0] if args else {} + if isinstance(arg0, Mapping): + if on_key_coll is RAISE: + if arg0 and kw: + # New mappings in both arg0 and kw -> + # Check if new key given twice with different values. + d1, d2 = (arg0, kw) if len(arg0) < len(kw) else (kw, arg0) + for (k, v) in iteritems(d1): + v2 = d2.get(k, missing) + if v2 is not missing and v2 != v: + raise KeyNotUniqueError(k) + # Check if a new key duplicates an existing key. + dupk = next(ifilter(_fwd.__contains__, chain(iterkeys(arg0), iterkeys(kw))), missing) + if dupk is not missing: + raise KeyExistsError((dupk, _fwd[dupk])) + if on_val_coll is RAISE: + # Want to check if a new value was given twice with different keys, + # but there's no way to do this in O(n) time without a malloc. + # So skip checking this here; we'll catch it below. + # --- + # Check if a new value duplicates an existing value. + dupv = next(ifilter(_inv.__contains__, chain(itervalues(arg0), itervalues(kw))), missing) + if dupv is not missing: + raise ValueExistsError((_inv[dupv], dupv)) + # End optimization. + updatefwd = self._dcls() updateinv = self._dcls() diff --git a/bidict/compat.py b/bidict/compat.py index 072b2681..1cb30646 100644 --- a/bidict/compat.py +++ b/bidict/compat.py @@ -57,7 +57,7 @@ iterkeys = methodcaller('iterkeys') itervalues = methodcaller('itervalues') iteritems = methodcaller('iteritems') - from itertools import izip, izip_longest + from itertools import ifilter, imap, izip, izip_longest else: # pragma: no cover viewkeys = methodcaller('keys') viewvalues = methodcaller('values') @@ -65,5 +65,7 @@ iterkeys = _compose(iter, viewkeys) itervalues = _compose(iter, viewvalues) iteritems = _compose(iter, viewitems) + ifilter = filter + imap = map izip = zip from itertools import zip_longest as izip_longest