在字典中添加新键并增加现有值

8 投票
5 回答
4596 浏览
提问于 2025-04-16 06:11

我正在处理一个CSV文件,并统计第四列中独特的值。目前我已经用三种方法写了这个程序。第一种方法是用“如果这个键在字典里”,第二种方法是捕捉键错误,第三种方法是使用“默认字典”。举个例子(其中x[3]是文件中的值,而“a”是一个字典):

第一种方法:

if x[3] in a:
    a[x[3]] += 1
else:
    a[x[3]] = 1

第二种方法:

try:
    b[x[3]] += 1
except KeyError:
    b[x[3]] = 1

第三种方法:

from collections import defaultdict
c = defaultdict(int)
c[x[3]] += 1

我想问的是:哪种方法更高效……更简洁……更好……等等。或者有没有更好的方法。虽然这三种方法都能工作并给出相同的结果,但我想借此机会向大家请教一下,学习一下。

谢谢 -

5 个回答

1
from collections import Counter
Counter(a)

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

6

你问哪个更高效。假设你是在说执行速度:如果你的数据量小,那就无所谓。如果数据量大且比较常见,那么“已经存在”的情况会比“字典中没有”的情况发生得多。这一点可以解释一些结果。

下面有一些代码,可以和timeit模块一起使用,来测试速度,而不需要读取文件的开销。我还自作主张添加了一个第五种方法,这个方法在至少1.5.2版本的Python上都能运行,效果还不错。

from collections import defaultdict, Counter

def tally0(iterable):
    # DOESN'T WORK -- common base case for timing
    d = {}
    for item in iterable:
        d[item] = 1
    return d

def tally1(iterable):
    d = {}
    for item in iterable:
        if item in d:
            d[item] += 1
        else:
            d[item] = 1
    return d

def tally2(iterable):
    d = {}
    for item in iterable:
        try:
            d[item] += 1
        except KeyError:
            d[item] = 1
    return d

def tally3(iterable):
    d = defaultdict(int)
    for item in iterable:
        d[item] += 1

def tally4(iterable):
    d = Counter()
    for item in iterable:
        d[item] += 1

def tally5(iterable):
    d = {}
    dg = d.get
    for item in iterable:
        d[item] = dg(item, 0) + 1
    return d

典型的运行结果(在Windows XP的“命令提示符”窗口中):

prompt>\python27\python -mtimeit -s"t=1000*'now is the winter of our discontent made glorious summer by this son of york';import tally_bench as tb" "tb.tally1(t)"
10 loops, best of 3: 29.5 msec per loop

这里是结果(每次循环的毫秒数):

0 base case   13.6
1 if k in d   29.5
2 try/except  26.1
3 defaultdict 23.4
4 Counter     79.4
5 d.get(k, 0) 29.2

另一个计时试验:

prompt>\python27\python -mtimeit -s"from collections import defaultdict;d=defaultdict(int)" "d[1]+=1"
1000000 loops, best of 3: 0.309 usec per loop

prompt>\python27\python -mtimeit -s"from collections import Counter;d=Counter()" "d[1]+=1"
1000000 loops, best of 3: 1.02 usec per loop

Counter的速度可能是因为它部分是用Python代码实现的,而defaultdict完全是用C语言实现的(至少在2.7版本中是这样)。

注意,Counter()并不仅仅是defaultdict(int)的“语法糖”——它实现了一个完整的bag,也就是multiset对象——具体细节可以查看文档;如果你需要一些复杂的后处理,这可能会帮你省去重新发明轮子的麻烦。如果你只是想计数,使用defaultdict就可以了。

更新:回应@Steven Rumbalski的问题:“我很好奇,如果把可迭代对象放进Counter构造函数中会发生什么:d = Counter(iterable)?(我用的是Python 2.6,无法测试。)”

tally6:只是做了d = Count(iterable); return d,耗时60.0毫秒。

你可以查看源代码(在SVN库中的collections.py)……这是我的Python27\Lib\collections.pyiterable不是映射实例时的处理:

            self_get = self.get
            for elem in iterable:
                self[elem] = self_get(elem, 0) + 1

见过这个代码吗?为了调用在Python 1.5.2中可以运行的代码,真是费了不少劲:-O

6

可以使用 collections.Counter。这个 Counter 就像是 defaultdict(int) 的一种简化写法,但它的一个很酷的地方是,它在创建的时候可以直接接受一个可迭代的对象,这样就省去了多一步的操作(我猜你上面提到的所有例子都是放在一个循环里的)。

from collections import Counter
count = Counter(x[3] for x in my_csv_reader)

collections.Counter 出现之前,collections.defaultdict 是处理这个任务最常用的方法,所以如果你使用的版本低于 2.7,就用 defaultdict 吧。

from collections import defaultdict
count = defaultdict(int)
for x in my_csv_reader:
    count[x[3]] += 1

撰写回答