减少字典值的并集会产生意外结果

时间:2018-08-01 10:04:59

标签: python python-2.7 dictionary lambda functools

我想对所有字典值(在这种情况下是集合)进行并集。如果输入列表中只有两个字典,我只会得到预期的结果。

输入列表中的

两个词典产生预期的结果:

>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}])
set([1, 2, 3, 4])
输入列表中的

三个词典会产生TypeError。

预期结果: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])}

空的输入列表也会产生不同的TypeError。

预期结果: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

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

3 个答案:

答案 0 :(得分:2)

TLDR:

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

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

您必须先从map dictset,然后然后 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
    • resultiterable1.)的第一个元素
      • function从未被调用;其返回和输入类型无关紧要
  2. iterable具有两个元素,并且未设置start
    • resultiterable1.
    • 第一个元素第一个元素function元素(next)上调用
    • 2.
      • function永远不会收到自己的结果;其返回类型毫无意义
  3. iterable具有两个以上的元素,并且未设置start
    • resultiterable1.)的第一个元素
    • 第一个元素function元素(next)上调用
    • 2.
    • 上一个结果function元素(next上调用
    • 2.
      • function收到自己的结果;其返回类型和输入类型必须匹配
  4. iterable空或不为空,并且已设置start
    • 如果startiterable的第一个元素,则与上述相同
  5. iterable并且未设置start
    • result无法设置并且引发TypeError1.

在您的情况下,即:

  • 两个词典是2.,并且可以正常工作。
  • 三个字典是3.,并且对不兼容的输入和返回类型感到扼制。
  • 一个空的输入列表是5,并且对缺失的输入失败-如预期。

如何代替

地图/缩小

您的reduce实际上一次在做两件事:分别转换/提取每个 元素,然后合并两个结果。这是经典的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)

当然,您也可以直接嵌套这两个表达式。

理解/减少

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

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

理解/分配

在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']

答案 1 :(得分:1)

传递给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异常,您只需要向{}提供一个初始值作为第三个参数,在您的情况下,它应该是一个空集:after,但是您只需首先需要放弃将字典列表传递给它的想法,而是将集合列表传递给它的想法。

答案 2 :(得分:1)

find('div')反复工作,它将在序列项之间应用归约聚合。例如,给定元素reduceij以及函数k,它将处理foo

在您的示例中,foo(foo(i, j), k)可以正常工作,给出一个foo(i, j),但是外部调用失败,因为结果是set,没有键set 。后台中的语法'a'调用[],这就是为什么您看到与此方法有关的错误的原因。

您能做什么?

一个小技巧是让您的函数输出字典,然后直接访问它的唯一值。这样可以确保您的函数始终输出带有键__getitem__的字典。

'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}