Reputation: 457
I have a nested dictionary containing (sub)dicts for "density" and "temperature". This is the structure:
profiles_dict =
{
"density": {
"elements": [
{
"layers": [
{
"top": 54.0,
"bottom": 3.75,
"value": 218.9
}
]
}
],
"type": "density"
},
"temperature": {
"elements": [
{
"layers": []
}
],
"type": "temperature"
}
}
As one can see in the temperature subdict, the "layers" key contains an empty list as value. My goal is to create a loop/function that removes the dictionary {"layers": []}
.
The result should look like this:
profiles_dict_new =
{
"density": {
"elements": [
{
"layers": [
{
"top": 54.0,
"bottom": 3.75,
"value": 218.9
}
]
}
],
"type": "density"
},
"temperature": {
"elements": [
],
"type": "temperature"
}
}
I have tried the following:
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
for i in v2:
for k3, v3 in i.items():
if v3 == []:
del(i)
With this loop, I can access {'layers': []}
, however I don't know how to properly remove it form the dictionray.
The second approach is based on Ajax1234 solution (Deleting Items based on Keys in nested Dictionaries in python)
profiles_dict_new = {d:{e:[{l:v for l, v in e.items() if v != []}]} for d, e in profiles_dict.items()}
print(profiles_dict_new)
This gives me the following error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-126f19ab7afd> in <module>
----> 1 profiles_dict_new = {d:{e:[{l:v for l, v in e.items() if v != []}]} for d, e in profiles_dict.items()}
2 print(profiles_dict_new)
<ipython-input-11-126f19ab7afd> in <dictcomp>(.0)
----> 1 profiles_dict_new = {d:{e:[{l:v for l, v in e.items() if v != []}]} for d, e in profiles_dict.items()}
2 print(profiles_dict_new)
TypeError: unhashable type: 'dict'
Help is appreciated for either of the presented approaches.
Upvotes: 2
Views: 1047
Reputation: 114420
del
does not delete objects: it deletes names. If multiple names refer to the same object, the other names persist.
Unbinding i
, the loop variable, does nothing to the underlying list, and i
just gets reassigned on the next iteration. You have to delete the actual list element v2[index_of_i]
to get it removed from the list.
To be able to do that safely, you need to replace the original list or iterate over a copy of the list backwards. You can iterate forwards too, but then the index requires extra care to ensure that it accounts for preceding deleted elements.
Secondly, the loop over i.items()
is counter-productive. You don't want to find a dictionary that has some key with an empty list in it. You want a dictionary that has exactly one key, so there's no need for a loop.
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
v1[k2] = [i for i in v2 if len(i) > 1 or next(i.values())]
Iterating backwards will allow you to keep the original list object:
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
n = len(v2) - 1 # capture before modifying
for i, e in enumerate(reversed(v2)):
if len(e) == 1 and not next(iter(e.values()))
del v2[n - i]
If you iterate forward, you'll have to make a copy of the list to do it properly, and do extra bookkeeping for the index:
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
count = 0
for i, e in enumerate(v2[:]):
if len(e) == 1 and not next(iter(e.values()))
del v2[i - count]
count += 1
The expression next(iter(...))
is a common idiom for getting a single element from something that has one element but can't be easily indexed, like a dict
or set
. Methods like pop
are destructive: if you just wanted to check the element, you have to put it back. next(iter(...))
lets you peek into the collection.
Upvotes: 3
Reputation: 55
Many text but what you need is deleting a dict
from a list
?!
Your list is not i
(element of list) it's v2
so make sure to give your vars good names and change the for
to a while
cause deleting during iteration can cause problems.
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
i = 0
while i < len(v2):
for k3, v3 in v2[i].items():
if v3 == []:
v2.pop(i) #remove k3 if v3 empty from list v2 at index i
i -= 1 #decrease index i
break #cause k3, v3 in v2[i].items() does not exist anymore
i += 1
If an item dict
in list
v2
contains more than one k3
which can be NOT empty and you do NOT want to REMOVE it TOO you can store the empty ones for later in a new list
remove_keys
.
If remove_keys
contains the same amout as the whole dict
you can .pop(index)
it from v2
else .pop(key)
all stored keys only from the current dict
v2[i]
.
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
i = 0
while i < len(v2):
remove_keys = [] #store k3 when v3 == []
for k3, v3 in v2[i].items():
if v3 == []:
remove_keys.append(k3)
if len(remove_keys) == len(v2[i]): #if this would remove all keys
v2.pop(i) #delete whole "empty" dict again...
i -= 1
else:
for k3 in remove_keys: #remove stored keys from dict in v2 at i
v2[i].pop(k3)
i += 1
Upvotes: 1
Reputation: 3346
instead of deleting assign empty list
you can try below code:
for k1, v1 in profiles_dict.items():
for k2, v2 in v1.items():
if type(v2) != list:
continue
for i in v2:
for k3, v3 in i.items():
if v3 == []:
v1[k2] = []
Upvotes: 1