代码中的Zipf定律

统计语言学中有一个Zipf定律,讲的是在自然语言的语料库里,一个单词出现的频率与它在频率表里的排名成反比。

根据Wikitionary上的一份资料,英文中出现频率最高的10个单词是这样的:

排名	词	计数
1	the	56271872
2	of	33950064
3	and	29944184
4	to	25956096
5	in	17420636
6	I	11764797
7	that	11073318
8	was	10078245
9	his	8799755
10	he	8397205

如果把英文中出现频率最高的100个单词按出现频率以及排名画成直方图,可以得到下面的图像:

Wiktionary frequency list

我们的确可以用一条曲线来拟合这个排名数据,如下:

Wiktionary frequency list 2

这条曲线的方程是 y = ax^b 的形式,也就是一个幂函数曲线。

事实上,Zipf定律中的“反比”即是 y = a / x,也即上面 b = -1 时的情况。也即Zipf定律其实也是一种幂律。

这是一个实验定律,而非理论定律,但是的确在很多现象中可以被观察到。比如不仅是自然语言,我们平常写的代码中也存在这个定律,就像淘宝的前端框架KISSY中。

我们来写一个 Python 脚本,统计一下 KISSY 所有 js 文件中的词频。代码如下:

# -*- coding: utf-8 -*-
#
# author: oldj
# blog: https://oldj.net
# email: oldj.wu@gmail.com
#

u"""Count word frequency in codes.

Usage:
    python cf.py --folder=folder_path --ext=filename_extension

Example:
    python cf.py --folder=/Users/wu/ozcode/kissy/src --ext=js

"""

import os
import sys
import re

ROOT = "/Users/wu/ozcode/kissy/src"
#ROOT = "/Users/wu/studio/tb-assets"


def getWords(content):

    return re.findall(r"w+", content)


def dealFile(path, words):

    c = open(path).read()
    fwords = getWords(c)

    for w in fwords:
        if w not in words:
            words[w] = 0
        words[w] += 1


def walk(root_path, ext):

    words = {}
    count = 0
    for root, dirs, files in os.walk(root_path):
        for fn in files:
            if fn.lower().endswith(ext):
                count += 1
                print("%d. %s" % (count, fn))
                dealFile(os.path.join(root, fn), words)

    save(ext, words)
    return count



def save(ext, words):
    kws = words.items()
    kws.sort(key=lambda a: -a[1])

    fn = "s_%s.txt" % ext
    f = open(fn, "w")
    for k, v in kws:
        ln = "%st%d" % (k, v)
#        print(ln)
        f.write("%sn" % ln)
    f.close()
    print("-" * 50)
    print("output -> %s" % os.path.abspath(fn))


def main(folder, ext):
    counts = []
    c = walk(root_path=folder, ext=ext)
    counts.append(c)

if __name__ == "__main__":
    folder = None
    ext = None
    argvs = sys.argv[1:]

    for kv in argvs:
        k, _, v = kv.partition("=")
        if k == "--folder":
            folder = v
        elif k == "--ext":
            ext = v

    if folder and ext:
        main(folder, ext)
    else:
        print(__doc__.replace("cf.py", __file__))

使用方法为:

python cf.py --folder=folder_path --ext=filename_extension

当然,要将上面的 folder_path 和 filename_extension 替换为你真实的内容,比如:

python cf.py --folder=/Users/wu/ozcode/kissy/src --ext=js

统计KISSY源码的src目录下的所有 .js 文件,找出所有匹配正则 w+ 的词,并按照词频从大到小排列,可以得到一个列表,其中前10项是这样的:

排名	词	出现次数
1	function	8437
2	var	6312
3	self	6120
4	if	5473
5	0	4525
6	return	4242
7	1	4057
8	S	3751
9	this	3452
10	expect	2947

当然,KISSY的开发者非常勤奋,更新很快,你最后得到的词频和我上面的可能会有一些不同,但比例应该是大致一致的。把前100项画成直方图,看起来是这样的:

Kissy *.js 词频

这些数字也与一条幂函数曲线吻合得非常好:

Kissy wf 2

除了js,KISSY中所有CSS文件的词频也符合同样的分布。我甚至还统计了一下工作环境下SVN上的所有js/css文件(这些文件出自数百名工程师之手)中的词频,发现这个规律仍然是存在的。

3 Replies to “代码中的Zipf定律”

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s