A simple recursive function could solve this for you:
a = {}
a["foo"] = ["bar"]
a["bar"] = ["baz", "baz2"]
a["baz"] = ["bax"]
def flatten(dic,arg):
if dic.get(arg,[]) == []:
return []
lst = []
for item in dic[arg]:
lst += flatten(dic,item) + [item]
return lst
print(flatten(a,'foo'))
>> ['bax', 'baz', 'baz2', 'bar']
The dic
argument defines what dictionary you are using, the arg
is the key of the dictionary. The function tries to find for every ite, a new entry as key in the dictionary. This algorithm performs a tree-like walk to the roots, and adds the items when it goes away from each root. This is also known as a post-order walk. You can then do this for every entry in the dictionary to flatten all keys.
NOTE that I did not take into account order. You could use another tree-walking method, to change the order of when items are added to the list.
To complete the answer, I applied it to your second example. I made a new dictionary that flattens the current dictionary by:
a = {i:flatten(a,i) for i in a.keys()}
So, the complete code will then be:
a = {}
a["foo"] = ["bar"]
a["bar"] = ["baz"]
a["bar2"] = ["bax"]
def flatten(dic,arg):
if dic.get(arg,[]) == []:
return []
lst = []
for item in dic[arg]:
lst += flatten(dic,item) + [item]
return lst
a = {i:flatten(a,i) for i in a.keys()}
>> {'foo': ['baz', 'bar'], 'bar': ['baz'], 'bar2': ['bax']}
NOTE: As noted in the comments, this implementation does not work when you are working with:
- A key value pair where the key is contained in the value
- Two key value pairs where key 1 is contained in value 2 and key 2 in value 1
These two scenarios create a cycle, which, will never end. To solve the first scenario, make sure for each key value pair, that the key is not contained in said value.
# Scenario 1
a['foo'] = ['foo', 'bar']
a['bar'] = ['baz']
should be something along the lines of*:
a['foo'] = a['foo'] - ['foo']
The second scenario is a little more tricky. The result of the flatten function is the same for key 1 and key 2. Ultimately, this would mean that calling the flatten function for one of the keys, and having both keys removed from their combined values, would work:
# Scenario 2
a['foo'] = ['bar','baz']
a['bar'] = ['foo','bax']
should therefore be something along the lines of*:
a['foo'] = a['bar'] = a['foo'] + f['bar'] - ['foo','bar']
However, the implementation itself and how you would solve this for different (longer, more complicated) cycles, is what I believe, out of the scope of the question.
*: the minus operator doesn't work on lists like this, but I bet you could come up with a solution for that, if you are going to look into this.
NOTE: You mentioned in the comments that you do not want 'bar' as key of your flattened dictionary. You will want to find the root of the dictionary such that any children are not keys. In order to do that, you need check for each key, if it is not in a value of the same or other keys. This code should do the trick:
allValues = []
for item in a.values():
allValues += item
roots = {key:a[key] for key in a.keys() if key not in allValues}
>> {'foo': ['baz', 'bar'], 'bar2': ['bax']}
First it combines all values of the keys. Then it will make the dictionary by looking at which key does not occur in the list of all of the values. The dictionary roots
gives you the dictionary as desired.