减少字典值的并集会产生意想不到的结果

Reduction of a union of dictionary values produces unexpected results

我想合并所有字典值,在本例中是集合。如果输入列表中恰好有两个词典,我只会得到预期的结果。

输入列表中的两个词典产生了预期的结果:

>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}])
set([1, 2, 3, 4])

输入列表中的三个词典产生类型错误。

预期结果:set([1, 2, 3, 4, 5, 6])

>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
  File "<input>", line 1, in <lambda>
    reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
TypeError: 'set' object has no attribute '__getitem__'

输入列表中的一个字典生成字典而不是集合。

预期结果:set([1, 2])

>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}])
{'a': set([1, 2])}

一个空的输入列表也会产生不同的类型错误。

预期结果:set([])

>>> reduce((lambda x, y: x['a'] | y['a']), [])
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    reduce((lambda x, y: x['a'] | y['a']), [])
TypeError: reduce() of empty sequence with no initial value

我需要帮助来理解我做错了什么以及为什么会产生这些结果。

传递给 reduce 的函数的输出必须与迭代器中的项目具有相同的类型,以便它可以使用相同的函数继续聚合项目值。

在您的例子中,lambda x, y: x['a'] | y['a'] 的输出是一个集合 {1, 2, 3, 4},因此当 reduce 尝试将第三项 {'a': {5, 6}}{1, 2, 3, 4} 聚合时,它失败了,因为 lambda 函数将 xy 都视为字典,并尝试通过键 'a' 获取每个项,而集合没有。

至于TypeError: reduce() of empty sequence with no initial value异常,你只需要提供reduce一个初始值作为第三个参数,在你的情况下应该是一个空集{},但是你只需要首先放弃向它传递一个字典列表的想法,而是向它传递一个集合列表。

reduce 迭代工作,它将对序列的项目应用减少聚合。例如,给定元素 ijk,连同函数 foo,它将处理 foo(foo(i, j), k).

在您的示例中,foo(i, j) 工作正常,给出 set,但外部调用失败,因为结果是 set,没有密钥 [=21] =].后台调用 __getitem__ 的语法 [],这就是为什么您会看到与此方法相关的错误。

你能做些什么?

一个简单的 hack 是让你的函数输出一个字典,然后直接访问它的唯一值。这可确保您的函数始终输出带有键 'a'.

的字典
reduce((lambda x, y: {'a': x['a'] | y['a']}),
       [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])['a']

# {1, 2, 3, 4, 5, 6}

更具可读性,可以定义一个命名函数:

def foo(x, y):
    return {'a': x['a'] | y['a']}

L = [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]

reduce(foo, L)['a']

TLDR:

reduce(function, iterable) 调用递归地将 function 应用于 iterable 先前结果的元素。这意味着 function 的 return 类型必须是有效的输入类型!

  • 在您的情况下,function 需要 dict,但会产生 set。由于无法在 set 上调用 x['y'],因此引发 TypeError
  • iterable 只有两个元素时,function 仅应用 一次 并且仅应用于这些元素。 function 的 return 类型不是有效输入类型的问题因此从未遇到过。

您必须先 mapdictset 然后 reduce set

reduce(lambda x, y: x | y, map(lambda x: x['a'], [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]))
#    merge via reduce ^     convert via map ^  

为什么 reduce 在某些情况下会失败

调用 reduce(function, iterable) 执行与此代码等效的操作:

def reduce(function, iterable, start=None):
    result = next(iterable) if start is None else start # 1.
    for element in iterable:
        result = function(result, element)              # 2.
    return result

这导致了几种情况:

  1. iterable 一个元素 并且 start 未设置
    • resultiterable (1.) 的第一个元素
      • function 从未被调用;它的 return 和输入类型无关紧要
  2. iterable 两个元素 并且 start 未设置
    • resultiterable(1.)
    • 第一个元素
    • function 第一个元素 next 元素上调用 (2.)
      • function 永远不会收到自己的结果;它的 return 类型没有意义
  3. iterable 两个以上的元素 并且 start 未设置
    • resultiterable (1.)
    • 第一个元素
    • function 第一个元素 next 元素上调用 (2.)
    • function 上一个结果 next 元素上调用 (2.)
      • function 收到自己的结果;它的 return 类型和输入类型必须匹配
  4. iterable空或不为空且设置start
    • 如果 startiterable
    • 的第一个元素,则与上面相同
  5. iterablestart 未设置
      无法设置
    • result 并引发 TypeError (1.)

你的情况是:

  • Two dictionaries 是 2. 并且按预期工作。
  • 三个词典 是 3. 并且在不兼容的输入和 return 类型上阻塞。
  • 一个空的输入列表 是 5. 并且在缺少输入时失败 - 正如预期的那样。

如何代替

map/reduce

你的 reduce 实际上是同时做两件事:它 converts/extracts 每个 元素单独,然后合并两个结果。这是一个经典的 map/reduce 任务:每个元素一个,所有元素一个。

您可以使用 mapreduce 内置函数将其直接拆分为两个单独的操作:

sets = map(lambda x: x['a'], [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
result = reduce(lambda x, y: x | y, sets)

当然你也可以直接嵌套两个表达式

comprehension/reduce

map 部分可以用理解来表达。

sets = (x['a'] for x in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
result = reduce(lambda x, y: x | y, sets)

comprehension/assignment

在 Python3.8 中,您也可以使用赋值表达式代替 reduce

result = set()
result = [(result := (result | x['a'])) for x in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]]

使用 for 循环

只是,你知道,把它写出来。

result = set()
for element in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]:
    result |= element['a']