Python 标准库精华: collections.Counter

这里有个好玩的问题。给定一个不同国家的电话号码库,确定每个国家中最普遍的显示格式,并使用它重新格式化这个国家的任意电话号码。例如,如果我们的数据语料库中的大多数美国电话号码都写成xxx-xxx -xxxx,那么字符串(206)1234567就应该转换为206-123-4567。

为了简单起见,我们假设所有的电话号码都是不包含国别前缀的。

转换

实际的转换并不是这篇文章的主题,但是为了完整起见,这里有两个补充的函数,一个确定模式,另一个根据模式格式化电话号码:

Python 标准库精华: collections.Counter_第1张图片

 (为了清晰起见省略了错误处理。)

简单的解决方案

通常的方法是使用类似于字典的结构,它将模式作为键保存,并计算模式在源数据库中出现的频率,因此结果有点类似于这样:

Python 标准库精华: collections.Counter_第2张图片

(不要介意数字,这是一个小数据库!)

据我所知,在这种情况下,人们通常会使用defaultdict。它在第一次访问时会自动初始化不存在的键值,你每次只需要执行result[key] += 1   即可。在我们的例子中,我们需要一个更复杂的东西: 一个defaultdict  用于存储country,它将使用嵌套的defaultdict   来初始化计数的键。总的来说它看起来可能是这样的:

Python 标准库精华: collections.Counter_第3张图片

Counter(计数器)

这里有一个经验法则: 当你看到 defaultdict(int)  时,你可以用一个Counter 替换它。它可以做 defaultdict 所能做的一切,加上一些方便的技巧,比如  .most_common() 方法,我们可以使用它来代替前面示例中笨拙的max() 表达式。

Python 标准库精华: collections.Counter_第4张图片

平铺和函数

我再为你提供另一个解决方案,那就是更多地使用 Counter 魔法。我还将用函数的方法替换 for 循环,这样一来 for 循环就不是非要不可:-)

下面是代码,然后是解释:

Python 标准库精华: collections.Counter_第5张图片

看吧,没有循环了!

让我来解释一下这里发生的一切。

事实上,Counter 可以处理一个平铺式序列的东西,然后一口气数完:

在我们的例子中,要想实现这一点,我们必须用平面数据结构替换嵌套数据结构。这可以通过简单地将键值粘合在一个元组中,       并用等效的 stats[(country, pattern)] 替换 stats[country][ pattern] (你可以对任意数量的键执行此操作)来实现。

表达式(country, get_pattern(phone)) for country, phone in db 看起来像一个没有方括号的列表推导。实际上,它是一个生成器表达式,你可以遍历它而不需要在内存中构造一个实际的列表。它通常用括号括起来( ... ),但是当它是一个函数调用中的唯一参数时,你可以省略它们,并避免使用像 Counter((..)) 这样丑陋的重复。

stats.most_common() 在没有参数的情况下会将结构的全部内容作为一个排好序的(key, count) 列表返回:

Python 标准库精华: collections.Counter_第6张图片

它是按计数排序的,不考虑country,但是我们不关心这个,你稍后会看到这些,以及为什么我们需要反转它。

最后的表达式是一个字典推导,它遍历已排序的列表,使用(country, pattern),_ 模拟一个键对的结构,将列表中嵌套的键对分解为单个变量。_  是我们不使用的东西的惯例名称,在我们的例子中就是count。因此,我们实际转换成的最终字典序列是这样的,country变成键,模式变成值:

Python 标准库精华: collections.Counter_第7张图片

重要的是:具有相同键的后续值会替换字典中已经存在的值。这就是为什么我们需要反转列表的原因,所以我们就用计数较大的模式替换最不常见的模式。这也是为什么我们不关心country之间的混合,因为它们不会相互影响。

诚然,最后一点是最不明显的。

这是不是更好一点?

我并不在乎,你可以自己判断:-)  我只是想实际的展示一下collections.Counter!

你可能感兴趣的:(python)