Python:将多个嵌套列表合并为字典

1 投票
3 回答
3223 浏览
提问于 2025-04-16 15:15

我有一堆列表,像下面这两个:

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

有没有简单的方法把它们合并成一个字典,格式像这样:

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

这个字典的嵌套层数是可以变化的。

3 个回答

1

我觉得把这个问题分成两部分来解决最合适(当然,这也是因为我第一次看问题时理解错了..'S)

转换

第一部分是把 [key, list1, list2] 这种数据结构转换成嵌套字典:

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)])

它的要求是:

  • 每个列表至少要有两个元素
  • 每个列表的第一个元素是一个键
  • 如果一个列表的第二个元素也是一个列表,那么后面的所有元素也必须是列表;这些会被合并成一个字典。

对我来说,比较棘手的地方是要明白你需要从递归函数中返回一个列表,而不是字典。否则,你就无法合并那些形成某些列表的第二和第三个元素的并行列表。

为了让这个更通用(也就是说,可以适用于元组和其他序列),我会把

if isinstance(item[1], list):

改成

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

你也可以让它适用于任何可迭代对象,但这需要稍微调整一下结构。

合并

第二部分是合并通过对两个给定数据结构运行第一部分得到的字典。我认为这会递归地合并任何数量的字典,只要它们的键不冲突,尽管我没有测试过其他的用例。

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)
1

这其实不是很“符合Python风格”,但我看不出有什么好的办法可以做到这一点而不使用递归。

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

这里有一个非常简单的实现方式,它没有处理一些特殊情况,比如列表里元素少于两个的情况,而且如果有重复的键,它会覆盖掉之前的键。不过这可以作为一个入门的参考:

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

输出结果:

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

撰写回答