How to update multiple key and values in dictionary? - python-3.x

Input:
lst = {"scot":{"23", "poland"}, "martin":{"32", "england"},
"":{"23", "ireland"},None:{"23", "indian"}, "1":{"", ""}}
Output:
lst2 = {"scot":{"230", "poland"}, "martin":{"320", "england"},
"":{"230", "ireland"},None:{"230", "indian"}, "10":{"", ""}}
Code :
for k,v in lst.items():
if k is not None:
if k.isdigit():
k = k + "0"
print(k)
if v is not None and type(v) == set :
for i in v:
if i.isdigit():
i =i + "0"
print (i)
print(lst)
But The values are not getting updated and I am getting below output:
{'scot': {'23', 'poland'}, 'martin': {'england', '32'}, '': {'23', 'ireland'}, None: {'23', 'indian'}, '1': {''}}

You were on the right track, but you were not updating the actual values in your dictionary. More importantly, you shouldn't be changing the iterables (dicts, sets, lists) while iterating through them - in the sense that you shouldn't be adding or removing new elements that would normally be part of the iteration (like add or remove key-value pairs while iterating through a dictionary).
Because of that, a simple implementation of your program needs to have two parts - first where you search your dictionary lst and record the necessary changes, and second where you implement those recorded changes.
Below code does just that,
lst = {"scot":{"23", "poland"}, "martin":{"32", "england"}, "":{"23", "ireland"},None:{"23", "indian"}, "1":{"", ""}}
keys_to_delete = []
kv_pairs_to_add = {}
ks_pairs_to_swap = []
# Collect what needs to change
for k,v in lst.items():
if k is not None:
if k.isdigit():
# Save the old key for later deletion
keys_to_delete.append(k)
# Create and record the new entry
kv_pairs_to_add[k + "0"] = v
k += "0"
print(k)
# ------ Was this supposed to be v or k?
if v is not None and type(v) == set:
for i in v:
if i.isdigit():
# Record the key, old value,
# and new value as a sublist
ks_pairs_to_swap.append([k, i, i+"0"])
i += "0"
print (i)
# Implement the changes
# Dictionary keys
for key in keys_to_delete:
del lst[key]
lst.update(kv_pairs_to_add)
# Sets
for key, ov, nv in ks_pairs_to_swap:
lst[key].remove(ov)
lst[key].add(nv)
print(lst)
Note the comment with ------ - just checking if vthere is not a bug, it seems your keys can be None, so the check should maybe be for k and not v.

I never put the code into my IDE but looking at your code it does seem to be right but I noticed that you're not ACTUALLY updating your list, your only creating variable.
For example
for k,v in lst.items():
This creates two variables k,v and you use those variables in the for loop with if statements, in order to actually update it you'd have to figure out a way to remove the previous string and append the new updated variable k or v
In the if statement you're not actually appending and removing anything from the dictionary you are only adding something to k or v and printing the variable out.
I don't think I wrote this well, but I hope I gave you a possible clear understanding on why its not actually getting updated.

lst = {"scot": {"23", "poland"}, "martin": {"32", "england"}, "": {"23", "ireland"}, None: {"23", "indian"}, "1": {"", ""}}
result = {}
for k, v in lst.items():
if k is not None or str:
for i in v:
if i.isdigit():
i += "0"
else:
j = i
result[k] = {i, j}
print(result)
Output:
{'scot': {'poland', '230'}, 'martin': {'england', "320"}, '': {'230', 'ireland'}, None: {'230', 'indian'}, '1': {''}}

Related

Find the index of a tuple when iterating across the first element of the tuple

How can you identify the index of a tuple when looping through a list of tuples[0]?
List = [('SPAM', 1.0),('EGGS', 4.2),('SPAM', 4.4),('BACON', 9.0),('COFFEE', 2.3)]
for i,j in List:
if i == 'SPAM':
list.remove(???)
print(list)
I've tried to employ the Enumerate() function, but that didn't seem to give me the correct output either.
List.pop()
Doesn't pop the correct index all the time.
Can anyone help, please?
Enumeration does work:
l = [('SPAM', 1.0),('EGGS', 4.2),('SPAM', 4.4),('BACON', 9.0),('COFFEE', 2.3)]
for idx, (i,j) in enumerate(l):
if i == 'SPAM':
print("FOUND SPAN at index %d" % idx)
However, you shouldn't mutate the list while iterating over it, rather take a filtering approach like so:
l_new = [(i, j) for i, j in l if i != 'SPAM']
You can use del to delete the element at the given index:
List = [('SPAM', 1.0),('EGGS', 4.2),('SPAM', 4.4),('BACON', 9.0),('COFFEE', 2.3)]
for (i, (e1, e2)) in enumerate(List):
if e1 == 'SPAM':
del List[i]
print(List) # prints [('EGGS', 4.2), ('BACON', 9.0), ('COFFEE', 2.3)]

