|
|
- from cpython.dict cimport (PyDict_Check, PyDict_CheckExact, PyDict_GetItem,
- PyDict_Merge, PyDict_New, PyDict_Next,
- PyDict_SetItem, PyDict_Update, PyDict_DelItem)
- from cpython.list cimport PyList_Append, PyList_New
- from cpython.object cimport PyObject_SetItem
- from cpython.ref cimport PyObject, Py_DECREF, Py_INCREF, Py_XDECREF
-
- # Locally defined bindings that differ from `cython.cpython` bindings
- from cytoolz.cpython cimport PyDict_Next_Compat, PtrIter_Next
-
- from copy import copy
-
-
- __all__ = ['merge', 'merge_with', 'valmap', 'keymap', 'itemmap', 'valfilter',
- 'keyfilter', 'itemfilter', 'assoc', 'dissoc', 'assoc_in', 'get_in',
- 'update_in']
-
-
- cdef int PyMapping_Next(object p, Py_ssize_t *ppos, PyObject* *pkey, PyObject* *pval) except -1:
- """Mimic "PyDict_Next" interface, but for any mapping"""
- cdef PyObject *obj
- obj = PtrIter_Next(p)
- if obj is NULL:
- return 0
- pkey[0] = <PyObject*>(<object>obj)[0]
- pval[0] = <PyObject*>(<object>obj)[1]
- Py_XDECREF(obj)
- return 1
-
-
- cdef f_map_next get_map_iter(object d, PyObject* *ptr) except NULL:
- """Return function pointer to perform iteration over object returned in ptr.
-
- The returned function signature matches "PyDict_Next". If ``d`` is a dict,
- then the returned function *is* PyDict_Next, so iteration wil be very fast.
-
- The object returned through ``ptr`` needs to have its reference count
- reduced by one once the caller "owns" the object.
-
- This function lets us control exactly how iteration should be performed
- over a given mapping. The current rules are:
-
- 1) If ``d`` is exactly a dict, use PyDict_Next
- 2) If ``d`` is subtype of dict, use PyMapping_Next. This lets the user
- control the order iteration, such as for ordereddict.
- 3) If using PyMapping_Next, iterate using ``iteritems`` if possible,
- otherwise iterate using ``items``.
-
- """
- cdef object val
- cdef f_map_next rv
- if PyDict_CheckExact(d):
- val = d
- rv = &PyDict_Next_Compat
- elif hasattr(d, 'iteritems'):
- val = iter(d.iteritems())
- rv = &PyMapping_Next
- else:
- val = iter(d.items())
- rv = &PyMapping_Next
- Py_INCREF(val)
- ptr[0] = <PyObject*>val
- return rv
-
-
- cdef get_factory(name, kwargs):
- factory = kwargs.pop('factory', dict)
- if kwargs:
- raise TypeError("{0}() got an unexpected keyword argument "
- "'{1}'".format(name, kwargs.popitem()[0]))
- return factory
-
-
- cdef object c_merge(object dicts, object factory=dict):
- cdef object rv
- rv = factory()
- if PyDict_CheckExact(rv):
- for d in dicts:
- PyDict_Update(rv, d)
- else:
- for d in dicts:
- rv.update(d)
- return rv
-
-
- def merge(*dicts, **kwargs):
- """
- Merge a collection of dictionaries
-
- >>> merge({1: 'one'}, {2: 'two'})
- {1: 'one', 2: 'two'}
-
- Later dictionaries have precedence
-
- >>> merge({1: 2, 3: 4}, {3: 3, 4: 4})
- {1: 2, 3: 3, 4: 4}
-
- See Also:
- merge_with
- """
- if len(dicts) == 1 and not PyDict_Check(dicts[0]):
- dicts = dicts[0]
- factory = get_factory('merge', kwargs)
- return c_merge(dicts, factory)
-
-
- cdef object c_merge_with(object func, object dicts, object factory=dict):
- cdef:
- dict result
- object rv, d
- list seq
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos
-
- result = PyDict_New()
- rv = factory()
- for d in dicts:
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- pos = 0
- while f(d, &pos, &pkey, &pval):
- obj = PyDict_GetItem(result, <object>pkey)
- if obj is NULL:
- seq = PyList_New(0)
- PyList_Append(seq, <object>pval)
- PyDict_SetItem(result, <object>pkey, seq)
- else:
- PyList_Append(<object>obj, <object>pval)
-
- f = get_map_iter(result, &obj)
- d = <object>obj
- Py_DECREF(d)
- pos = 0
- while f(d, &pos, &pkey, &pval):
- PyObject_SetItem(rv, <object>pkey, func(<object>pval))
- return rv
-
-
- def merge_with(func, *dicts, **kwargs):
- """
- Merge dictionaries and apply function to combined values
-
- A key may occur in more than one dict, and all values mapped from the key
- will be passed to the function as a list, such as func([val1, val2, ...]).
-
- >>> merge_with(sum, {1: 1, 2: 2}, {1: 10, 2: 20})
- {1: 11, 2: 22}
-
- >>> merge_with(first, {1: 1, 2: 2}, {2: 20, 3: 30}) # doctest: +SKIP
- {1: 1, 2: 2, 3: 30}
-
- See Also:
- merge
- """
- if len(dicts) == 1 and not PyDict_Check(dicts[0]):
- dicts = dicts[0]
- factory = get_factory('merge_with', kwargs)
- return c_merge_with(func, dicts, factory)
-
-
- cpdef object valmap(object func, object d, object factory=dict):
- """
- Apply function to values of dictionary
-
- >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]}
- >>> valmap(sum, bills) # doctest: +SKIP
- {'Alice': 65, 'Bob': 45}
-
- See Also:
- keymap
- itemmap
- """
- cdef:
- object rv
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- rv[<object>pkey] = func(<object>pval)
- return rv
-
-
- cpdef object keymap(object func, object d, object factory=dict):
- """
- Apply function to keys of dictionary
-
- >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]}
- >>> keymap(str.lower, bills) # doctest: +SKIP
- {'alice': [20, 15, 30], 'bob': [10, 35]}
-
- See Also:
- valmap
- itemmap
- """
- cdef:
- object rv
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- rv[func(<object>pkey)] = <object>pval
- return rv
-
-
- cpdef object itemmap(object func, object d, object factory=dict):
- """
- Apply function to items of dictionary
-
- >>> accountids = {"Alice": 10, "Bob": 20}
- >>> itemmap(reversed, accountids) # doctest: +SKIP
- {10: "Alice", 20: "Bob"}
-
- See Also:
- keymap
- valmap
- """
- cdef:
- object rv, k, v
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- k, v = func((<object>pkey, <object>pval))
- rv[k] = v
- return rv
-
-
- cpdef object valfilter(object predicate, object d, object factory=dict):
- """
- Filter items in dictionary by value
-
- >>> iseven = lambda x: x % 2 == 0
- >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
- >>> valfilter(iseven, d)
- {1: 2, 3: 4}
-
- See Also:
- keyfilter
- itemfilter
- valmap
- """
- cdef:
- object rv
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- if predicate(<object>pval):
- rv[<object>pkey] = <object>pval
- return rv
-
-
- cpdef object keyfilter(object predicate, object d, object factory=dict):
- """
- Filter items in dictionary by key
-
- >>> iseven = lambda x: x % 2 == 0
- >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
- >>> keyfilter(iseven, d)
- {2: 3, 4: 5}
-
- See Also:
- valfilter
- itemfilter
- keymap
- """
- cdef:
- object rv
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- if predicate(<object>pkey):
- rv[<object>pkey] = <object>pval
- return rv
-
-
- cpdef object itemfilter(object predicate, object d, object factory=dict):
- """
- Filter items in dictionary by item
-
- >>> def isvalid(item):
- ... k, v = item
- ... return k % 2 == 0 and v < 4
-
- >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
- >>> itemfilter(isvalid, d)
- {2: 3}
-
- See Also:
- keyfilter
- valfilter
- itemmap
- """
- cdef:
- object rv, k, v
- f_map_next f
- PyObject *obj
- PyObject *pkey
- PyObject *pval
- Py_ssize_t pos = 0
-
- rv = factory()
- f = get_map_iter(d, &obj)
- d = <object>obj
- Py_DECREF(d)
- while f(d, &pos, &pkey, &pval):
- k = <object>pkey
- v = <object>pval
- if predicate((k, v)):
- rv[k] = v
- return rv
-
-
- cpdef object assoc(object d, object key, object value, object factory=dict):
- """
- Return a new dict with new key value pair
-
- New dict has d[key] set to value. Does not modify the initial dictionary.
-
- >>> assoc({'x': 1}, 'x', 2)
- {'x': 2}
- >>> assoc({'x': 1}, 'y', 3) # doctest: +SKIP
- {'x': 1, 'y': 3}
- """
- cdef object rv
- rv = factory()
- if PyDict_CheckExact(rv):
- PyDict_Update(rv, d)
- else:
- rv.update(d)
- rv[key] = value
- return rv
-
-
- cpdef object assoc_in(object d, object keys, object value, object factory=dict):
- """
- Return a new dict with new, potentially nested, key value pair
-
- >>> purchase = {'name': 'Alice',
- ... 'order': {'items': ['Apple', 'Orange'],
- ... 'costs': [0.50, 1.25]},
- ... 'credit card': '5555-1234-1234-1234'}
- >>> assoc_in(purchase, ['order', 'costs'], [0.25, 1.00]) # doctest: +SKIP
- {'credit card': '5555-1234-1234-1234',
- 'name': 'Alice',
- 'order': {'costs': [0.25, 1.00], 'items': ['Apple', 'Orange']}}
- """
- cdef object prevkey, key
- cdef object rv, inner, dtemp
- prevkey, keys = keys[0], keys[1:]
- rv = factory()
- if PyDict_CheckExact(rv):
- PyDict_Update(rv, d)
- else:
- rv.update(d)
- inner = rv
-
- for key in keys:
- if prevkey in d:
- d = d[prevkey]
- dtemp = factory()
- if PyDict_CheckExact(dtemp):
- PyDict_Update(dtemp, d)
- else:
- dtemp.update(d)
- else:
- d = factory()
- dtemp = d
- inner[prevkey] = dtemp
- prevkey = key
- inner = dtemp
-
- inner[prevkey] = value
- return rv
-
-
- cdef object c_dissoc(object d, object keys):
- cdef object rv, key
- rv = copy(d)
- for key in keys:
- if key in rv:
- del rv[key]
- return rv
-
-
- def dissoc(d, *keys):
- """
- Return a new dict with the given key(s) removed.
-
- New dict has d[key] deleted for each supplied key.
- Does not modify the initial dictionary.
-
- >>> dissoc({'x': 1, 'y': 2}, 'y')
- {'x': 1}
- >>> dissoc({'x': 1, 'y': 2}, 'y', 'x')
- {}
- >>> dissoc({'x': 1}, 'y') # Ignores missing keys
- {'x': 1}
- """
- return c_dissoc(d, keys)
-
-
- cpdef object update_in(object d, object keys, object func, object default=None, object factory=dict):
- """
- Update value in a (potentially) nested dictionary
-
- inputs:
- d - dictionary on which to operate
- keys - list or tuple giving the location of the value to be changed in d
- func - function to operate on that value
-
- If keys == [k0,..,kX] and d[k0]..[kX] == v, update_in returns a copy of the
- original dictionary with v replaced by func(v), but does not mutate the
- original dictionary.
-
- If k0 is not a key in d, update_in creates nested dictionaries to the depth
- specified by the keys, with the innermost value set to func(default).
-
- >>> inc = lambda x: x + 1
- >>> update_in({'a': 0}, ['a'], inc)
- {'a': 1}
-
- >>> transaction = {'name': 'Alice',
- ... 'purchase': {'items': ['Apple', 'Orange'],
- ... 'costs': [0.50, 1.25]},
- ... 'credit card': '5555-1234-1234-1234'}
- >>> update_in(transaction, ['purchase', 'costs'], sum) # doctest: +SKIP
- {'credit card': '5555-1234-1234-1234',
- 'name': 'Alice',
- 'purchase': {'costs': 1.75, 'items': ['Apple', 'Orange']}}
-
- >>> # updating a value when k0 is not in d
- >>> update_in({}, [1, 2, 3], str, default="bar")
- {1: {2: {3: 'bar'}}}
- >>> update_in({1: 'foo'}, [2, 3, 4], inc, 0)
- {1: 'foo', 2: {3: {4: 1}}}
- """
- cdef object prevkey, key
- cdef object rv, inner, dtemp
- prevkey, keys = keys[0], keys[1:]
- rv = factory()
- if PyDict_CheckExact(rv):
- PyDict_Update(rv, d)
- else:
- rv.update(d)
- inner = rv
-
- for key in keys:
- if prevkey in d:
- d = d[prevkey]
- dtemp = factory()
- if PyDict_CheckExact(dtemp):
- PyDict_Update(dtemp, d)
- else:
- dtemp.update(d)
- else:
- d = factory()
- dtemp = d
- inner[prevkey] = dtemp
- prevkey = key
- inner = dtemp
-
- if prevkey in d:
- key = func(d[prevkey])
- else:
- key = func(default)
- inner[prevkey] = key
- return rv
-
-
- cdef tuple _get_in_exceptions = (KeyError, IndexError, TypeError)
-
-
- cpdef object get_in(object keys, object coll, object default=None, object no_default=False):
- """
- Returns coll[i0][i1]...[iX] where [i0, i1, ..., iX]==keys.
-
- If coll[i0][i1]...[iX] cannot be found, returns ``default``, unless
- ``no_default`` is specified, then it raises KeyError or IndexError.
-
- ``get_in`` is a generalization of ``operator.getitem`` for nested data
- structures such as dictionaries and lists.
-
- >>> transaction = {'name': 'Alice',
- ... 'purchase': {'items': ['Apple', 'Orange'],
- ... 'costs': [0.50, 1.25]},
- ... 'credit card': '5555-1234-1234-1234'}
- >>> get_in(['purchase', 'items', 0], transaction)
- 'Apple'
- >>> get_in(['name'], transaction)
- 'Alice'
- >>> get_in(['purchase', 'total'], transaction)
- >>> get_in(['purchase', 'items', 'apple'], transaction)
- >>> get_in(['purchase', 'items', 10], transaction)
- >>> get_in(['purchase', 'total'], transaction, 0)
- 0
- >>> get_in(['y'], {}, no_default=True)
- Traceback (most recent call last):
- ...
- KeyError: 'y'
-
- See Also:
- itertoolz.get
- operator.getitem
- """
- cdef object item
- try:
- for item in keys:
- coll = coll[item]
- return coll
- except _get_in_exceptions:
- if no_default:
- raise
- return default
-
|