减少字典值的并集会产生意想不到的结果
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 函数将 x
和 y
都视为字典,并尝试通过键 'a'
获取每个项,而集合没有。
至于TypeError: reduce() of empty sequence with no initial value
异常,你只需要提供reduce
一个初始值作为第三个参数,在你的情况下应该是一个空集{}
,但是你只需要首先放弃向它传递一个字典列表的想法,而是向它传递一个集合列表。
reduce
迭代工作,它将对序列的项目应用减少聚合。例如,给定元素 i
、j
和 k
,连同函数 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 类型不是有效输入类型的问题因此从未遇到过。
您必须先 map
从 dict
到 set
, 然后 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
这导致了几种情况:
iterable
有 一个元素 并且 start
未设置
result
是 iterable
(1.
) 的第一个元素
function
从未被调用;它的 return 和输入类型无关紧要
iterable
有 两个元素 并且 start
未设置
result
是iterable
(1.
) 的第一个元素
function
在 第一个元素 和 next
元素上调用 (2.
)
function
永远不会收到自己的结果;它的 return 类型没有意义
iterable
有 两个以上的元素 并且 start
未设置
result
是 iterable
(1.
) 的 第一个元素
function
在 第一个元素 和 next
元素上调用 (2.
)
function
在 上一个结果 和 next
元素上调用 (2.
)
function
收到自己的结果;它的 return 类型和输入类型必须匹配
iterable
为空或不为空且设置start
- 如果
start
是 iterable
的第一个元素,则与上面相同
iterable
为 空 且 start
未设置
无法设置 result
并引发 TypeError
(1.
)
你的情况是:
- Two dictionaries 是 2. 并且按预期工作。
- 三个词典 是 3. 并且在不兼容的输入和 return 类型上阻塞。
- 一个空的输入列表 是 5. 并且在缺少输入时失败 - 正如预期的那样。
如何代替
map/reduce
你的 reduce
实际上是同时做两件事:它 converts/extracts 每个 元素单独,然后合并两个结果。这是一个经典的 map/reduce 任务:每个元素一个,所有元素一个。
您可以使用 map
和 reduce
内置函数将其直接拆分为两个单独的操作:
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']
我想合并所有字典值,在本例中是集合。如果输入列表中恰好有两个词典,我只会得到预期的结果。
输入列表中的两个词典产生了预期的结果:
>>> 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 函数将 x
和 y
都视为字典,并尝试通过键 'a'
获取每个项,而集合没有。
至于TypeError: reduce() of empty sequence with no initial value
异常,你只需要提供reduce
一个初始值作为第三个参数,在你的情况下应该是一个空集{}
,但是你只需要首先放弃向它传递一个字典列表的想法,而是向它传递一个集合列表。
reduce
迭代工作,它将对序列的项目应用减少聚合。例如,给定元素 i
、j
和 k
,连同函数 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 类型不是有效输入类型的问题因此从未遇到过。
您必须先 map
从 dict
到 set
, 然后 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
这导致了几种情况:
iterable
有 一个元素 并且start
未设置result
是iterable
(1.
) 的第一个元素function
从未被调用;它的 return 和输入类型无关紧要
iterable
有 两个元素 并且start
未设置result
是iterable
(1.
) 的第一个元素
function
在 第一个元素 和next
元素上调用 (2.
)function
永远不会收到自己的结果;它的 return 类型没有意义
iterable
有 两个以上的元素 并且start
未设置result
是iterable
(1.
) 的 第一个元素
function
在 第一个元素 和next
元素上调用 (2.
)function
在 上一个结果 和next
元素上调用 (2.
)function
收到自己的结果;它的 return 类型和输入类型必须匹配
iterable
为空或不为空且设置start
- 如果
start
是iterable
的第一个元素,则与上面相同
- 如果
iterable
为 空 且start
未设置-
无法设置
result
并引发TypeError
(1.
)
你的情况是:
- Two dictionaries 是 2. 并且按预期工作。
- 三个词典 是 3. 并且在不兼容的输入和 return 类型上阻塞。
- 一个空的输入列表 是 5. 并且在缺少输入时失败 - 正如预期的那样。
如何代替
map/reduce
你的 reduce
实际上是同时做两件事:它 converts/extracts 每个 元素单独,然后合并两个结果。这是一个经典的 map/reduce 任务:每个元素一个,所有元素一个。
您可以使用 map
和 reduce
内置函数将其直接拆分为两个单独的操作:
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']