jonozzz
jonozzz

Reputation: 83

Python: Combine several nested lists into a dictionary

I have a bunch of lists like the following two:

['a', ['b', ['x', '1'], ['y', '2']]]
['a', ['c', ['xx', '4'], ['gg', ['m', '3']]]]

What is the easiest way to combine all of them into a single dictionary that looks like:

{'a': {
    'b': {
        'x':1,
        'y':2
    }
    'c': {
        'xx':4,
        'gg': {
            'm':3
        }
    }
}

The depth of nesting is variable.

Upvotes: 1

Views: 3264

Answers (3)

intuited
intuited

Reputation: 24044

It made the most sense to me to break this problem into two parts (well, that and I misread the question the first time through..'S)

transformation

The first part transforms the [key, list1, list2] data structure into nested dictionaries:

def recdict(elements):
    """Create recursive dictionaries from [k, v1, v2, ...] lists.

    >>> import pprint, functools
    >>> pprint = functools.partial(pprint.pprint, width=2)
    >>> pprint(recdict(['a', ['b', ['x', '1'], ['y', '2']]]))
    {'a': {'b': {'x': '1',
                 'y': '2'}}}
    >>> pprint(recdict(['a', ['c', ['xx', '4'], ['gg', ['m', '3']]]]))
    {'a': {'c': {'gg': {'m': '3'},
                 'xx': '4'}}}
    """

    def rec(item):
        if isinstance(item[1], list):
            return [item[0], dict(rec(e) for e in item[1:])]

        return item

    return dict([rec(elements)])

It expects that

  • every list has at least two elements
  • the first element of every list is a key
  • if the second element of a list is a list, then all subsequent elements are also lists; these are combined into a dictionary.

The tricky bit (at least for me) was realizing that you have to return a list from the recursive function rather than a dictionary. Otherwise, you can't combine the parallel lists that form the second and third elements of some of the lists.

To make this more generally useful (i.e. to tuples and other sequences), I would change

if isinstance(item[1], list):

to

if (isinstance(item[1], collections.Sequence)
    and not isinstance(item[1], basestring)):

You can also make it work for any iterable but that requires a little bit of reorganization.

merging

The second part merges the dictionaries that result from running the first routine on the two given data structures. I think this will recursively merge any number of dictionaries that don't have conflicting keys, though I didn't really test it for anything other than this use case.

def mergedicts(*dicts):
    """Recursively merge an arbitrary number of dictionaries.
    >>> import pprint
    >>> d1 = {'a': {'b': {'x': '1',
    ...                   'y': '2'}}}
    >>> d2 = {'a': {'c': {'gg': {'m': '3'},
    ...                   'xx': '4'}}}
    >>> pprint.pprint(mergedicts(d1, d2), width=2)
    {'a': {'b': {'x': '1',
                 'y': '2'},
           'c': {'gg': {'m': '3'},
                 'xx': '4'}}}
    """

    keys = set(k for d in dicts for k in d)

    def vals(key):
        """Returns all values for `key` in all `dicts`."""
        withkey = (d for d in dicts if d.has_key(key))
        return [d[key] for d in withkey]

    def recurse(*values):
        """Recurse if the values are dictionaries."""
        if isinstance(values[0], dict):
            return mergedicts(*values)
        if len(values) == 1:
            return values[0]
        raise TypeError("Multiple non-dictionary values for a key.")

    return dict((key, recurse(*vals(key))) for key in keys)

Upvotes: 1

jon_darkstar
jon_darkstar

Reputation: 16768

It's not really 'pythonic' but i dont see a good way to do this without recursion

def listToDict(l):
    if type(l) != type([]): return l
    return {l[0] : listToDict(l[1])}

Upvotes: 1

GWW
GWW

Reputation: 44093

Here's a very crude implementation, it does not handle weird cases such as lists having less than two elements and it overwrites duplicate keys, but its' something to get you started:

l1 = ['a', ['b', ['x', '1'], ['y', '2']]]
l2 = ['a', ['c', ['xx', '4'], ['gg', ['m', '3']]]]

def combine(d, l):
    if not l[0] in d:
        d[l[0]] = {}

    for v in l[1:]:
        if type(v) == list:
            combine(d[l[0]],v)
        else:
            d[l[0]] = v

h = {}
combine(h, l1)
combine(h, l2)
print h

Output:

{'a': {'c': {'gg': {'m': '3'}, 'xx': '4'}, 'b': {'y': '2', 'x': '1'}}}

Upvotes: 2

Related Questions