Python 具有 defaultdict(int) 行为的计数器
Python Counter with defaultdict(int) behaviour
考虑以下代码:
from collections import Counter
from cytoolz import merge_with
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {"a" : "blue", "b" : "green", "c" : "yellow", "d" : "red", "e" : "black"}
pair_dict = merge_with(tuple, my_dict, Counter(my_list))
我得到以下 pair_dict
:
{'a': ('blue', 3),
'b': ('green', 2),
'c': ('yellow', 1),
'd': ('red', 1),
'e': ('black',)}
在我的真实案例应用程序中,我需要 pair_dict
中的值成对,因此 pair_dict["e"]
应该是 ('black', 0)
.
如果我可以使用 a class 扩展 Counter
并具有 defaultdict(int)
的良好行为,那将非常方便。
这很容易做到吗?
我天真地尝试了以下方法:
class DefaultCounter(defaultdict, Counter):
pass
pair_dict = merge_with(tuple, my_dict, DefaultCounter(my_list))
但我得到 TypeError: first argument must be callable or None
。我猜这是因为 defaultdict
需要一个工厂函数。
所以我尝试了以下方法:
pair_dict = merge_with(tuple, my_dict, DefaultCounter(int, my_list))
这导致 ValueError: dictionary update sequence element #0 has length 1; 2 is required
。
我也试过 class DefaultCounter(Counter, defaultdict)
但这没有达到预期的效果:pair_dict["e"]
仍然是 ('black',)
。
可能应该在 class 的定义中做一些其他事情。
所以我试着适应:
class DefaultCounter(Counter):
def __missing__(self, key):
self[key] = 0
return 0
但这也没有达到预期的效果(pair_dict["e"]
仍然缺少第二个元素)。
编辑:Counter
的行为已与 defaultdict(int)
相同,但 merge_with
不会触发此行为。
如评论中所建议,Counter
已经具有所需的行为:
my_counts = Counter(my_list)
assert my_counts["e"] == 0
问题实际上可能在于 merge_with
的工作方式:它不会触发所需的 defaultdict
行为。
以下测试使用 defaultdict
而不是 Counter
验证了这一点:
from collections import defaultdict
my_counts = defaultdict(int)
for letter in my_list:
my_counts[letter] += 1
pair_dict = merge_with(tuple, my_dict, my_counts)
assert pair_dict["e"] == ('black',)
因此,在与另一个字典合并之前,必须确保在 Counter
中创建了所有键,例如使用 .
字典理解应该类似于列表理解。
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {"a" : "blue", "b" : "green",
"c" : "yellow", "d" : "red", "e" : "black"}
res={key:(my_dict[k], my_list.count(k)) for k in my_dict}
res
# {'a': ('blue', 3), 'b': ('green', 2), 'c': ('yellow', 1),
# 'd': ('red', 1), 'e': ('black', 0)}
不是您要求的,但第一个选项是使用 dict
键初始化 Counter
,然后使用 list
更新它,最后使用 dict
理解以获得所需的输出:
>>> c = Counter(my_dict.keys())
>>> c.update(my_list)
>>> {k:(my_dict[k],v-1) for k,v in c.items()}
{'a': ('blue', 3), 'b': ('green', 2), 'c': ('yellow', 1), 'd': ('red', 1), 'e': ('black', 0)}
结合使用和merge_with
,我想出了以下解决方案:
from collections import Counter
from cytoolz import merge_with
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {
"a" : "blue", "b" : "green", "c" : "yellow", "d" : "red", "e" : "black"}
my_counts = Counter(my_dict.keys()).update(my_list)
pair_dict = merge_with(
tuple, my_dict,
{k : v - 1 for (k, v) in my_counter.items()})
assert pair_dict["e"] == ('black', 0)
不是直接的答案,而是解决这个问题的一些其他方法。 join
:
from toolz import first, join
{k: (v, c) for (_, c), (k, v) in join(
leftkey=first, leftseq=Counter(my_list).items(),
rightkey=first, rightseq=my_dict.items(),
left_default=(None, 0))}
和merge_with
:
from toolz import *
merge_with(
tuple,
my_dict,
reduce(
lambda acc, x: update_in(acc, x, identity, 0),
my_dict.keys(),
Counter(my_list)))
考虑以下代码:
from collections import Counter
from cytoolz import merge_with
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {"a" : "blue", "b" : "green", "c" : "yellow", "d" : "red", "e" : "black"}
pair_dict = merge_with(tuple, my_dict, Counter(my_list))
我得到以下 pair_dict
:
{'a': ('blue', 3),
'b': ('green', 2),
'c': ('yellow', 1),
'd': ('red', 1),
'e': ('black',)}
在我的真实案例应用程序中,我需要 pair_dict
中的值成对,因此 pair_dict["e"]
应该是 ('black', 0)
.
如果我可以使用 a class 扩展 Counter
并具有 defaultdict(int)
的良好行为,那将非常方便。
这很容易做到吗?
我天真地尝试了以下方法:
class DefaultCounter(defaultdict, Counter):
pass
pair_dict = merge_with(tuple, my_dict, DefaultCounter(my_list))
但我得到 TypeError: first argument must be callable or None
。我猜这是因为 defaultdict
需要一个工厂函数。
所以我尝试了以下方法:
pair_dict = merge_with(tuple, my_dict, DefaultCounter(int, my_list))
这导致 ValueError: dictionary update sequence element #0 has length 1; 2 is required
。
我也试过 class DefaultCounter(Counter, defaultdict)
但这没有达到预期的效果:pair_dict["e"]
仍然是 ('black',)
。
可能应该在 class 的定义中做一些其他事情。
所以我试着适应
class DefaultCounter(Counter):
def __missing__(self, key):
self[key] = 0
return 0
但这也没有达到预期的效果(pair_dict["e"]
仍然缺少第二个元素)。
编辑:Counter
的行为已与 defaultdict(int)
相同,但 merge_with
不会触发此行为。
如评论中所建议,Counter
已经具有所需的行为:
my_counts = Counter(my_list)
assert my_counts["e"] == 0
问题实际上可能在于 merge_with
的工作方式:它不会触发所需的 defaultdict
行为。
以下测试使用 defaultdict
而不是 Counter
验证了这一点:
from collections import defaultdict
my_counts = defaultdict(int)
for letter in my_list:
my_counts[letter] += 1
pair_dict = merge_with(tuple, my_dict, my_counts)
assert pair_dict["e"] == ('black',)
因此,在与另一个字典合并之前,必须确保在 Counter
中创建了所有键,例如使用
字典理解应该类似于列表理解。
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {"a" : "blue", "b" : "green",
"c" : "yellow", "d" : "red", "e" : "black"}
res={key:(my_dict[k], my_list.count(k)) for k in my_dict}
res
# {'a': ('blue', 3), 'b': ('green', 2), 'c': ('yellow', 1),
# 'd': ('red', 1), 'e': ('black', 0)}
不是您要求的,但第一个选项是使用 dict
键初始化 Counter
,然后使用 list
更新它,最后使用 dict
理解以获得所需的输出:
>>> c = Counter(my_dict.keys())
>>> c.update(my_list)
>>> {k:(my_dict[k],v-1) for k,v in c.items()}
{'a': ('blue', 3), 'b': ('green', 2), 'c': ('yellow', 1), 'd': ('red', 1), 'e': ('black', 0)}
结合使用merge_with
,我想出了以下解决方案:
from collections import Counter
from cytoolz import merge_with
my_list = ["a", "b", "a", "a", "c", "d", "b"]
my_dict = {
"a" : "blue", "b" : "green", "c" : "yellow", "d" : "red", "e" : "black"}
my_counts = Counter(my_dict.keys()).update(my_list)
pair_dict = merge_with(
tuple, my_dict,
{k : v - 1 for (k, v) in my_counter.items()})
assert pair_dict["e"] == ('black', 0)
不是直接的答案,而是解决这个问题的一些其他方法。 join
:
from toolz import first, join
{k: (v, c) for (_, c), (k, v) in join(
leftkey=first, leftseq=Counter(my_list).items(),
rightkey=first, rightseq=my_dict.items(),
left_default=(None, 0))}
和merge_with
:
from toolz import *
merge_with(
tuple,
my_dict,
reduce(
lambda acc, x: update_in(acc, x, identity, 0),
my_dict.keys(),
Counter(my_list)))