forked from cool-RR/python_toolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdict_tools.py
More file actions
173 lines (128 loc) · 5.1 KB
/
dict_tools.py
File metadata and controls
173 lines (128 loc) · 5.1 KB
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
# Copyright 2009-2017 Ram Rachum.
# This program is distributed under the MIT license.
'''Defines several functions that may be useful when working with dicts.'''
from __future__ import generator_stop
import collections
from python_toolbox import cute_iter_tools
from python_toolbox import comparison_tools
def filter_items(d, condition, double=False, force_dict_type=None):
'''
Get new dict with items from `d` that satisfy the `condition` functions.
`condition` is a function that takes a key and a value.
The newly created dict will be of the same class as `d`, e.g. if you passed
an ordered dict as `d`, the result will be an ordered dict, using the
correct order.
Specify `double=True` to get a tuple of two dicts instead of one. The
second dict will have all the rejected items.
'''
# todo future: possibly shallow-copy `d` to allow for dict classes that
# have more state, (like default factory.)
if force_dict_type is not None:
dict_type = force_dict_type
else:
dict_type = type(d) if (type(d).__name__ != 'dictproxy') else dict
if double:
return tuple(
map(
dict_type,
cute_iter_tools.double_filter(
lambda key_value: condition(key_value[0], key_value[1]),
d.items()
)
)
)
else:
return dict_type(
(key, value) for (key, value) in d.items() if condition(key, value)
)
def get_tuple(d, iterable):
'''Get a tuple of values corresponding to an `iterable` of keys.'''
return tuple(d[key] for key in iterable)
def get_contained(d, container):
'''Get a list of the values in the dict whose keys are in `container`.'''
return [value for (key, value) in d.items() if (key in container)]
def fancy_string(d, indent=0):
'''Show a dict as a string, slightly nicer than dict.__repr__.'''
small_space = ' ' * indent
big_space = ' ' * (indent + 4)
huge_space = ' ' * (indent + 8)
def show(thing, indent=0):
space = ' ' * indent
enter_then_space = '\n' + space
return repr(thing).replace('\n', enter_then_space)
temp1 = (
(big_space + repr(key) + ':\n' + huge_space + show(value, indent + 8))
for (key, value) in list(d.items()))
temp2 = small_space + '{\n' + ',\n'.join(temp1) + '\n' + small_space +'}'
return temp2
def devour_items(d):
'''Iterator that pops (key, value) pairs from `d` until it's empty.'''
while d:
yield d.popitem()
def devour_keys(d):
'''Iterator that pops keys from `d` until it's exhaused (i.e. empty).'''
while d:
key = next(iter(d.keys()))
del d[key]
yield key
def sum_dicts(dicts):
'''
Return the sum of a bunch of dicts i.e. all the dicts merged into one.
If there are any collisions, the latest dicts in the sequence win.
'''
result = {}
for dict_ in dicts:
result.update(dict_)
return result
def remove_keys(d, keys_to_remove):
'''
Remove keys from a dict.
`keys_to_remove` is allowed to be either an iterable (in which case it will
be iterated on and keys with the same name will be removed), a container
(in which case this function will iterate over the keys of the dict, and if
they're contained they'll be removed), or a filter function (in which case
this function will iterate over the keys of the dict, and if they pass the
filter function they'll be removed.)
If key doesn't exist, doesn't raise an exception.
'''
if isinstance(keys_to_remove, collections.abc.Iterable):
for key in keys_to_remove:
try:
del d[key]
except KeyError:
pass
else:
if isinstance(keys_to_remove, collections.abc.Container):
filter_function = lambda value: value in keys_to_remove
else:
assert isinstance(keys_to_remove, collections.abc.Callable)
filter_function = keys_to_remove
for key in list(d.keys()):
if filter_function(key):
del d[key]
def get_sorted_values(d, key=None):
'''
Get the values of dict `d` as a `tuple` sorted by their respective keys.
'''
kwargs = {'key': key,} if key is not None else {}
return get_tuple(d, sorted(d.keys(), **kwargs))
def reverse(d):
'''
Reverse a `dict`, creating a new `dict` where keys and values are switched.
Example:
>>> reverse({'one': 1, 'two': 2, 'three': 3})
{1: 'one', 2: 'two', 3: 'three'})
This function requires that:
1. The values will be distinct, i.e. no value will appear more than once.
2. All the values be hashable.
'''
new_d = {}
for key, value in d.items():
if value in new_d:
raise Exception(
f"Value {value} appeared twice! Once with a key of {key} and "
f"then again with a key of {new_d[value]}. This function is "
f"intended only for dicts with distinct values."
)
new_d[value] = key
return new_d