1

I would like to iterate through a dict whose values are a mixture of lists, unicode strings, dicts, bools, and ints to produce a one-dimensional dict that has all key-value pairs. I don't care to preserve keys whose associated values are dicts.

I tried a recursive function but I am missing some steps. Perhaps I need to use .update() or += somewhere?

def unravel(data):
    resultsdict = {}
    for k in data:
        if isinstance(data[k],dict):
            unravel(data[k])
        else:
            resultsdict[k] = data[k]  

Example of my top-level dict's values:

<type 'list'>
<type 'bool'>
<type 'dict'>
<type 'unicode'>
<type 'bool'>
<type 'unicode'>
<type 'dict'>
<type 'int'>
<type 'unicode'>
Bharel
  • 23,672
  • 5
  • 40
  • 80
Benjamin James
  • 941
  • 1
  • 9
  • 24
  • It seems every time you dive into this recursive function, you're reseting the dictionary with resultsdict = {}. I think that may be an issue? – Adib Apr 12 '16 at 20:13
  • So for every other dict that is contained within your dict, you want to move that dict’s content up to the root level of your structure? E.g. `{ 'a': { 'b': 'c'}, 'd': 'e' }` becomes `{ 'b': 'c', 'd': 'e' }`? – poke Apr 12 '16 at 20:13
  • @poke you are correct – Benjamin James Apr 12 '16 at 20:16
  • @Adib also correct, perhaps I should be using `yield` ? – Benjamin James Apr 12 '16 at 20:17

3 Answers3

4

You were almost there, you need to return the created dictionary though, and update the dictionary with the returned value from the recursive call:

def unravel (data):
    d = {}
    for k, v in data.items():
        if isinstance(v, dict):
            d.update(unravel(v))
        else:
            d[k] = v
    return d

Used like this:

>>> unravel({ 'a': { 'b': 'c', 'd': 'e' }, 'f': 'g', 'h': { 'i': 'j' } })
{'f': 'g', 'i': 'j', 'b': 'c', 'd': 'e'}
poke
  • 369,085
  • 72
  • 557
  • 602
1

Your method unravel creates a new instance of resultsdict every time you recursively call it. Hence, not all data is getting into the master dictionary (so to speak). Try something like this instead:

def unravel(data, resultsdict={}):
    for k in data:
        if isinstance(data[k],dict):
            unravel(data[k], resultsdict)
        else:
            resultsdict[k] = data[k]
    return resultsdict

In recursion scenarios such as this, you should keep carrying the mutable datastructure with you every time you recurse.

th3an0maly
  • 3,360
  • 8
  • 33
  • 54
  • 1
    Please avoid [mutable default arguments](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument), even if you don’t actually mutate it here. – poke Apr 12 '16 at 20:19
  • Actually, I didn’t see that edit. Now it’s just broken. Try `unravel({'a': 'b'})` followed with a `unravel({'b': 'c'})`. The latter will return `{'a': 'b', 'b': 'c'}` now. – poke Apr 12 '16 at 20:22
  • I like your solution :D – th3an0maly Apr 12 '16 at 20:22
  • You're welcome. I'm glad you selected poke's answer. It's spot on :) – th3an0maly Apr 12 '16 at 20:51
0

You can just unpack all the dictionary tuples and then flatten the whole thing

# Turn every key,value pair into a list of tuples 
# [(key, value)] if it's any datatype but dict
# value.iteritems() otherwise
grouped_key_value_pairs = [v.iteritems() if isinstance(v,dict) else [(k,v)] for k,v in data.iteritems()]

# Flatten into a single list of tuples and turn into dict!
result = dict([kv_pair for group in all_kv_pairs for kv_pair in group])
jfbeltran
  • 1,808
  • 3
  • 13
  • 17