Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/toolz/dicttoolz.py: 27%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import operator
2import collections
3from functools import reduce
4from collections.abc import Mapping
6__all__ = ('merge', 'merge_with', 'valmap', 'keymap', 'itemmap',
7 'valfilter', 'keyfilter', 'itemfilter',
8 'assoc', 'dissoc', 'assoc_in', 'update_in', 'get_in')
11def _get_factory(f, kwargs):
12 factory = kwargs.pop('factory', dict)
13 if kwargs:
14 raise TypeError("{}() got an unexpected keyword argument "
15 "'{}'".format(f.__name__, kwargs.popitem()[0]))
16 return factory
19def merge(*dicts, **kwargs):
20 """ Merge a collection of dictionaries
22 >>> merge({1: 'one'}, {2: 'two'})
23 {1: 'one', 2: 'two'}
25 Later dictionaries have precedence
27 >>> merge({1: 2, 3: 4}, {3: 3, 4: 4})
28 {1: 2, 3: 3, 4: 4}
30 See Also:
31 merge_with
32 """
33 if len(dicts) == 1 and not isinstance(dicts[0], Mapping):
34 dicts = dicts[0]
35 factory = _get_factory(merge, kwargs)
37 rv = factory()
38 for d in dicts:
39 rv.update(d)
40 return rv
43def merge_with(func, *dicts, **kwargs):
44 """ Merge dictionaries and apply function to combined values
46 A key may occur in more than one dict, and all values mapped from the key
47 will be passed to the function as a list, such as func([val1, val2, ...]).
49 >>> merge_with(sum, {1: 1, 2: 2}, {1: 10, 2: 20})
50 {1: 11, 2: 22}
52 >>> merge_with(first, {1: 1, 2: 2}, {2: 20, 3: 30}) # doctest: +SKIP
53 {1: 1, 2: 2, 3: 30}
55 See Also:
56 merge
57 """
58 if len(dicts) == 1 and not isinstance(dicts[0], Mapping):
59 dicts = dicts[0]
60 factory = _get_factory(merge_with, kwargs)
62 values = collections.defaultdict(lambda: [].append)
63 for d in dicts:
64 for k, v in d.items():
65 values[k](v)
67 result = factory()
68 for k, v in values.items():
69 result[k] = func(v.__self__)
70 return result
73def valmap(func, d, factory=dict):
74 """ Apply function to values of dictionary
76 >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]}
77 >>> valmap(sum, bills) # doctest: +SKIP
78 {'Alice': 65, 'Bob': 45}
80 See Also:
81 keymap
82 itemmap
83 """
84 rv = factory()
85 rv.update(zip(d.keys(), map(func, d.values())))
86 return rv
89def keymap(func, d, factory=dict):
90 """ Apply function to keys of dictionary
92 >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]}
93 >>> keymap(str.lower, bills) # doctest: +SKIP
94 {'alice': [20, 15, 30], 'bob': [10, 35]}
96 See Also:
97 valmap
98 itemmap
99 """
100 rv = factory()
101 rv.update(zip(map(func, d.keys()), d.values()))
102 return rv
105def itemmap(func, d, factory=dict):
106 """ Apply function to items of dictionary
108 >>> accountids = {"Alice": 10, "Bob": 20}
109 >>> itemmap(reversed, accountids) # doctest: +SKIP
110 {10: "Alice", 20: "Bob"}
112 See Also:
113 keymap
114 valmap
115 """
116 rv = factory()
117 rv.update(map(func, d.items()))
118 return rv
121def valfilter(predicate, d, factory=dict):
122 """ Filter items in dictionary by value
124 >>> iseven = lambda x: x % 2 == 0
125 >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
126 >>> valfilter(iseven, d)
127 {1: 2, 3: 4}
129 See Also:
130 keyfilter
131 itemfilter
132 valmap
133 """
134 rv = factory()
135 for k, v in d.items():
136 if predicate(v):
137 rv[k] = v
138 return rv
141def keyfilter(predicate, d, factory=dict):
142 """ Filter items in dictionary by key
144 >>> iseven = lambda x: x % 2 == 0
145 >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
146 >>> keyfilter(iseven, d)
147 {2: 3, 4: 5}
149 See Also:
150 valfilter
151 itemfilter
152 keymap
153 """
154 rv = factory()
155 for k, v in d.items():
156 if predicate(k):
157 rv[k] = v
158 return rv
161def itemfilter(predicate, d, factory=dict):
162 """ Filter items in dictionary by item
164 >>> def isvalid(item):
165 ... k, v = item
166 ... return k % 2 == 0 and v < 4
168 >>> d = {1: 2, 2: 3, 3: 4, 4: 5}
169 >>> itemfilter(isvalid, d)
170 {2: 3}
172 See Also:
173 keyfilter
174 valfilter
175 itemmap
176 """
177 rv = factory()
178 for item in d.items():
179 if predicate(item):
180 k, v = item
181 rv[k] = v
182 return rv
185def assoc(d, key, value, factory=dict):
186 """ Return a new dict with new key value pair
188 New dict has d[key] set to value. Does not modify the initial dictionary.
190 >>> assoc({'x': 1}, 'x', 2)
191 {'x': 2}
192 >>> assoc({'x': 1}, 'y', 3) # doctest: +SKIP
193 {'x': 1, 'y': 3}
194 """
195 d2 = factory()
196 d2.update(d)
197 d2[key] = value
198 return d2
201def dissoc(d, *keys, **kwargs):
202 """ Return a new dict with the given key(s) removed.
204 New dict has d[key] deleted for each supplied key.
205 Does not modify the initial dictionary.
207 >>> dissoc({'x': 1, 'y': 2}, 'y')
208 {'x': 1}
209 >>> dissoc({'x': 1, 'y': 2}, 'y', 'x')
210 {}
211 >>> dissoc({'x': 1}, 'y') # Ignores missing keys
212 {'x': 1}
213 """
214 factory = _get_factory(dissoc, kwargs)
215 d2 = factory()
217 if len(keys) < len(d) * .6:
218 d2.update(d)
219 for key in keys:
220 if key in d2:
221 del d2[key]
222 else:
223 remaining = set(d)
224 remaining.difference_update(keys)
225 for k in remaining:
226 d2[k] = d[k]
227 return d2
230def assoc_in(d, keys, value, factory=dict):
231 """ Return a new dict with new, potentially nested, key value pair
233 >>> purchase = {'name': 'Alice',
234 ... 'order': {'items': ['Apple', 'Orange'],
235 ... 'costs': [0.50, 1.25]},
236 ... 'credit card': '5555-1234-1234-1234'}
237 >>> assoc_in(purchase, ['order', 'costs'], [0.25, 1.00]) # doctest: +SKIP
238 {'credit card': '5555-1234-1234-1234',
239 'name': 'Alice',
240 'order': {'costs': [0.25, 1.00], 'items': ['Apple', 'Orange']}}
241 """
242 return update_in(d, keys, lambda x: value, value, factory)
245def update_in(d, keys, func, default=None, factory=dict):
246 """ Update value in a (potentially) nested dictionary
248 inputs:
249 d - dictionary on which to operate
250 keys - list or tuple giving the location of the value to be changed in d
251 func - function to operate on that value
253 If keys == [k0,..,kX] and d[k0]..[kX] == v, update_in returns a copy of the
254 original dictionary with v replaced by func(v), but does not mutate the
255 original dictionary.
257 If k0 is not a key in d, update_in creates nested dictionaries to the depth
258 specified by the keys, with the innermost value set to func(default).
260 >>> inc = lambda x: x + 1
261 >>> update_in({'a': 0}, ['a'], inc)
262 {'a': 1}
264 >>> transaction = {'name': 'Alice',
265 ... 'purchase': {'items': ['Apple', 'Orange'],
266 ... 'costs': [0.50, 1.25]},
267 ... 'credit card': '5555-1234-1234-1234'}
268 >>> update_in(transaction, ['purchase', 'costs'], sum) # doctest: +SKIP
269 {'credit card': '5555-1234-1234-1234',
270 'name': 'Alice',
271 'purchase': {'costs': 1.75, 'items': ['Apple', 'Orange']}}
273 >>> # updating a value when k0 is not in d
274 >>> update_in({}, [1, 2, 3], str, default="bar")
275 {1: {2: {3: 'bar'}}}
276 >>> update_in({1: 'foo'}, [2, 3, 4], inc, 0)
277 {1: 'foo', 2: {3: {4: 1}}}
278 """
279 ks = iter(keys)
280 k = next(ks)
282 rv = inner = factory()
283 rv.update(d)
285 for key in ks:
286 if k in d:
287 d = d[k]
288 dtemp = factory()
289 dtemp.update(d)
290 else:
291 d = dtemp = factory()
293 inner[k] = inner = dtemp
294 k = key
296 if k in d:
297 inner[k] = func(d[k])
298 else:
299 inner[k] = func(default)
300 return rv
303def get_in(keys, coll, default=None, no_default=False):
304 """ Returns coll[i0][i1]...[iX] where [i0, i1, ..., iX]==keys.
306 If coll[i0][i1]...[iX] cannot be found, returns ``default``, unless
307 ``no_default`` is specified, then it raises KeyError or IndexError.
309 ``get_in`` is a generalization of ``operator.getitem`` for nested data
310 structures such as dictionaries and lists.
312 >>> transaction = {'name': 'Alice',
313 ... 'purchase': {'items': ['Apple', 'Orange'],
314 ... 'costs': [0.50, 1.25]},
315 ... 'credit card': '5555-1234-1234-1234'}
316 >>> get_in(['purchase', 'items', 0], transaction)
317 'Apple'
318 >>> get_in(['name'], transaction)
319 'Alice'
320 >>> get_in(['purchase', 'total'], transaction)
321 >>> get_in(['purchase', 'items', 'apple'], transaction)
322 >>> get_in(['purchase', 'items', 10], transaction)
323 >>> get_in(['purchase', 'total'], transaction, 0)
324 0
325 >>> get_in(['y'], {}, no_default=True)
326 Traceback (most recent call last):
327 ...
328 KeyError: 'y'
330 See Also:
331 itertoolz.get
332 operator.getitem
333 """
334 try:
335 return reduce(operator.getitem, keys, coll)
336 except (KeyError, IndexError, TypeError):
337 if no_default:
338 raise
339 return default