Reusing an expression in a comprehension - python

Is it possible to reuse a subexpression that might be large, or expensive to compute, or non-idempotent like a generator, in a comprehension?
Say there is a list of strings to be to converted to a dict:
items = ['ab: 1', 'cd: 2', 'ef:3'] ===> {'ab': '1', 'cd': '2', 'ef': '3'}
A loop computes the "expensive" split expression once:
d = {}
for item in items:
k, v = item.split(':', maxsplit=1)
d.update({k.strip(): v.strip()})
A comprehension repeats the computation for each output element:
d = {x.split(':', maxsplit=1)[0].strip(): x.split(':', maxsplit=1)[1].strip() for x in items}
But the desired solution is a comprehension that computes the expression once and then reuses it:
d = {k.strip(): v.strip() for x in items for k, v in x.split(':', maxsplit=1)} # Wrong - need an assignment
Can it be done?

If you don't need a dictionary comprehension, the dict object already takes in tuples:
dict(map(str.strip, x.split(':')) for x in items)
# {'ab': '1', 'cd': '2', 'ef': '3'}
And this would be the dict comprehension if you want:
{k: v for k, v in [map(str.strip, x.split(':')) for x in items]}
# {'ab': '1', 'cd': '2', 'ef': '3'}

a = ['ab: 1', 'cd: 2', 'ef:3']
Using generator expression with map and a dictionary comprehension.
In [46]: b = (map(str.strip,thing.split(':')) for thing in a)
In [47]: d = {k:v for (k,v) in b}
In [48]: d
Out[48]: {'ab': '1', 'cd': '2', 'ef': '3'}
Each item in a is split once and the items in the resultant list are stripped once. Execution of those functions is delayed till the dictionary comprehension is executed.

Related

Convert string to dictionary with list of values

What is the best way to convert a string to dictionary with value of dictionary as a list
for example
str = "abc=1,abc=2,abc=3,xyz=5,xyz=6"
i need the output as:
d = {"abc":["1","2","3"],"xyz":["5","6"]}
I'm very new to python.
my code:
d = {k: [v] for k, v in map(lambda item: item.split('='), s.split(","))}
Here is the solution with dict.setdefault method.
>>> help({}.setdefault)
Help on built-in function setdefault:
setdefault(key, default=None, /) method of builtins.dict instance
Insert key with a value of default if key is not in the dictionary.
Return the value for key if key is in the dictionary, else default.
>>> your_str = "abc=1,abc=2,abc=3,xyz=5,xyz=6"
>>>
>>> result = {}
>>>
>>> for pair in your_str.split(","):
... name, val = pair.split("=")
... result.setdefault(name, []).append(val)
>>> result
{'abc': ['1', '2', '3'], 'xyz': ['5', '6']}
You could also use defaultdict with default factory as list
>>> from collections import defaultdict
>>>
>>> your_str = "abc=1,abc=2,abc=3,xyz=5,xyz=6"
>>>
>>> result = defaultdict(list)
>>> for pair in str.split(","):
... name, val = pair.split("=")
... result[name].append(val)
...
>>> dict(result)
{'abc': ['1', '2', '3'], 'xyz': ['5', '6']}
The reason the code you have tried already isn't giving you the desired result is the fact that you are overwriting the value assigned to each key as you iterate over the list. What you need to do is append to the value already assigned to the key - except if the key doesn't exist, in which case you need to initialise that key.
This would be one way to go:
s1 = "abc=1,abc=2,abc=3,xyz=5,xyz=6"
list1 = [(each.split('=')) for each in s1.split(',')]
d = {}
for key, val in list1:
if key in d.keys():
d[key].append(val)
else:
d[key] = [val]
print (d)
#result: {'abc': ['1', '2', '3'], 'xyz': ['5', '6']}
You could simplify this and eliminate the if-else by using defaultdict, like so:
from collections import defaultdict
d = defaultdict(lambda: [])
s1 = "abc=1,abc=2,abc=3,xyz=5,xyz=6"
list1 = [(each.split('=')) for each in s1.split(',')]
for key, val in list1:
d[key].append(val)
print (d)
#result: {'abc': ['1', '2', '3'], 'xyz': ['5', '6']}
# initialize a dictionary
d = {}
# split the string (my_str) according to "," in order to get pairs such as 'abc/1' and 'xyz/5' in a list
for elt in my_str.split(",") :
# for each string of the list, split according to '/' to get the pairs ['abc', 1]
# complete the dictionary
if elt.split('/')[0] not in d.keys():
d[elt.split('/')[0]] = [elt.split('/')[1]]
else :
d[elt.split('/')[0]].append(elt.split('/')[1])

