将字典列表拆分为字典的列表

10

我需要做的是将类似以下内容进行转换

{'key1': [1, 2, 3], 'key2': [4, 5, 6]}

转换为

[{'key1': 1, 'key2': 4}, {'key1': 2, 'key2': 5}, {'key1': 3, 'key2': 6}]

值列表的长度可能会有所不同! 最快的方法是什么(最好不使用for循环)?


你可能需要稍微澄清一下你的问题...我花了一些时间才理解你从示例中想要什么:将一个具有两个键(key1,key2)的映射(每个键都有一个值列表(长度相同但不同))转换为一对列表,在第i个位置的一对中,key1和key2分别设置为它们各自列表的第i个元素。就是这样吗? - Eric Bréchemier
为什么对于不使用for循环如此着迷?那是一个愚蠢的限制。 - jcdyer
这不是必须的,我写了“最好”。虽然可能有一种快速的Pythonic方法来完成这个任务(有些神奇的函数我还不知道;) - user187676
相关链接:https://dev59.com/Tl0a5IYBdhLWcg3wRW2V - 0 _
8个回答

12

适用于任意数量的键

>>> map(dict, zip(*[[(k, v) for v in value] for k, value in d.items()]))
[{'key2': 4, 'key1': 1}, {'key2': 5, 'key1': 2}, {'key2': 6, 'key1': 3}]
例如:
d = {'key3': [7, 8, 9], 'key2': [4, 5, 6], 'key1': [1, 2, 3]}

>>> map(dict, zip(*[[(k, v) for v in value] for k, value in d.items()]))
[{'key3': 7, 'key2': 4, 'key1': 1}, {'key3': 8, 'key2': 5, 'key1': 2}, {'key3': 9, 'key2': 6, 'key1': 3}]

适用于任何数量的值或键的通用解决方案:(Python2.6)

>>> from itertools import izip_longest
>>> d = {'key2': [3, 4, 5, 6], 'key1': [1, 2]}
>>> map(lambda a: dict(filter(None, a)), izip_longest(*[[(k, v) for v in value] for k, value in d.items()]))
[{'key2': 3, 'key1': 1}, {'key2': 4, 'key1': 2}, {'key2': 5}, {'key2': 6}]

如果你没有Python 2.6:

>>> d = {'key2': [3, 4, 5, 6], 'key1': [1, 2]}
>>> map(lambda a: dict(filter(None, a)), map(None, *[[(k, v) for v in value] for k, value in d.items()]))
[{'key2': 3, 'key1': 1}, {'key2': 4, 'key1': 2}, {'key2': 5}, {'key2': 6}]

4
假设键的数量和每个键的值都是任意且先验未知的,使用for循环获取结果最为简单。
  itit = thedict.iteritems()
  k, vs = next(itit)
  result = [{k: v} for v in vs]
  for k, vs in itit:
    for d, v in itertools.izip(result, vs):
      d[k] = v

这个数据结构可以被折叠起来,但我对此持怀疑态度,因为这样做可能会影响性能(如果涉及的数据结构非常庞大以至于需要性能优化,则在内存中构建任何额外的辅助结构都可能变得昂贵——我的简单方法特别小心地避免了任何这样的中间结构)。

编辑:另一种选择,特别是当整体数据结构很大,但在某些用例中您可能只需要“部分”“转换”结构时,就是构建一个提供所需接口的类,但是实时生成,而不是一次性完成所有转换(如果原始结构可以更改,并且转换后的结构需要反映原始结构的当前状态等等,则这可能特别有帮助)。

当然,对于这样的目的,非常有帮助的是确定您的下游代码使用“字典列表”的确切功能。例如,假设您实际上只需要“只读”索引(不更改、迭代、切片、排序等):X[x]必须返回一个字典,其中每个键k映射到一个值,使得(将O定义为原始字典列表)X[x][k] is O[k][x]。然后:

class Wrap1(object):
  def __init__(self, O):
    self.O = O
  def __getitem__(self, x):
    return dict((k, vs[x]) for k, vs in self.O.iteritems())

如果您实际上不需要包装结构来跟踪对原始结构的修改,那么__getitem__也可以“缓存”它返回的字典。
class Wrap2(object):
  def __init__(self, O):
    self.O = O
    self.cache = {}
  def __getitem__(self, x):
    r = self.cache.get(x)
    if r is None:
      r = self.cache[x] = dict((k, vs[x]) for k, vs in self.O.iteritems())
    return r

请注意,这种方法可能会在缓存中出现一些重复,例如,如果O的列表每个都有7个项目,则在x==6x==-1处的缓存可能会出现两个相等的字典;如果这是一个问题,你可以在使用__getitem__之前,将负数x归一化为len(self.O)加上它们来解决。
如果你还需要迭代,以及这个简单的索引,那也不难:只需添加一个__iter__方法,容易实现,比如一个简单的生成器...:
  def __iter__(self, x):
    for i in xrange(len(self.O)):
      yield self[i]

逐步地,如果您需要列表的更多功能(最坏的情况是,一旦您实现了这个__iter__,您可以构建self.L = list(self) - 回到“大爆炸”方法 - 然后,对于任何进一步的请求,将其推迟到self.L...但是,如果您希望采用这种方法处理特殊方法,您将不得不制作一个特殊的元类,或者使用一些更微妙的技巧,如self.__class__ = list; self[:] = self.L,然后是适当的dels;-)。


2

如果您始终可以使用两个密钥:

[{'key1':a, 'key2':b} for (a,b) in zip(d['key1'], d['key2'])]

1
如何?
d = {'key1': [1, 2, 3], 'key2': [4, 5, 6]}
[dict(zip(d.keys(),i)) for i in zip(*d.values())]

返回:
[{'key1': 1, 'key2': 4}, {'key1': 2, 'key2': 5}, {'key1': 3, 'key2': 6}]

1
>>> a = {'key1': [1, 2, 3], 'key2': [4, 5, 6]}
>>> [dict((key, a[key][i]) for key in a.keys()) for i in range(len(a.values()[0]))]
[{'key2': 4, 'key1': 1}, {'key2': 5, 'key1': 2}, {'key2': 6, 'key1': 3}]

值列表的长度可能会变化。range(3)是相当恒定的。 - zlack

1
d = {'key1': [1, 2, 3], 'key2': [4, 5, 6]}

keys = d.keys()
vals = zip(*[d[k] for k in keys])
l = [dict(zip(keys, v)) for v in vals]
print l

生成

[{'key2': 4, 'key1': 1}, {'key2': 5, 'key1': 2}, {'key2': 6, 'key1': 3}]

1
没有使用for循环,map的内部过程实际上是在迭代,只是没有使用关键字for
>>> x={'key1': [1, 2, 3], 'key2': [4, 5, 6]}

>>> map(lambda x,y:{'key1':x,'key2':y},x['key1'],x['key2'])

[{'key2': 4, 'key1': 1}, {'key2': 5, 'key1': 2}, {'key2': 6, 'key1': 3}]

0
list(map( dict, zip(*([(key, val) for val in data[key]] for key in data.keys()))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接