8

我正在处理一个 CSV 文件并计算第 4 列的唯一值。到目前为止,我已经对这三种方式进行了编码。一个使用“if key in dictionary”,第二个捕获 KeyError,第三个使用“DefaultDictionary”。例如(其中 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

我的问题是:哪种方式更有效……更清洁……更好……等等。或者有没有更好的方法。两种方式都有效并给出相同的答案,但我认为我会利用蜂巢思维作为学习案例。

谢谢 -

4

5 回答 5

6

使用collections.Counter. Counter是 for 的语法糖defaultdict(int),但它很酷的是它在构造函数中接受一个可迭代的,从而节省了一个额外的步骤(我假设你上面的所有示例都包装在一个 for 循环中。)

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
于 2010-10-27T18:42:50.763 回答
6

你问哪个更有效率。假设您正在谈论执行速度:如果您的数据很小,那没关系。如果它是大而典型的,“已经存在”的情况将比“不在字典中”的情况发生得更频繁。这一观察解释了一些结果。

下面是一些代码,可与timeit模块一起使用以探索速度而无需文件读取开销。我冒昧地添加了第 5 种方法,该方法并非没有竞争力,并且可以在至少 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)——它实现了一个完整的bagakamultiset对象——有关详细信息,请参阅文档;如果您需要一些花哨的后期处理,它们可能会让您免于重新发明轮子。如果您只想计算事物,请使用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

于 2010-10-27T20:37:34.677 回答
1
from collections import Counter
Counter(a)
于 2010-10-27T18:34:44.743 回答
0

由于您无权访问Counter,因此最好的选择是您的第三种方法。它更干净,更容易阅读。此外,它没有前两种方法所具有的永久测试(和分支),这使得它更有效。

于 2010-10-27T18:39:14.297 回答
0

使用setdefault.

a[x[3]] = a.setdefault(x[3], 0) + 1

setdefault获取指定键的值(x[3]在这种情况下),或者如果它不存在,则获取指定的值(0在这种情况下)。

于 2010-10-27T23:00:13.600 回答