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



我想将所有字典值联合起来,在本例中为集合。只有当输入列表中正好有两个字典时,我才会得到预期的结果。

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

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

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

TLDR:

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

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

您必须先从dictmapset然后reduceset

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;其返回和输入类型无关紧要
  2. iterable有两个元素,未设置start
    • resultiterable的第一个元素(1.)
    • function第一个元素next元素(2.) 上调用
      • function永远不会收到自己的结果;它的返回类型毫无意义
  3. iterable具有两个以上的元素,并且未设置start
    • resultiterable的第一个元素(1.)
    • function第一个元素next元素上调用(2.)
    • function上一个结果next元素上调用 (2.)
      • function接收自己的结果;其返回类型和输入类型必须匹配
  4. iterable空或不为空,并设置start
    • 同上,如果startiterable的第一个元素
  5. iterable为空且未设置start
    • 无法设置result并引发TypeError(1.)

在您的情况下,即:

  • 两个词典是 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']
传递给reduce

的函数的输出必须与迭代器中的项具有相同的类型,以便它可以使用相同的函数不断聚合项值。

在你的例子中,lambda x, y: x['a'] | y['a']的输出是一个集合{1, 2, 3, 4},所以当reduce尝试用{1, 2, 3, 4}聚合第三个{'a': {5, 6}}时,它会失败,因为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,没有密钥'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}

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

def foo(x, y):
return {'a': x['a'] | y['a']}
L = [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]
reduce(foo, L)['a']

相关内容

  • 没有找到相关文章

最新更新