Maybe the dict is not intended to be used in this way, but I need to add more than one value to the same key. My intension is to use a kind of transitory property. If my dict is A:B and B:C, than I want to have the dict A:[B,C].
Let's make an example in order to explain better what I'd like to do:
numDict={'60':['4869'], '4869':['629'], '13':['2']}
I want it to return:
{'60':['4869','629'], '13':['2']}
For just two elements, it is possible to use something like this:
result={}
for key in numDict.keys():
if [key] in numDict.values():
result[list(numDict.keys())[list(numDict.values()).index([key])]]=[key]+numDict[key]
But what about if I have more elements? For example:
numDict={'60':['4869'], '4869':['629'], '13':['2'], '629':['427'}
What can I do in order to get returned {'60':[4869,629,427'], '13':['2']}?
def unchain(d):
#assemble a collection of keys that are not also values. These will be the keys of the final dict.
top_level_keys = set(d.keys()) - set(d.values())
result = {}
for k in top_level_keys:
chain = []
#follow the reference chain as far as necessary.
value = d[k]
while True:
if value in chain: raise Exception("Referential loop detected: {} encountered twice".format(value))
chain.append(value)
if value not in d: break
value = d[value]
result[k] = chain
return result
numDict={'60':'4869', '4869':'629', '13':'2', '629':'427'}
print(unchain(numDict))
Result:
{'60': ['4869', '629', '427'], '13': ['2']}
You might notice that I changed the layout of numDict since it's easier to process if the values aren't one-element lists. But if you're dead set on keeping it that way, you can just add d = {k:v[0] for k,v in d.items()} to the top of unchain, to convert from one to the other.
You can build your own structure, consisting of a reverse mapping of (values, key), and a dictionary of (key, [values]). Adding a key, value pair consists of following a chain of existing entries via the reverse mapping, until it finds the correct location; in case it does not exist, it introduces a new key entry:
class Groupir:
def __init__(self):
self.mapping = {}
self.reverse_mapping = {}
def add_key_value(self, k, v):
self.reverse_mapping[v] = k
val = v
key = k
while True:
try:
self.reverse_mapping[val]
key = val
val = self.reverse_mapping[val]
except KeyError:
try:
self.mapping[val].append(v)
except KeyError:
self.mapping[val] = [v]
break
with this test client:
groupir = Groupir()
groupir.add_key_value(60, 4869)
print(groupir.mapping)
groupir.add_key_value(4869, 629)
print(groupir.mapping)
groupir.add_key_value(13, 2)
print(groupir.mapping)
groupir.add_key_value(629, 427)
print(groupir.mapping)
outputs:
{60: [4869]}
{60: [4869, 629]}
{60: [4869, 629], 13: [2]}
{60: [4869, 629, 427], 13: [2]}
Restrictions:
Cycles as mentioned in comments.
Non unique keys
Non unique values
Probably some corner cases to take care of.
I have written a code for it. See if it helps.
What I have done is to go on diving in till i can go (hope you understand this statement) and mark them as visited as they will no longer be required. At the end I filter out the root keys.
numDict={'60':['4869'], '4869':['629'], '13':['2'], '629':['427']}
l = list(numDict) # list of keys
l1 = {i:-1 for i in numDict} # to track visited keys (initialized to -1 initially)
for i in numDict:
# if key is root and diving in is possible
if l1[i] == -1 and numDict[i][0] in l:
t = numDict[i][0]
while(t in l): # dive deeper and deeper
numDict[i].extend(numDict[t]) # update the value of key
l1[t] = 1 # mark as visited
t = numDict[t][0]
# filter the root keys
answer = {i:numDict[i] for i in numDict if l1[i] == -1}
print(answer)
Output:
{'60': ['4869', '629', '427'], '13': ['2']}
Related
I want to implement a function that:
Given a dictionary and an iterable of keys,
deletes the value accessed by iterating over those keys.
Originally I had tried
def delete_dictionary_value(dict, keys):
inner_value = dict
for key in keys:
inner_value = inner_value[key]
del inner_value
return dict
Thinking that since inner_value is assigned to dict by reference, we can mutate dict implcitly by mutating inner_value. However, it seems that assigning inner_value itself creates a new reference (sys.getrefcount(dict[key]) is incremented by assigning inner_value inside the loop) - the result being that the local variable assignment is deled but dict is returned unchanged.
Using inner_value = None has the same effect - presumably because this merely reassigns inner_value.
Other people have posted looking for answers to questions like:
how do I ensure that my dictionary includes no values at the key x - which might be a question about recursion for nested dictionaries, or
how do I iterate over values at a given key (different flavours of this question)
how do I access the value of the key as opposed to the keyed value in a dictionary
This is none of the above - I want to remove a specific key,value pair in a dictionary that may be nested arbitrarily deeply - but I always know the path to the key,value pair I want to delete.
The solution I have hacked together so far is:
def delete_dictionary_value(dict, keys):
base_str = f"del dict"
property_access_str = ''.join([f"['{i}']" for i in keys])
return exec(base_str + property_access_str)
Which doesn't feel right.
This also seems like pretty basic functionality - but I've not found an obvious solution. Most likely I am missing something (most likely something blindingly obvious) - please help me see.
If error checking is not required at all, you just need to iterate to the penultimate key and then delete the value from there:
def del_by_path(d, keys):
for k in keys[:-1]:
d = d[k]
return d.pop(keys[-1])
d = {'a': {'b': {'c': {'d': 'Value'}}}}
del_by_path(d, 'abcd')
# 'Value'
print(d)
# {'a': {'b': {'c': {}}}}
Just for fun, here's a more "functional-style" way to do the same thing:
from functools import reduce
def del_by_path(d, keys):
*init, last = keys
return reduce(dict.get, init, d).pop(last)
Don't use a string-evaluation approach. Try to iteratively move to the last dictionary and delete the key-value pair from it. Here a possibility:
def delete_key(d, value_path):
# move to most internal dictionary
for kp in value_path[:-1]:
if kp in dd and isinstance(d[kp], dict):
d = d[kp]
else:
e_msg = f"Key-value delete-operation failed at key '{kp}'"
raise Exception(e_msg)
# last entry check
lst_kp = value_path[-1]
if lst_kp not in d:
e_msg = f"Key-value delete-operation failed at key '{lst_kp}'"
raise Exception(e_msg)
# delete key-value of most internal dictionary
print(f'Value "{d[lst_kp]}" at position "{value_path}" deleted')
del d[lst_kp]
d = {1: 2, 2:{3: "a"}, 4: {5: 6, 6:{8:9}}}
delete_key(d, [44, 6, 0])
#Value "9" at position "[4, 6, 8]" deleted
#{1: 2, 2: {3: 'a'}, 4: {5: 6, 6: {}}}
I have a dictionary like
{a:{b:{c:{d:2}}}, e:2, f:2}
How am I supposed to get the value of d and change it in python? Previous questions only showed how to get the level of nesting but didn't show how to get the value. In this case, I do not know the level of nesting of the dict. Any help will be appreciated, thank you!!!
P.S. I am using python 3
How about some recursion?
def unest(data, key):
if key in data.keys():
return data.get(key)
else:
for dkey in data.keys():
if isinstance(data.get(dkey), dict):
return unest(data.get(dkey), key)
else:
continue
d = {'a':{'b':{'c':{'d':25}}}, 'e':2, 'f':2}
r = unest(d, 'd')
# 25
r = unest(d, 'c')
# {'d': 25}
Edit: As Paul Rooney points out we don't have to use data.keys, we can simply iterate over the keys by doing if key in data
Edit 2: Given the input you provided this would find the deepest level, however this does not cover certain cases, such as where for example, key e is also a nested dictionary that goes n + 1 levels where n is equal to the depth of key a.
def find_deepest(data):
if not any([isinstance(data.get(k), dict) for k in data]):
return data
else:
for dkey in data:
if isinstance(data.get(dkey), dict):
return find_deepest(data.get(dkey))
else:
continue
u = find_deepest(d)
# {'d': 2}
I was searching for a similar solution, but I wanted to find the deepest instance of any key within a (possibly) nested dictionary and return it's value. This assumes you know what key you want to search for ahead of time. It's unclear to me if that is a valid assumption for the original question, but I hope this will help others if they stumble on this thread.
def find_deepest_item(obj, key, deepest_item = None):
if key in obj:
deepest_item = obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = find_deepest_item(v, key, deepest_item)
if item is not None:
deepest_item = item
return deepest_item
In this example, the 'd' key exists at the top level as well:
d = {'a':{'b':{'c':{'d':25}}}, 'd':3, 'e':2, 'f':2}
v = find_deepest_item(d, 'd')
# 25
Search for 'c':
v = find_deepest_item(d, 'c')
# {'d': 25}
abc/pqr123/xy2/yes//T
abc/pqr245/kl3/yes//T
abc/ijk123/op5/yes//T
abc/pqr245/kl4/yes//T
These are the input values that I want to convert to a nested dictionary.Each value such as
abc, pqr123, xy2, yes, T
represents the name of a product.
My output should look something like this:
{"abc":{"pqr123":{"xy2":{"yes":{"T":[]}},"pqr245":"kl3":{"yes":{"T":
[]}},"kl4":{"yes":{"T":[]}},"ijk123":{"op5":{"yes":{"T":[]}}}
So I need a nested dictionary of all unique values and at the last key of the dictionary should have a value of empty list.
Below is my snippet of code that generates the output I require, but I want to do it more dynamically so it is best suited even
if the length of the input grows or shrinks. Please do let me know if are any better solution for this problem.
data_dict={}
for item in meta_line.split(','):
item = item.replace('//','/')
item = str(item)
item = item.split('/')
if item[0] == "":
continue
if item[0] not in data_dict.keys():
data_dict[item[0]] = {}
if item[1] not in data_dict[item[0]].keys():
data_dict[item[0]][item[1]] = {}
if item[2] not in data_dict[item[0]][item[1]].keys():
data_dict[item[0]][item[1]][item[2]] = {}
if item[3] not in data_dict[item[0]][item[1]][item[2]].keys():
data_dict[item[0]][item[1]][item[2]][item[3]] = {}
if item[4] not in data_dict[item[0]][item[1]][item[2]][item[3]].keys():
data_dict[item[0]][item[1]][item[2]][item[3]][item[4]] = []
You probably want something that's not dependent on so many massively nested brackets. This is a problem that using references to a mutable object will work well on.
meta_line = 'abc/pqr123/xy2/yes//T,abc/pqr245/kl3/yes//T,abc/ijk123/op5/yes//T,abc/pqr245/kl4/yes//T'
data = dict()
for item in meta_line.split(','):
dref = data
dict_tree = item.strip().replace('//', '/').split('/')
for i, val in enumerate(dict_tree):
if val in dref:
pass
elif i != len(dict_tree) - 1:
dref[val] = dict()
elif i == len(dict_tree) - 1:
dref[val] = list()
dref = dref[val]
Every iteration of the inner loop will move the reference dref down a level, and then reset it on every iteration of the outer loop. At the end, data should hold your nested dict.
Edit: Sorry, I just noticed that you wanted the last level to be a list. This is one solution to that problem, but isn't the best (it will create errors if there's a list in a spot that a later data entry wants to be a dict instead). I would probably choose to build my nested dict and then recursively replace any empty dicts with empty lists afterwards to avoid that problem.
You can use The dict.setdefault method in a loop to build the nested dictionary. I'll use the pprint module to display the output. Note that pprint.pprint sorts dictionary keys before the output is computed.
from pprint import pprint
data = '''\
abc/pqr123/xy2/yes//T
abc/pqr245/kl3/yes//T
abc/ijk123/op5/yes//T
abc/pqr245/kl4/yes//T
'''.splitlines()
nested_dict = {}
for row in data:
d = nested_dict
keys = [s for s in row.split('/') if s]
for key in keys[:-1]:
d = d.setdefault(key, {})
d[keys[-1]] = []
pprint(nested_dict)
output
{'abc': {'ijk123': {'op5': {'yes': {'T': []}}},
'pqr123': {'xy2': {'yes': {'T': []}}},
'pqr245': {'kl3': {'yes': {'T': []}}, 'kl4': {'yes': {'T': []}}}}}
I have a python dictionary containing n key-value pairs, out of which n-1 values are identical and 1 is not. I need to find the key of the distinct element.
For example: consider a python list [{a:1},{b:1},{c:2},{d:1}]. I need the to get 'c' as the output.
I can use a for loop to compare consecutive elements and then use two more for loops to compare those elements with the other elements. But is there a more efficient way to go about it or perhaps a built-in function which I am unaware of?
If you have a dictionary you can quickly check and find the first value which is different from the next two values cycling around the keys of your dictionary.
Here's an example:
def find_different(d):
k = d.keys()
for i in xrange(0, len(k)):
if d[k[i]] != d[k[(i+1)%len(k)]] and d[k[i]] != d[k[(i+2)%len(k)]]:
return k[i]
>>> mydict = {'a':1, 'b':1, 'c':2, 'd':1}
>>> find_different(mydict)
'c'
Otherwise, if what you have is a list of single-key dictionaries, then you can do it quite nicely mapping your list with a function which "extracts" the values from your elements, then check each one using the same logic.
Here's another working example:
def find_different(l):
mask = map(lambda x: x[x.keys()[0]], l)
for i in xrange(0, len(l)):
if mask[i] != mask[(i+1)%len(l)] and mask[i] != mask[(i+2)%len(l)]:
return l[i].keys()[0]
>>> mylist = [{'a':1},{'b':1},{'c':2},{'d':1}]
>>> find_different(mylist)
'c'
NOTE: these solutions do not work in Python 3 as the map function doesn't return a list and neither does the .keys() method of dictionaries.
Assuming that your "list of pairs" (actually list of dictionaries, sigh) cannot be changed:
from collections import defaultdict
def get_pair(d):
return (d.keys()[0], d.values()[0])
def extract_unique(l):
d = defaultdict(list)
for key, value in map(get_pair, l):
d[value].append(key)
return filter(lambda (v,l): len(l) == 1, d.items())[0][1]
If you already have your dictionary, then you make a list of all of the keys: key_list = yourDic.keys(). Using that list, you can then loop through your dictionary. This is easier if you know one of the values, but below I assume that you do not.
yourDic = {'a':1, 'b':4, 'c':1, 'd':1, }
key_list = yourDic.keys()
previous_value = yourDic[key_list[0]] # Making it so loop gets past first test
count = 0
for key in key_list:
test_value = yourDic[key]
if (test_value != previous_value) and count == 1: # Checks first key
print key_list[count - 1]
break
elif (test_value != previous_value):
print key
break
else:
previous_value = test_value
count += 1
So, once you find the value that is different, it will print the key. If you want it to print the value, too, you just need a print test_value statement
I've searched around for the error it gives me, but I don't understand that quite well. They did something with for k, v in dbdata.items, but that didn't work for me neither, it gives me other errors.
Well, I want is to delete multiple items.
tskinspath = ['1', '2']
#
dbdata = {}
dbdata['test'] = {}
dbdata['test']['skins_t'] = {}
# Adds the items
dbdata['test']['skins_t']['1'] = 1
dbdata['test']['skins_t']['2'] = 0
dbdata['test']['skins_t']['3'] = 0
dbdata['test']['skins_t']['4'] = 0
# This doesn't work
for item in dbdata["test"]["skins_t"]:
if item not in tskinspath:
if dbdata["test"]["skins_t"][item] == 0:
del dbdata["test"]["skins_t"][item]
# exceptions.RunetimeError: dictonary changed size during iteration
Instead of iterating over the dictionary, iterate over dict.items():
for key, value in dbdata["test"]["skins_t"].items():
if key not in tskinspath:
if value == 0:
del dbdata["test"]["skins_t"][key]
On py3.x use list(dbdata["test"]["skins_t"].items()).
Alternative:
to_be_deleted = []
for key, value in dbdata["test"]["skins_t"].iteritems():
if key not in tskinspath:
if value == 0:
to_be_deleted.append(key)
for k in to_be_deleted:
del dbdata["test"]["skins_t"][k]
The error message says it: you shouldn't modify the dictionary that you are iterating over. Try
for item in set(dbdata['test']['skins_t']):
...
This way you are iterating over a set that contains all keys from dbdata['test']['skins_t'].
As the question details is way aside from the question, If you are looking for a solution that deletes multiple keys from a given dict use this snippet
[s.pop(k) for k in list(s.keys()) if k not in keep]
Additionally, you can create a new dict via comprehension.
new_dict = { k: old_dict[k] for k in keep }