【问题标题】:Flatten a dictionary of dictionaries (2 levels deep) of lists展平列表的字典字典(2 级深)
【发布时间】:2020-02-27 08:29:26
【问题描述】:

我正在努力解决这个问题,但它不够灵活。

在我的 Python 脚本中,我有一个列表字典。 (实际上它变得更深了,但这个问题不涉及这个级别。)我想把所有这些都压缩成一个长列表,扔掉所有的字典键。

所以我想变身

{1: {'a': [1, 2, 3], 'b': [0]},
 2: {'c': [4, 5, 1], 'd': [3, 8]}}

[1, 2, 3, 0, 4, 5, 1, 3, 8]

我可能会设置一个 map-reduce 来迭代外部字典的项目,以从每个子字典构建一个子列表,然后将所有子列表连接在一起。

但这对于大型数据集来说似乎效率低下,因为中间数据结构(子列表)会被丢弃。有没有办法一次性完成?

除此之外,我很乐意接受一个有效的两级实现......我的 map-reduce 生锈了!

更新: 对于那些感兴趣的人,下面是我最终使用的代码。

请注意,尽管我在上面要求一个列表作为输出,但我真正需要的是一个排序列表;即展平的输出可以是任何可以排序的迭代。

def genSessions(d):
    """Given the ipDict, return an iterator that provides all the sessions,
    one by one, converted to tuples."""
    for uaDict in d.itervalues():
        for sessions in uaDict.itervalues():
            for session in sessions:
                yield tuple(session)

...

# Flatten dict of dicts of lists of sessions into a list of sessions.
# Sort that list by start time
sessionsByStartTime = sorted(genSessions(ipDict), key=operator.itemgetter(0))
# Then make another copy sorted by end time.
sessionsByEndTime = sorted(sessionsByStartTime, key=operator.itemgetter(1))

再次感谢所有提供帮助的人。

[更新:将nthGetter() 替换为operator.itemgetter(),感谢@intuited。]

【问题讨论】:

  • 酷,很高兴你把它整理好了,可以这么说。您可能知道或可能不知道的几件事:1)采用 Martelli 先生的回答并将括号(即[])更改为括号将使其成为执行相同操作的“生成器表达式”作为你的 genSessions 函数,可以说可读性有点差。
  • 2) 标准库函数operator.itemgetter 与您的nthGetter 做同样的事情。该模块中的函数在 C 中实现并针对速度进行了调整,因此如果这是一个瓶颈,则使用该函数可以为您提供加速。它还可以让你通过使用一个常用的习惯来减少代码的大小,这总是很好的。
  • @intuited - 感谢您的两个提示。我不知道制作匿名生成器 (1) - 我会这样做,尽管正如你所说,它可能更具可读性。 (2) 我以为我曾在这样的操作员身上看到过,但是当我看的时候却找不到。我一定会使用 itemgetter。
  • @ninjagecko:我回滚了你的更改。您的编者注(“由于这不使用递归或外积原语,它只能在深度完全等于 2 的嵌套列表上工作”),虽然是真的,但属于评论。如问题中所述,我将代码设计为在深度为 2 的嵌套结构上工作。我恢复了标题,因为您更改的标题描述了接受的解决方案而不是问题;而人们通常从问题开始。
  • 我很抱歉。我已经把你刚才说的“(2层深)”的标题说清楚了,让更多的人不糊涂。由于标题相似,另一个问题被标记为重复。

标签: python data-structures mapreduce dictionary


【解决方案1】:

我希望你意识到你在字典中看到的任何顺序都是偶然的——它的存在只是因为当屏幕上显示时,必须选择一些顺序,但绝对不能保证。

各个子列表之间的排序问题得到连接,

[x for d in thedict.itervalues()
   for alist in d.itervalues()
   for x in alist]

做你想做的事,没有任何低效或中间列表。

【讨论】:

  • 谢谢。这正是我一直在寻找的……简短而高效。我计划在之后对扁平列表进行排序。我的示例输出并非旨在暗示已定义的顺序,但您最好检查一下。
  • @Lipis:有什么特别的原因吗?这是一个很好的答案,但我在 cmets 中解释了为什么我接受了直觉的答案。
  • @LarsH aha.. 没太注意.. 只是偶然发现了这个问题,我以为它被遗忘了之类的.. :) 另外我是 Alex 的粉丝.. 但没关系我现在读了评论..
【解决方案2】:

编辑:重新阅读原始问题并修改答案以假设所有非字典都是要展平的列表。