python3 value returned wrong with container variable

I meet a code that failed to meet my expectation. Details as below:
a = ['name']
b = [('name=cheng',),('name=huang',),('name=pan',)]
Dict = {}
c = []
for i in range(0,3):
for j in range(0,1):
Dict[a[j]] = b[i][j]
c.append(Dict)
print(c)
>>> [{'name':'name=pan'},{'name':'name=pan'},{'name':'name=pan'}]
what i expected should be
>>> [{'name':'name=cheng'},{'name':'name=huang'},{'name':'name=pan'}]
So could you please tell me how to solve the issue ?
You are changing the value of Dict in place and not creating a new dictionary every time. Each iteration of the loop, you are setting Dict["name"] equal to one of the elements in b and then appending it to the list. The next iteration of your loop changes dict in place (meaning the previous version you appending to c will also be changed). The result is that your list c is filled with 3 exact copies (exact same location in memory) of the dictionary created in the final iteration of the loop.
How do you fix this? Make a new dictionary every time.
a = ['name']
b = [('name=cheng',),('name=huang',),('name=pan',)]
c = []
for i in range(0,3):
for j in range(0,1):
temp_dict = {a[j]: b[i][j]}
c.append(temp_dict)
print(c)
Result:
[{'name': 'name=cheng'}, {'name': 'name=huang'}, {'name': 'name=pan'}]
You use the same value of Dict for all of the iterations of the loop. So all of the dictionaries are the same. You just have three copies of the same dictionary in the list.
If you move the Dict = {} statement into the loop, it will be fixed.
a = ['name']
b = [('name=cheng',),('name=huang',),('name=pan',)]
c = []
for i in range(0,3):
Dict = {}
for j in range(0,1):
Dict[a[j]] = b[i][j]
c.append(Dict)
print(c)
Or more Pythonic:
keys = ['name']
values_list = [('name=cheng',), ('name=huang',), ('name=pan',)]
result = []
for values in values_list:
result.append(dict(zip(keys, values)))
print(result)
This works by using the zip builtin which does the same thing as [(x[i], y[i]) for i in range(min(len(x), len(y))] without needing to keep track of the indices or lengths.
The dict class can build a dictionary from a list of tuples, which is what this solution uses.

Pyspark Runtime Error Dictionary Changed size during iteration [duplicate]

I have obj like this
{hello: 'world', "foo.0.bar": v1, "foo.0.name": v2, "foo.1.bar": v3}
It should be expand to
{ hello: 'world', foo: [{'bar': v1, 'name': v2}, {bar: v3}]}
I wrote code below, splite by '.', remove old key, append new key if contains '.', but it said RuntimeError: dictionary changed size during iteration
def expand(obj):
for k in obj.keys():
expandField(obj, k, v)
def expandField(obj, f, v):
parts = f.split('.')
if(len(parts) == 1):
return
del obj[f]
for i in xrange(0, len(parts) - 1):
f = parts[i]
currobj = obj.get(f)
if (currobj == None):
nextf = parts[i + 1]
currobj = obj[f] = re.match(r'\d+', nextf) and [] or {}
obj = currobj
obj[len(parts) - 1] = v
for k, v in obj.iteritems():
RuntimeError: dictionary changed size during iteration
Like the message says: you changed the number of entries in obj inside of expandField() while in the middle of looping over this entries in expand.
You might try instead creating a new dictionary of the form you wish, or somehow recording the changes you want to make, and then making them AFTER the loop is done.
You might want to copy your keys in a list and iterate over your dict using the latter, eg:
def expand(obj):
keys = list(obj.keys()) # freeze keys iterator into a list
for k in keys:
expandField(obj, k, v)
I let you analyse if the resulting behavior suits your expected results.
Edited as per comments, thank you !
I had a similar issue with wanting to change the dictionary's structure (remove/add) dicts within other dicts.
For my situation I created a deepcopy of the dict. With a deepcopy of my dict, I was able to iterate through and remove keys as needed.Deepcopy - PythonDoc
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
Hope this helps!
For those experiencing
RuntimeError: dictionary changed size during iteration
also make sure you're not iterating through a defaultdict when trying to access a non-existent key! I caught myself doing that inside the for loop, which caused the defaultdict to create a default value for this key, causing the aforementioned error.
The solution is to convert your defaultdict to dict before looping through it, i.e.
d = defaultdict(int)
d_new = dict(d)
or make sure you're not adding/removing any keys while iterating through it.
Rewriting this part
def expand(obj):
for k in obj.keys():
expandField(obj, k, v)
to the following
def expand(obj):
keys = obj.keys()
for k in keys:
if k in obj:
expandField(obj, k, v)
shall make it work.

Search for a key for a given value in a dictionary

I have this dictionary below
metric_app_mapper = \
{"appdynamics":
{
"avgresptime": ["average response time", "avgresptime", "avgrest", "art"],
"callspermin": ["calls per minute", "callspermin", "calls/min"],
"allapps": ["all apps", "all apps info", "all applications"]
},
"another_software":
{
"metric": ["synonyms"]
}
}
I am trying to get the key for the value "art". But I am only able to search for the value "art" or the list it is in. Here is my code below
search_key = [j for i in metric_app_mapper.values() for j in i.values() for k in j if k == "art"]
print("Key found", search_key)
I get the list for the given value
Key found [['average response time', 'avgresptime', 'avgrest', 'art']]
How do I get the associated key in this case "avgresptime" for the given list I found for the value and in turn it's given key which is "Appdynamics"? Is there any better way to do it since my approach involves O(n^3) runnning time?
Here's a recursive solution that yields key "paths". The (*keys, k) syntax is available in Python versions >= 3.5, you can also use keys + (k,)
from collections.abc import Collection
def find(dictionary, value, keys=()):
for k, v in dictionary.items():
path_to = (*keys, k)
if v == value:
yield path_to
elif isinstance(v, dict):
yield from find(v, value, path_to)
elif isinstance(v, Collection): # might make more sense to use Container, I'm not sure
if value in v:
yield path_to
print(list(find(metric_app_mapper, 'art')))
# [('appdynamics', 'avgresptime')]
As a little modifcation to #Jon Clements code, this is what gives me what I need.
interpreted_tool = [[k1, k2] for k1, v1 in metric_app_mapper.items() for k2, v2 in v1.items() for v3 in v2 if v3 == metric]
Try this:-
for i,j in metric_app_mapper.items():
for k,l in j.items():
if "art" in l:
print(k,i)

How to get keys from nested dictionary of arbitrary length in Python

I have a dictionary object in python. Let's call it as dict. This object could contain another dictionary which may in turn contain another dictionary and so on.
dict = { 'k': v, 'k1': v1, 'dict2':{'k3': v3, 'k4':v4} , 'dict3':{'k5':v5, dict4:{'k6':v6}}}
This is just an example. Length of outermost dictionary could be anything. I want to extract keys from such dictionary object in following two ways :
get list of only keys.
[k,k1,k2,k3,k4,k5,k6]
get list of keys and its parent associated dictionary so something like this :
outer_dict_keys = [k ,dict2, dict3]
dict2_keys = [k3,k4]
dict3_keys = [k5, dict4]
dict4_keys = [k6]
Outermost dictionary dict length is always changing so I can not hard code anything.
What is best way to achieve above result ?
Use a mix of iteration and tail recursion. After quoting undefined names, making spacing uniform, and removing 'k2' from the first result, I came up with the code below. (Written and tested for 3.4, it should run on any 3.x and might on 2.7.) A key thing to remember is that the iteration order of dicts is essentially random, and varies with each run. Recursion as done here visit sub-dicts in depth-first rather than breadth-first order. For dict0, both are the same, But if dict4 were nested in dict2 rather than dict3, they would not be.
dict0 = {'k0': 0, 'k1': 1, 'dict2':{'k3': 3, 'k4': 4},
'dict3':{'k5': 5, 'dict4':{'k6': 6}}}
def keys(dic, klist=[]):
subdics = []
for key in sorted(dic):
val = dic[key]
if isinstance(val, dict):
subdics.append(val)
else:
klist.append(key)
for subdict in subdics:
keys(subdict, klist)
return klist
result = keys(dict0)
print(result, '\n', result == ['k0','k1','k3','k4','k5','k6'])
def keylines(dic, name='outer_dict', lines=[]):
vals = []
subdics = []
for key in sorted(dic):
val = dic[key]
if isinstance(val, dict):
subdics.append((key,val))
else:
vals.append(key)
vals.extend(pair[0] for pair in subdics)
lines.append('{}_keys = {}'.format(name, vals))
for subdict in subdics:
keylines(subdict[1], subdict[0], lines)
return lines
result = keylines(dict0)
for line in result:
print(line,)
print()
expect = [
"outer_dict_keys = ['k0', 'k1', 'dict2', 'dict3']",
"dict2_keys = ['k3', 'k4']",
"dict3_keys = ['k5', 'dict4']",
"dict4_keys = ['k6']"]
for actual, want in zip(result, expect):
if actual != want:
print(want)
for i, (c1, c2) in enumerate(zip(actual, want)):
if c1 != c2:
print(i, c1, c2)

Resources