How to add index to a list inside a dictionary

I have a dictionary here:
dict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
What is the necessary process to get the following result?
dict = {'A':['1_01','1_02','1_03','1_04','1_05'], 'B':['2_01','2_02'], 'C':['3_01','3_02','3_03','3_04']}
I have been learning python for quite a while now but dictionary is kind of new to me.
As others mentioned, refrain from using built-in keywords as variable names, such as dict. I kept it for simplicity on your part.
This is probably the most pythonic way of doing it (one line of code):
dict = {key:[x+"_0"+str(cnt+1) for cnt,x in enumerate(value)] for key,value in dict.items()}
You could also iterate through each dictionary item, and then each list item and manually change the list names as shown below:
for key,value in dict.items():
for cnt,x in enumerate(value):
dict[key][cnt] = x+"_0"+str(cnt+1)
Also, as some others have mentioned, if you want numbers greater than 10 to save as 1_10 rather than 1_010 you can you an if/else statement inside of the list comprehension...
dict = {key:[x+"_0"+str(cnt+1) if cnt+1 < 10 else x+"_"+str(cnt+1) for cnt,x in enumerate(value)] for key,value in dict.items()}
First iterate on keys.
Then loop on keys you are getting on key like for 'A' value is ['1','1','1','1','1'] then we can change the element at ['1','1','1','1','1']
enumerate() helps you iterate on index,value then index starts with zero as per your expected output add 1 to index. As you want the trailing 0 before each count we did '%02d'% (index+1)
Like this:
dict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
for i in dict.keys(): #iterate on keys
for index,val in enumerate(dict[i]): #took value as we have key in i
element='%02d'% (index+1) #add trailing 0 we converted 1 to int 01
dict[i][index]=val+"_"+ str(element) #assign new value with converting integer to string
print(dict)
Output:
{'A': ['1_01', '1_02', '1_03', '1_04', '1_05'], 'C': ['3_01', '3_02', '3_03', '3_04'], 'B': ['2_01', '2_02']}
Use enumerate to iterate over list keeping track of index:
d = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
newd = {}
for k, v in d.items():
newd[k] = [f'{x}_0{i}' for i, x in enumerate(v, 1)]
print(newd)
Also a dictionary-comprehension:
d = {k: [f'{x}_0{i}' for i, x in enumerate(v, 1)] for k, v in d.items()}
Note: Don't name your dictionary as dict because it shadows the built-in.
d= {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
{x:[j + '_'+ '{:02}'.format(i+1) for i,j in enumerate(y)] for x,y in d.items()}
adict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3'], 'D': '23454'}
newdict = {}
for i,v in adict.items():
if isinstance(v, list):
count = 0
for e in v:
count += 1
e += '_0' + str(count)
newdict[i] = newdict.get(i, [e]) + [e]
else:
newdict[i] = newdict.get(i, v)
print (newdict)
#{'A': ['1_01', '1_01', '1_02', '1_03', '1_04', '1_05'], 'B': ['2_01', '2_01', '2_02'], 'C': ['3_01', '3_01', '3_02', '3_03', '3_04'], 'D': '23454'}
This solution will check for whether your value in the dictionary is a list before assigning an index to it
You can use a dictcomp:
from itertools import starmap
d = {
'A': ['1', '1', '1', '1', '1'],
'B': ['2', '2'],
'C': ['3', '3', '3', '3']
}
f = lambda x, y: '%s_%02d' % (y, x)
print({k: list(starmap(f, enumerate(v, 1))) for k, v in d.items()})
# {'A': ['1_01', '1_02', '1_03', '1_04', '1_05'], 'B': ['2_01', '2_02'], 'C': ['3_01', '3_02', '3_03', '3_04']}

Python pop dictionary list values

I have a dictionary:
{'dict': [['IE', '5', '-5'], ['UK', '3', '-9']]}
I wish to pop the list values that are outside of the UK, therefore taking the first value within the lists and comparing to see if it is equal to 'UK'.
I currently have:
for k,v in insideUK.items():
for i in v:
if i[0] == "UK":
print(x)
else:
k.pop(v)
I know after the else is wrong but need help!
I wish for the dict to look like this once finished popping values that aren't equal to "UK".
{'dict': [['UK', '3', '-9']]}
You can use a list comprehension to filter out based on the first element
>>> data = {'dict': [['IE', '5', '-5'], ['UK', '3', '-9']]}
>>> {'dict': [i for i in data['dict'] if i[0] == 'UK']}
{'dict': [['UK', '3', '-9']]}
You can also do it using the filter function:
d = {'dict': list(filter(lambda i: 'UK' in i, d['dict']))}
print(d)
Output:
{'dict': [['UK', '3', '-9']]}
A nested dict and list expression can do this:
{k: [i for i in v if i[0] == 'UK'] for k,v in insideUK.items()}
If you really want to do it with a for-loop and change the list in-place, you could do something like this:
for k,v in insideUK.items():
for i in v:
if i[0] == "UK":
print(x)
else:
v.remove(i)
But it is discouraged strongly to change the list you are iterating over during the iteration

python print single instance of dictionary key value pairs after for loop

I'm using python 2.7 to loop through two dictionaries to swap key:value pairs between them. I am using a final for loop to recover average costs for a key that has an 'AND' operator.
list1 = {'dish1': ['apples', 'AND', 'pears'], 'dish2': ['oranges', 'AND', 'cherries'], 'dish3': ['carrots', 'OR', 'tomatoes']}
list2 = {'apples': '10', 'pears': '2', 'oranges': '5', 'cherries': '6', 'carrots': '5', 'tomatoes': '4'}
AND_List = {k:v for k, v in list1.items() if 'AND' in v}
DictList = {}
avgDict = {}
for k, v in AND_List.items():
for j, i in list2.items():
if j in v:
DictList.setdefault(k, []).append(float(i))
for k, i in DictList.iteritems():
avgDict[k] = sum(i)/float(len(i))
print avgDict
>>{'dish1': 6.0}
>>{'dish1': 6.0, 'dish2': 5.5}
I'd like the print statement after the last for loop to only print one instance of dish1 and dish2. It seems to be sequentially printing each conditionally recovered key:value. I'd appreciate any advice. Thanks!
I think your indents are incorrect. See if this is what you are looking for:
for k, v in AND_List.items():
for j, i in list2.items():
if j in v:
DictList.setdefault(k, []).append(float(i))
for k, i in DictList.iteritems():
avgDict[k] = sum(i)/float(len(i))
print avgDict

Selecting K largest values in a dictionary of dictionary in python

I have a dictionary of dictionary as below:
ls = [{'0': {'1': '1','2': '0.5','3': '1'},'1': {'0': '0.2','2': '1','3': '0.8'},}]
I would like to select k-largest values with their keys for each key of dictionary (ls). I have written below commands. It just gives me the k-largest keys without their values.
Python Code:
import heapq
k=2
for dic in ls:
for key in dic:
print(heapq.nlargest(k, dic[key], key=dic[key].get))
Output
['2', '3']
['3', '1']
I need to have value of each selected key.
First of all, I just wanted to check why you have
ls = [{'0': {'1': '1','2': '0.5','3': '1'},'1': {'0': '0.2','2': '1','3': '0.8'},}]
This is a list containing a dict, which doesn't match your description in the question.
Here is a solution that uses dict comprehensions that should give you what you want :)
def get_n_largest_vals(n, d):
x = {key: heapq.nlargest(len, map(int, d[key])) for key in d}
return {key: map(str, x[key]) for key in x}
Here it is being used for your problem:
ls = [{'0': {'1': '1','2': '0.5','3': '1'},'1': {'0': '0.2','2': '1','3': '0.8'},}]
d = ls[0]
get_n_largest_vals(2, d)
>>> {'0': ['3', '2'], '1': ['3', '2']}
How about:
from operator import itemgetter
for d in ls:
for key, d2 in d.items():
print(dict(heapq.nlargest(k, d2.items(), key=itemgetter(1))))
Notice that your values are still strings so they'd be lexically ordered, which is not what you want, because '2' > 12' And the dictionary is not ordered!

Categories