[英]Python Counter with defaultdict(int) behaviour
Consider the following piece of code: 考虑以下代码:
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))
I obtain the following pair_dict
: 我获得了以下
pair_dict
:
{'a': ('blue', 3),
'b': ('green', 2),
'c': ('yellow', 1),
'd': ('red', 1),
'e': ('black',)}
In my real case application I need the values in my pair_dict
to be pairs, so pair_dict["e"]
should be ('black', 0)
. 在我的实际应用程序中,我需要
pair_dict
的值为pair,因此pair_dict["e"]
应为('black', 0)
。
It would be very convenient if I could have a class that extends Counter
with the nice behaviour of a defaultdict(int)
. 如果我有一个使用
defaultdict(int)
的良好行为扩展Counter
的类将非常方便。
Is this easily done? 这很容易吗?
I naïvely tried the following: 我天真地尝试了以下方法:
class DefaultCounter(defaultdict, Counter):
pass
pair_dict = merge_with(tuple, my_dict, DefaultCounter(my_list))
But I get TypeError: first argument must be callable or None
. 但是我得到
TypeError: first argument must be callable or None
。 I guess this is due to the fact that defaultdict
expects a factory function. 我想这是因为
defaultdict
需要一个工厂函数。
So I tried the following: 所以我尝试了以下方法:
pair_dict = merge_with(tuple, my_dict, DefaultCounter(int, my_list))
This results in ValueError: dictionary update sequence element #0 has length 1; 2 is required
这导致
ValueError: dictionary update sequence element #0 has length 1; 2 is required
ValueError: dictionary update sequence element #0 has length 1; 2 is required
. ValueError: dictionary update sequence element #0 has length 1; 2 is required
。
I also tried class DefaultCounter(Counter, defaultdict)
but this does not have the desired effect: pair_dict["e"]
is still ('black',)
. 我也尝试过
class DefaultCounter(Counter, defaultdict)
但这没有达到预期的效果: pair_dict["e"]
仍然是('black',)
。
Probably something else should be done in the definition of the class. 可能在类的定义中还应该做其他事情。
So I tried to adapt this answer : 所以我试着调整这个答案 :
class DefaultCounter(Counter):
def __missing__(self, key):
self[key] = 0
return 0
But this also doesn't have the desired effect ( pair_dict["e"]
still misses a second element). 但这也没有达到预期的效果(
pair_dict["e"]
仍然错过了第二个元素)。
Counter
already behaves as defaultdict(int)
, but merge_with
does not trigger this behaviour. Counter
已经表现为defaultdict(int)
,但merge_with
不会触发此行为。 As suggested in the comments, a Counter
already has the desired behaviour: 正如评论中所建议的那样,
Counter
已经具有所需的行为:
my_counts = Counter(my_list)
assert my_counts["e"] == 0
The issue may actually lie in the way merge_with
works: It doesn't trigger the desired defaultdict
behaviour. 问题实际上可能在于
merge_with
工作方式:它不会触发所需的defaultdict
行为。
This is verified by the following test using a defaultdict
instead of a Counter
: 以下测试使用
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',)
One must therefore ensure that all keys have been created in the Counter
before merging with the other dict, for instance using this trick . 因此,必须确保在与其他字典合并之前已在
Counter
创建了所有键,例如使用此技巧 。
Not what you asked for, but 1 option would be to initialise a Counter
with the dict
keys and then update it with the list
and finally use a dict
comprehension to get your desired output: 不是你要求的,但是一个选项是用
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)}
Not a direct answer, but some other ways to approach this porblem. 不是直接的答案,而是一些其他方法来处理这个问题。
join
: 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))}
and merge_with
: 和
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)))
A dictionary comprehension should do similar to list comprehension. 字典理解应该与列表理解类似。
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)}
Combining this answer with the use of merge_with
, I came up with the following solution: 结合这个答案和
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.