My goal is to convert a class to a dict, recursively. I understand that I need to specify the rules related to the conversion, but I feel like there is some way for built-in python to recurse the class and produce a dict. See the following code:
class Foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
def __iter__(self):
yield 'a', self.a
yield 'b', self.b
yield 'c', self.c
class Bar:
def __init__(self, foos):
self.foos = foos
self.d = 5
def __iter__(self):
yield 'foos', self.foos
yield 'd', self.d
foos = [Foo(), Foo(), Foo()]
bar = Bar(foos)
print(dict(bar))
The result of this is:
{'foos': [<__main__.Foo object at 0x108e6be48>, <__main__.Foo object at 0x108e6bef0>, <__main__.Foo object at 0x108e6bf28>], 'd': 5}
foos in this case is a list of Foo()s, which I would hope to see an expanded list of dicts. I know I can add the following to the Foo class to make this work:
def __repr__(self):
return repr(dict(self))
Results in the desired output:
{'foos': [{'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'b': 2, 'c': 3}], 'd': 5}
but that approach seems hacky - as in, I'm forcing it to represent itself as a dict, rather than naturally iterable (which feels like I'm trying to put a square peg in a round hole).
My question to the python world is: what is the proper way to approach this so that I can call dict() on my class and get a full dictionary?
I've searched around and can't seem to find anything specific to this topic so I'm going to ask here. If this already exists please point me in the right direction. Thanks in advance for your help!
I've came up with two hacks that can fill your needs.
1st: Exposing dicts in foos variable by replacing this line:
foos = [Foo(), Foo(), Foo()]
by this kind of hacks using list comprehension:
foos =[dict(k) for k in [Foo(), Foo(), Foo()]]
Or, simply like this:
foos = [dict(Foo()), dict(Foo()), dict(Foo())]
then:
print(dict(bar))
>>> {'d': 5, 'foos': [{'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}]}
2nd: Exposing dicts inside Bar.__iter__() by replacing this line:
yield 'foos', self.foos
by:
yield [dict(k) for k in self.foos]
Then:
print(dict(bar))
>>> {'d': 5, 'foos': [{'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}]}
Bonus (not exactly what you want): Using dict comprehension by replacing this line:
print(dict(bar))
by:
print({k:v if isinstance(v, int) else [dict(j) for j in v] for k,v in dict(bar).items()})
which will print:
>>> {'d': 5, 'foos': [{'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}, {'a': 1, 'c': 3, 'b': 2}]}
Related
I am given a dictionary of values and a dictionary of lists
dict_v = {'A': 2, 'B': 3, 'C': 4}
&
dict_l = {'A': [1,3,4], 'B': [8,5,2], 'C': [4,6,2]}
I am trying to append the values of dict_v to the values of dict_l.
So I want it to look like this:
{'A': [1,3,4,2], 'B': [8,5,2,3], 'C': [4,6,2,4]}
Ive tried the following code:
for key in dict_l:
if key in dict_v:
dict_v[key]=dict_l[key]+dict_v[key]
else:
dict_l[key]=stock_prices[key]
print(dict_v)
it's giving me an error message
You can append to a list in this sense like this
dict_v = {'A': 2, 'B': 3, 'C': 4}
dict_l = {'A': [1,3,4], 'B': [8,5,2], 'C': [4,6,2]}
for key in dict_l:
if key in dict_v:
dict_l[key] += [dict_v[key]]
print(dict_l)
The change is that you are appending the value dict_v[key] as a list [dict_v[key]] and appending it to the entries that are already in dict_l[key] using +=. This way you can also append multiple values as a list to an already existing list.
because you are adding to a list you should user the append() method to add to list
dict_v = {'A': 2, 'B': 3, 'C': 4}
dict_l = {'A': [1,3,4], 'B': [8,5,2], 'C': [4,6,2]}
for key in dict_v.keys():
dict_l[key].append(dict_v[key])
print(dict_l)
Here is my answer:
dict_v = {'A': 2, 'B': 3, 'C': 4}
dict_l = {'A': [1,3,4], 'B': [8,5,2], 'C': [4,6,2]}
for key in dict_v:
if key in dict_l:
temp = dict_l[key]
temp.append(dict_v[key])
dict_l[key] = temp
else:
dict_l[key] = stock_prices[key]
print(dict_l)
So instead of for key in dict_l, I did for key in dict_v just for the sake of logic. I also appended key value from dict_v to dict_l, but in your code you appended the key value from dict_l to dict_v, and I changed that. Hope it helps!
What would be the pythonic way to remove elements that are not uniques for certain keys?
Let's say one has a list of dicts such as:
[
{'a': 1, 'b': 'j'},
{'a': 2, 'b': 'j'},
{'a': 3, 'b': 'i'}
]
The expected output would remove the second element, because the key b equals to j in more than one element. Thus:
[
{'a': 1, 'b': 'j'},
{'a': 3, 'b': 'i'}
]
This is what I have tried:
input = [
{'a': 1, 'b': 'j'},
{'a': 2, 'b': 'j'},
{'a': 3, 'b': 'i'}
]
output = []
for input_element in input:
if not output:
output.append(input_element)
else:
for output_element in output:
if input_element['b'] != output_element['b']:
output.append(input_element)
Would the solution be simpler if that'd be a list of tuples, such as:
[(1, 'j'), (2, 'j'), (3, 'i')]
# to produce
[(1, 'j'), (3, 'i')]
Here is an approach using any() and list-comprehension:
Code:
l=[
{'a': 1, 'b': 'j'},
{'a': 2, 'b': 'j'},
{'a': 3, 'b': 'i'}
]
new_l = []
for d in l:
if any([d['b'] == x['b'] for x in new_l]):
continue
new_l.append(d)
print(new_l)
Output:
[{'a': 1, 'b': 'j'}, {'a': 3, 'b': 'i'}]
You could define a custom container class which implements the __eq__ and __hash__ magic methods. That way, you can use a set to remove "duplicates" (according to your criteria). This doesn't necessarily preserve order.
from itertools import starmap
from typing import NamedTuple
class MyTuple(NamedTuple):
a: int
b: str
def __eq__(self, other):
return self.b == other.b
def __hash__(self):
return ord(self.b)
print(set(starmap(MyTuple, [(1, 'j'), (2, 'j'), (3, 'i')])))
Output:
{MyTuple(a=3, b='i'), MyTuple(a=1, b='j')}
>>>
I suggest this implementation:
_missing = object()
def dedupe(iterable, selector=_missing):
"De-duplicate a sequence based on a selector"
keys = set()
if selector is _missing: selector = lambda e: e
for e in iterable:
if selector(e) in keys: continue
keys.add(selector(e))
yield e
Advantages:
Returns a generator:
It iterates the original collection just once, lazily. That could be useful
and/or performatic in some scenarios, specially if you will chain
additional query operations.
input = [{'a': 1, 'b': 'j'}, {'a': 2, 'b': 'j'}, {'a': 3, 'b': 'i'}]
s = dedupe(input, lambda x: x['b'])
s = map(lambda e: e['a'], s)
sum(s) # Only now the list is iterated. Result: 4
Accepts any kind of iterable:
Be it a list, set, dictionary or a custom iterable class. You can construct whatever collection type out of it, without iterating multiple times.
d = {'a': 1, 'b': 1, 'c': 2}
{k: v for k, v in dedupe(d.items(), lambda e: e[1])}
# Result (dict): {'a': 1, 'c': 2}
{*dedupe(d.items(), lambda e: e[1])}
# Result (set of tuples): {('a', 1), ('c', 2)}
Takes an optional selector function (or any callable):
This gives you flexibility to re-use this function in many different contexts, with any custom logic or types. If the selector is absent, it compares the whole elements.
# de-duping based on absolute value:
(*dedupe([-3, -2, -2, -1, 0, 1, 1, 2, 3, 3], abs),)
# Result: (-3, -2, -1, 0)
# de-duping without selector:
(*dedupe([-3, -2, -2, -1, 0, 1, 1, 2, 3, 3]),)
# Result: (-3, -2, -1, 0, 1, 2, 3)
def drop_dup_key(src, key):
''' src is the source list, and key is a function to obtain the key'''
keyset, result = set(), []
for elem in src:
keyval = key(elem)
if keyval not in keyset:
result.append(elem)
keyset.add(keyval)
return result
Use it like this:
drop_dup_key(in_list, lambda d: return d.get('b'))
The comparison of tuples to dictionaries isn't quite accurate since the tuples only contain the dictionary values, not the keys, and I believe you are asking about duplicate key:value pairs.
Here is a solution which I believe solves your problem, but might not be as pythonic as possible.
seen = set()
kept = []
for d in x:
keep = True
for k, v in d.items():
if (k, v) in seen:
keep = False
break
seen.add((k, v))
if keep:
kept.append(d)
print(kept)
Output:
[{'a': 1, 'b': 'j'}, {'a': 3, 'b': 'i'}]
Is it possible to make a function that will return a nested dict depending on the arguments?
def foo(key):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
return d[key]
foo(['c']['d'])
I waiting for:
3
I'm getting:
TypeError: list indices must be integers or slices, not str
I understanding that it possible to return a whole dict, or hard code it to return a particular part of dict, like
if 'c' and 'd' in kwargs:
return d['c']['d']
elif 'c' and 'e' in kwargs:
return d['c']['e']
but it will be very inflexible
When you give ['c']['d'], you slice the list ['c'] using the letter d, which isin't possible. So what you can do is, correct the slicing:
foo('c')['d']
Or you could alter your function to slice it:
def foo(*args):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
d_old = dict(d) # if case you have to store the dict for other operations in the fucntion
for i in args:
d = d[i]
return d
>>> foo('c','d')
3
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
def funt(keys):
val = d
for key in keys:
if val:
val = val.get(key)
return val
funt(['c', 'd'])
Additionally to handle key not present state.
One possible solution would be to iterate over multiple keys -
def foo(keys, d=None):
if d is None:
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
if len(keys) == 1:
return d[keys[0]]
return foo(keys[1:], d[keys[0]])
foo(['c', 'd'])
I want to write a code which takes the following inputs:
list (list of maps)
request_keys (list of strings)
operation (add,substract,multiply,concat)
The code would look at the list for the maps having the same value for all keys except the keys given in request_keys. Upon finding two maps for which the value in the search keys match, the code would do the operation (add,multiple,substract,concat) on the two maps and combine them into one map. This combination map would basically replace the other two maps.
i have written the following peice of code to do this. The code only does add operation. It can be extended to make the other operations
In [83]: list
Out[83]:
[{'a': 2, 'b': 3, 'c': 10},
{'a': 2, 'b': 3, 'c': 3},
{'a': 2, 'b': 4, 'c': 4},
{'a': 2, 'b': 3, 'c': 2},
{'a': 2, 'b': 3, 'c': 3}]
In [84]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:def func(list,request_keys):
: new_list = []
: found_indexes = []
: for i in range(0,len(list)):
: new_item = list[i]
: if i in found_indexes:
: continue
: for j in range(0,len(list)):
: if i != j and {k: v for k,v in list[i].iteritems() if k not in request_keys} == {k: v for k,v in list[j].iteritems() if k not in request_keys}:
: found_indexes.append(j)
: for request_key in request_keys:
: new_item[request_key] += list[j][request_key]
: new_list.append(new_item)
: return new_list
:--
In [85]: func(list,['c'])
Out[85]: [{'a': 2, 'b': 3, 'c': 18}, {'a': 2, 'b': 4, 'c': 4}]
In [86]:
What i want to know is, is there a faster, more memory efficient, cleaner and a more pythonic way of doing the same?
Thank you
You manually generate all the combinations and then compare each of those combinations. This is pretty wasteful. Instead, I suggest grouping the dictionaries in another dictionary by their matching keys, then adding the "same" dictionaries. Also, you forgot the operator parameter.
import collections, operator, functools
def func(lst, request_keys, op=operator.add):
matching_dicts = collections.defaultdict(list)
for d in lst:
key = tuple(sorted(((k, d[k]) for k in d if k not in request_keys)))
matching_dicts[key].append(d)
for group in matching_dicts.values():
merged = dict(group[0])
merged.update({key: functools.reduce(op, (g[key] for g in group))
for key in request_keys})
yield merged
What this does: First, it creates a dictionary, mapping the key-value pairs that have to be equal for two dictionaries to match to all those dictionaries that have those key-value pairs. Then it iterates the dicts from those groups, using one of that group as a prototype and updating it with the sum (or product, or whatever, depending on the operator) of the all the dicts in that group for the required_keys.
Note that this returns a generator. If you want a list, just call it like list(func(...)), or accumulate the merged dicts in a list and return that list.
from itertools import groupby
from operator import itemgetter
def mergeDic(inputData, request_keys):
keys = inputData[0].keys()
comparedKeys = [item for item in keys if item not in request_keys]
grouper = itemgetter(*comparedKeys)
result = []
for key, grp in groupby(sorted(inputData, key = grouper), grouper):
temp_dict = dict(zip(comparedKeys, key))
for request_key in request_keys:
temp_dict[request_key] = sum(item[request_key] for item in grp)
result.append(temp_dict)
return result
inputData = [{'a': 2, 'b': 3, 'c': 10},
{'a': 2, 'b': 3, 'c': 3},
{'a': 2, 'b': 4, 'c': 4},
{'a': 2, 'b': 3, 'c': 2},
{'a': 2, 'b': 3, 'c': 3}]
from pprint import pprint
pprint(mergeDic(inputData,['c']))
I am new to python and i am stuck while making a dictionary.. please help :)
This is what I am starting with :
dict = {}
dict['a']={'ra':7, 'dec':8}
dict['b']={'ra':3, 'dec':5}
Everything perfect till now. I get :
In [93]: dict
Out[93]: {'a': {'dec':8 , 'ra': 7}, 'b': {'dec': 5, 'ra': 3}}
But now, if I want to add more things to key 'a' and i do :
dict['a']={'dist':12}
Then it erases the previous information of 'a' and what i get now is :
In [93]: dict
Out[93]: {'a': {'dist':12}, 'b': {'dec': 5, 'ra': 3}}
What i get want to have is :
In [93]: dict
Out[93]: {'a': {'dec':8 , 'ra': 7, 'dist':12}, 'b': {'dec': 5, 'ra': 3}}
Can someone please help??
>>> d = {}
>>> d['a'] = {'ra':7, 'dec':8}
>>> d['b'] = {'ra':3, 'dec':5}
>>> d['a']['dist'] = 12
>>> d
{'a': {'dec': 8, 'dist': 12, 'ra': 7}, 'b': {'dec': 5, 'ra': 3}}
If you want to update dictionary from another dictionary, use update():
Update the dictionary with the key/value pairs from other, overwriting
existing keys.
>>> d = {}
>>> d['a'] = {'ra':7, 'dec':8}
>>> d['b'] = {'ra':3, 'dec':5}
>>> d['a'].update({'dist': 12})
>>> d
{'a': {'dec': 8, 'dist': 12, 'ra': 7}, 'b': {'dec': 5, 'ra': 3}}
Also, don't use dict as a variable name - it shadows built-in dict type. See what can possibly happen:
>>> dict(one=1)
{'one': 1}
>>> dict = {}
>>> dict(one=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'dict' object is not callable
Do this:
dict['a']['dist'] = 12
Try this:
dict['a'].update( {'dist': 12} )
Instead of assigning {'dist':12} to dict['a'], use the update method.
dict['a'].update( {'dist':12} )
This has the advantage of not needing to "break apart" the new dictionary to find which key(s) to insert into the target. Consider:
a = build_some_dictionary()
for k in a:
dict['a'] = a[k]
vs.
dict['a'].update(a)