从 Counter 中删除最不常见的元素

removing least common elements from Counter

是否有 "faster way" 从 Counter 中删除值小于特定值的键值对?

我做了以下事情:

counter_dict = {k:v for k, v in counter_dict.items() if v > 5}

当前代码的主要问题是对 .items 的调用,它将创建所有项目的列表:

一种优化可能是使用 Counter.iteritems 而不是 .items,以节省创建列表并再次遍历它的代价。

>>> from collections import Counter
>>> cnt = Counter("asbdasdbasdbadaasasdasadsa")
>>> {k:v for k,v in cnt.iteritems() if v > 5}
{'a': 10, 's': 7, 'd': 6}

另一个优化可能是不调用 .items 方法,而是迭代键并使用键访问值:

>>> from collections import Counter
>>> cnt = Counter("asbdasdbasdbadaasasdasadsa")
>>> {k:cnt[k] for k in cnt if cnt[k] > 5}
{'a': 10, 's': 7, 'd': 6}

如果我们尝试 测量 %timeit 在 ipython 中的差异,使用带有你提到的 if 条件 iteritems 获胜 :

In [1]: import random

In [2]: from collections import Counter

In [3]: MILLION = 10**6

In [4]: cnt = Counter(random.randint(0, MILLION) for _ in xrange(MILLION))

In [5]: %timeit {k:v for k, v in cnt.iteritems() if v < 5}
10 loops, best of 3: 140 ms per loop

In [6]: %timeit {k:v for k, v in cnt.items() if v**2 < 5}
1 loops, best of 3: 290 ms per loop

In [7]: %timeit {k:cnt[k] for k in cnt if cnt[k] < 5}
1 loops, best of 3: 272 ms per loop

随着条件的变化:

In [8]: %timeit {k:v for k, v in cnt.iteritems() if v > 5}
10 loops, best of 3: 87 ms per loop

In [9]: %timeit {k:v for k, v in cnt.items() if v > 5}
1 loops, best of 3: 186 ms per loop

In [10]: %timeit {k:cnt[k] for k in cnt if cnt[k] > 5}
10 loops, best of 3: 153 ms per loop

所以你最好不要每次都重新创建整个字典:

to_remove = set()
for key, value in counter_dict.viewitems():
   if value <= 5:
      to_remove.add(key)

for key in to_remove:
    del counter_dict[key]

将 "for" 语句展开为更多行并不一定意味着性能下降。虽然在这种情况下可能没有太大的性能提升,但内存消耗至少应该下降。

另一种选择是让您的 "counter_dict" 成为更智能的对象 知道当计数 <= 5 时不产生它的值 - 这将使这一步 "lazy".

一些事情(但不仅仅是 - 正确的事情是实施这个 使用 ABC 元类 - collections.MutableMapping

class MyDict(dict):
   def __init__(*args, **kw):
       self.threshold = None
       super(MyDict,self).__init__(*args, **kw)
   def __getitem__(self, key):
       value = super(MyDict, self).__getitem__(key)
       if self.threshold is None or key > self.threshold:
          return value
       raise ItemError
   # the same for __contains__ and other interesting methods

并且在应该开始过滤时更改字典中的 "threshold" 属性对象。这或多或少有点过头了——因为你的检查仍然会完成,只是稀释了时间——但也许在消费对象时,你处于 async/multithread 可以并行进行的工作负载——但如果你需要不同的阈值代码的不同部分,拥有它可能是一件好事。