如果您不确定字典的深度,您可能需要使用递归函数。 @Arrieta 已经有 posted 一个递归构建非字典值列表的函数。

这是一个生成器,在字典树中产生连续的非字典值:

def flatten(d):
    """Recursively flatten dictionary values in `d`.

    >>> hat = {'cat': ['images/cat-in-the-hat.png'],
    ...        'fish': {'colours': {'red': [0xFF0000], 'blue': [0x0000FF]},
    ...                 'numbers': {'one': [1], 'two': [2]}},
    ...        'food': {'eggs': {'green': [0x00FF00]},
    ...                 'ham': ['lean', 'medium', 'fat']}}
    >>> set_of_values = set(flatten(hat))
    >>> sorted(set_of_values)
    [1, 2, 255, 65280, 16711680, 'fat', 'images/cat-in-the-hat.png', 'lean', 'medium']
    """
    try:
        for v in d.itervalues():
            for nested_v in flatten(v):
                yield nested_v
    except AttributeError:
        for list_v in d:
            yield list_v

doctest 将生成的迭代器传递给set 函数。这很可能是您想要的,因为正如 Martelli 先生指出的那样,字典的值没有内在的顺序,因此没有理由跟踪它们被发现的顺序。

您可能希望跟踪每个值的出现次数;如果您将迭代器传递给set,此信息将丢失。如果你想跟踪它,只需将flatten(hat) 的结果传递给其他函数而不是set。在 Python 2.7 下,其他函数可能是 collections.Counter。为了与进化较少的 python 兼容,您可以编写自己的函数或(在效率上有所损失)将 sorteditertools.groupby 结合起来。

【讨论】:

  • 感谢您提供的信息丰富的回答。我使用的数据结构是常规的,但为了通用性,我很高兴了解递归方法。对于 set(),似乎元素必须是可散列的,但在我的情况下并非如此:元素本身就是列表,正如我在原始问题中提到的那样。但是我可能会将它们转换为元组,因为它们不再需要是可变的。
  • 虽然我已经接受了亚历克斯的出色回答,但我还是打算改接受这个。希望这不是失礼。为什么:1)这提供了一个迭代器而不是一个列表,它可能比一个将被丢弃的显式中间列表更有效地传递给 sorted()(尽管我承认我要求一个列表作为输出)。 2)你花了很多时间和解释;我学到了更多。 3) 分数对你来说比对他更重要。 :-)
  • @LarsH:啊,你的意思是“它变得更深一点”的评论。是的,如果您将 lists-within-lists-within-dicts-within-dict 转换为元组,您可以将它们添加到集合中。像set(tuple(list_) for list_ in flatten(hat)) 这样的东西是最简单的方法。尽管您想要这样做的事实表明可能有理由将它们保留为列表,或者从一开始就使用元组。再说一次,如果您确实打算在它们的可变存在中创建这个特定点的“快照”,那么这样做是有意义的。
  • 我实际上不会使用集合...我偶尔会有价值相等的不同元素,我希望它们不同,但它们的数量还不够(也不是它们的等式显着)以使对每个元素进行计数是值得的。在我构建它们时,这些元素需要是可变的,但它们不再需要超过这一点。
  • 如果您有兴趣,我发布了我最终使用的代码。再次感谢您的帮助。
【解决方案3】:

递归函数可能会起作用:

def flat(d, out=[]):
 for val in d.values():
  if isinstance(val, dict):
    flat(d, out)
  else:
    out+= val

如果您尝试使用:

>>> d = {1: {'a': [1, 2, 3], 'b': [0]}, 2: {'c': [4, 5, 6], 'd': [3, 8]}}
>>> out = []
>>> flat(d, out)
>>> print out
[1, 2, 3, 0, 4, 5, 6, 3, 8]

注意字典没有顺序,所以列表是随机顺序的。

您也可以return out(在循环结束时)并且不要使用列表参数调用该函数。

def flat(d, out=[]):
 for val in d.values():
  if isinstance(val, dict):
    flat(d, out)
  else:
    out+= val
 return out

调用为:

my_list = flat(d)

【讨论】:

  • 感谢您的回答。我从中学到了一些好东西!就像使用 += (aka .append()) 就地连接一个累积列表,因此不构建临时中间列表。
  • 你可以从这个例子中学到更多:关于无限递归和使用函数参数默认值的错误方法
猜你喜欢
  • 2019-01-11
  • 1970-01-01
  • 2018-07-19
  • 2019-11-04
  • 2021-01-05
  • 1970-01-01
  • 2019-05-28
  • 1970-01-01
  • 2021-09-07
相关资源
最近更新 更多