Tuesday, January 27, 2009

據說俺的blog/space�面技術文章太多,導致進入者死傷無數

同時據說北京試行單雙日行車.
于是有兩套方案可以試用:
1. 單雙號寫技術與日常文章
2. 日常文章與技術文章交替

恩... 也是時候積澱一下blog的內容了, 不能蝦米東西都發.

------------------------------
本文原文發表于:
http://iveney.blogspot.com

Monday, January 26, 2009

各位春节快乐~~

各位来自地球与火星的各种人类与生物们,小刚在这里给你们拜年了 :)
走过路过, 记得留下您的祝福 :)

Thursday, January 22, 2009

井底之蛙

世界太大,生命太短。牛人��,菜�有我。
接�得越多,越��得自己是井底之蛙,越�感�自卑。

曾�有人说我牛,我说不。
于是被之冠以虚伪的``美称''。
我一向不�便代表人,但这时也不得不感叹到,
小朋友,看�你����世面,原�井底之蛙不止我一�。

Anyway,切�要把自卑�化��力,切忌在自卑中��。

------------------------------
本文原文�表于:
http://iveney.blogspot.com

Wednesday, January 21, 2009

�魔城要出�影版!!!

http://www.cnbeta.com/articles/75240.htm

NO.3《恶魔城》电影版


保罗 W.S 安得森(Paul W.S. Anderson)参与制作筹备多时的恶魔城电影改编版将迈入全力拍摄阶段。我们从该报导中也获悉电影制片方罗格电影(Rogue Pictures)希望对电影版的剧本进行一个全面的修整。在描述故事梗概之前,可以说看到原先的故事情节被改动了是十分令人高兴的。我们都非常喜欢 KONAMI的恶魔城系列游戏,当然希望电影改编版也能够让我们为之振奋。

评:Konami的恶魔城系列也是一个非常长命的游戏系列,不论是人物刻画还是背景设定,都充满了欧洲中世纪的奇幻风格,如果要翻拍成电影的话,个人感觉也许会走《凡赫辛》的路线(猜测,猜测而已》,灰色,冷兵器,这种感觉才合适吸血鬼的世界。

上映日期:2009年

Tuesday, January 20, 2009

大杂烩:GNU binutils strings/ 好看的man page / gcc 语言标准选项 / source-highlight 与 less 结合

strings filename:

from man page:

DESCRIPTION
       For  each  file  given,  GNU  strings  prints  the  printable character
       sequences that are at least 4 characters long (or the number given with
       the  options  below)  and are followed by an unprintable character.  By
       default, it only prints the strings from  the  initialized  and  loaded
       sections  of  object  files;  for  other  types of files, it prints the
       strings from the whole file.

       strings is mainly useful  for  determining  the  contents  of  non-text
       files.


---------------------------------------

export PAGER=most

方案一: woman

方案二:vim

function man() {  /usr/bin/man $* | col -b | /usr/bin/vim -R -c 'set ft=man
nomod nolist' - ; }

之后
man ls

方案三 : 定义less显示escape sequence的颜色:
http://wiki.clug.org.za/wiki/Colour_on_the_command_line#Colourful_manpages_.28RedHat_style.29
# For colourful man pages (CLUG-Wiki style)
export LESS_TERMCAP_mb=$'\E[01;31m'
export LESS_TERMCAP_md=$'\E[01;31m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_se=$'\E[0m'
export LESS_TERMCAP_so=$'\E[01;44;33m'
export LESS_TERMCAP_ue=$'\E[0m'
export LESS_TERMCAP_us=$'\E[01;32m'

---------------------------------------

使用
gcc -Wall -pedantic -ansi
可以启用许多警告和额外的检查以检验程序是否符合C语言标准。

---------------------------------------
source-highlight 与 less 结合

http://linuxtoy.org/archives/less-highlight.html

注意source-highlight版本太低则不支持default.lang(我的2.4.5就不支持,于是弄了个2.11)

Saturday, January 17, 2009

MSN換一個nb的頭像

from希伯來文。感覺這個符號很nb。

http://en.wikipedia.org/wiki/Aleph_number

------------------------------
本文原文發表于:
http://iveney.blogspot.com

Tuesday, January 13, 2009

生活就是要不停地改变自己的习惯.....

由www-> telnet bbs
由普通编辑器 -> vim
由adobe reader-> apvlv
由普通firefox+鼠标浏览-> vimperator

......orz

Monday, January 12, 2009

牛B:測試名字在圓周率前40億十進制位中排多少

est的blog得到的連接:http://initiative.yo2.cn/archives/636133

猛擊這裡進行測試:http://pi.nersc.gov/

iveney的ASCII value是:
0x69,0x76,0x65,0x6e,0x65,0x79

測試結果為:
search string = "iveney"
30-bit binary equivalent =   010011011000101011100010111001

search string found at binary index =  3558297688
binary pi    : 1100100001001101100010101110001011100111000101000100111101010110
binary string:         010011011000101011100010111001                          
character pi    : vm,vcg-bwzo,vhrnhiveneyxti.kifr.,udmm-
character string:                  iveney

也就是,把iveney用30位binary表示后,在pi裡面搜索它的第一次出現,得到結果的index為:3,558,297,688
在35億左右……

Friday, January 9, 2009

字符集与编码处理乱谈:C/C++ 中的处理、unicode与字体的关系、几种常用unicode编码的对比

预备知识:
1.系统里面有locale这个东西。比如我的系统就是LC_ALL=zh_CN.utf8

2.我们知道,一个文本文件是由一系列字符序列组成的。
这些字符又是属于某个字符集的。这个字符集可以用某种编码表示。
总而言之,某个cpp文件必须是有某种编码存储的。

3.C/C++里存储字符有两种方法。一是用"ANSI字节序列",即用char str[]这种byte的数组来存储。
因此字符的编码是编译时固定的,如:
一个用utf8编码的cpp文件:char str[]="阿"
则编译后,str的内容为:0xe9 0x98 0xbf,这就是'阿'对应的utf8编码。
如果用sizeof来求str的大小,可以得到4.显然上面已经有了3个byte来存储这个'阿'字。
而还有一个NULL(0)在str[3]的位置。因此是4.
如果用strlen来求str的长度,则可以得到3.

但是如果是用gbk编码的文件:char str[]="阿"
则编译后,str的内容为:0xb0 0xa2,这就是'阿'对应的gbk编码。
用sizeof和strlen,分别得到3与2.

使用文本编辑器保存文件时,如果选择"ANSI",则是使用当前的locale来进行对应的本地化编码存储。

以下转自:http://www.regexlab.com/zh/encoding.htm

理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:

  概念描述 举例
字符 人们使用的记号,抽象意义上的一个符号。 '1', '中', 'a', '$', '¥', ……
字节 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 0x01, 0x45, 0xFA, ……
ANSI
字符串
在内存中,如果"字符"是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串或者多字节字符串 "中文123"
(占7字节)
UNICODE
字符串
在内存中,如果"字符"是以在 UNICODE 中的序号存在的,那么我们称这种字符串为 UNICODE 字符串或者宽字节字符串 L"中文123"
(占10字节)

在C/C++里,mbs(MultiByte characterS,多字节字符) 和wcs(Wide CharacterS,宽字符)是两个完全distinct的概念。
mbs即是字节串,使用char 来存储,而wcs是unicode 字符串,使用wchar_t 来存储。
我们可以使用mbstowcs和wcstombs进行互相转换。参考:
http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_18.html#SEC310
要注意的是,必须先使用setlocale设定程序的区域。
但是,程序运行时也受到运行环境的限制,必须先用 locale -a 查看系统支持的locale,
系统支持的locale才可以放在setlocale里面作为参数,否则wcstombs会不成功。

一般来说,可以使用这样的方法处理:
读写文件时,使用mbs进行具体的编码存储(external representation);而在程序内部,则统一使用wcs进行字符串的处理(internal representation)。

C/C++中mbs/wcs的I/O
keyword: wprintf, wcslen, fwide,

在C/C++里,用wchar_t来存储一个unicode字符。
在GNU glibc的实现里,它总是4 bytes。与Unicode的UCS-4编码是对应的。
即wchar_t a='阿';
如果把a当作整数输出,则可以得到963f,这正是``阿'' 的Unicode codepoint.
(捎带一提,与UTF-8不同。UTF-32, UCS-4 与 Unicode codepoint是一一对应的编码,但是UTF-32是UCS-4的子集)

这里要注意一个叫fwide的东西。
根据man page,每一个STREAM都有一个属性,即它是面向字节的流还是面向宽字符的流,并且:

Once  a  stream  has  an orientation, it cannot be changed and persists until the stream is closed.

可以用fwide(stream,0)的返回值来判断流的orientation。但是使用这个语句可能本身就会更改流的orientation。
在输出任何东西到流之前,必须决定这个流的orientation,也是使用fwide
fwide(stream,NEG)表示设置为byte-orientation, NEG < 0
fwide(stream,POS)表示设置为wide character-orientation, POS > 0

这里介绍了C中的字节流与宽字符流。

所以我们面对不同的STREAM要用不同的printf版本,并且要在一个STREAM打开与关闭期间统一使用。
一旦普通版本或者宽字符版本的IO库函数作用于某个stream,这个stream就固定为对应的类型了……
此后使用fwide没有任何作用。(唯一的方法是:使用freopen重新打开流)
printf本身就支持宽字符的输出:使用 %lc  或者 %ls 对应 %c 和 %s 即可。
同样地,wprintf也支持字节的输出(%c %s)
并且,在使用printf的时候,程序输出的``external representation''是根据setlocale设置的具体编码值(即不是unicode的codepoint这种internal representation),如果当前环境不支持该编码,则输出失败。
scanf亦是同理。

总结:
1.读写文件时,使用mbs进行具体的编码存储(external representation);而在程序内部,则统一使用wcs进行字符串的处理(internal representation)。
2.mbs(MultiByte characterS,多字节字符) 和wcs(Wide CharacterS,宽字符)是两个完全distinct的概念。
具体参考资料看这里:
http://www.gnu.org/software/libc/manual/html_node/

----------附:iconv 编码实验for  utf32 / ucs4 ---------------
(假设以下文档的编码与文件名对应。)
当前有一个文档为utf8,内容为"你他妈的 0123 abcd" ,执行命令:
$ iconv -t utf32 utf8 | xxd
0000000: fffe 0000 604f 0000 d64e 0000 8859 0000  ....`O...N...Y..
0000010: 8476 0000 2000 0000 3000 0000 3100 0000  .v.. ...0...1...
0000020: 3200 0000 3300 0000 2000 0000 6100 0000  2...3... ...a...
0000030: 6200 0000 6300 0000 6400 0000            b...c...d...
可见,iconv把utf8转为utf32,并且自动添加BOM=ff fe(big endian)

$ iconv -t ucs4 utf8 | xxd
0000000: 0000 4f60 0000 4ed6 0000 5988 0000 7684  ..O`..N...Y...v.
0000010: 0000 0020 0000 0030 0000 0031 0000 0032  ... ...0...1...2
0000020: 0000 0033 0000 0020 0000 0061 0000 0062  ...3... ...a...b
0000030: 0000 0063 0000 0064                      ...c...d

可见,iconv却没有添加BOM,并且完全是按照字节顺序输出的(因此与big endian machine的读写方法冲突)。

根据wikipedia的介绍,utf32是ucs4的子集,它们都是用4字节来存储每一个字符,因此可以存储所有unicode的codepoint,
也因此它们的编码方式很简单:用每个byte来与codepoint一一对应。
功能上来说,utf32与ucs4是相同的。但是utf32 的document规定了一些unicode相关的语义。

同时有一个有趣的小发现:unicode的字库应该包含unicode所有的所有字符。
然而由于现在unicode的字符空间还没有被分配完,
因此有些合法的codepoint是没有字符的(informally speaking),而只是用它的codepoint hex value表示,比如这个:
hex 1D11E = 𝄞
正是U+01D11E 对应的``字符'' 打印出来的效果(glyph),哈哈。
另外,在windows下,可以安装Chinese(Taiwan)的unicode输入法进行输入,不过我发觉只支持4位的code point……
上面那个1D11E就打不出来。
此外,字体文件(ttf等)也有字符集的区别;
字体文件可能支持unicode(似乎TT必须支持),也可能不支持(如Courier, Fixed Sys)。
不同字体文件包含unicode字符集的不同内容(如中文字体就包含中文字体,韩语字体则包含棒子字而不包含中文,显然英文字体只有英文)。
windows的SimSun和heiti就有不同的字符数,不信查一下U+00CC这个字符,SimSun有而heiti没有。

事实上:
Currently (August, 2008), no single "Unicode font" includes all the characters defined in the present revision of the ISO 10646 (Unicode) standard.In fact, it would be impossible to create such a font in any common font format, as Unicode includes over 100,000 characters, while no widely-used font format supports more than 65,535 glyphs. So while one could make a set of related fonts to cover all of Unicode, a single Unicode font is not possible at this time.

也就是说,流行的字体格式最多也只支持2^16个字,远远不够unicode的字符个数。

----------附:几种常用unicode编码的对比 ---------------
先摘录一段from 这里:http://en.wikipedia.org/wiki/Code_point
A unicode text file is not necessarily merely a sequence of code points encoded into 4 byte blocks. Instead an encoding scheme is used to serialize a sequence of code points into a sequence of bytes(回忆一下huffman编码). A number of such schemes exist, and these trade between space efficiency and ease of encoding. A variable number of bytes can be used for each character.

这里主要介绍的是几种流行unicode编码的方法:
utf8, utf16/ucs2, utf32/ucs4

首先明确一下ucs(universal character set)和utf(unicode transformation format)的区别。
N年前,有两个组织在搞unicode,于是有两套不同但类似的标准。
后来他们意识到他们在折腾,于是便合并了,但是留下了N多历史遗留名词与标准。
关于ucs2与utf16的区别,这里有官方的解释:
http://unicode.org/faq/basic_q.html#25

简而言之,从数据交换的角度来说,它们的编码方法是完全一样的,都代表了相同的从unicode字符集到字节编码的映射。
但是utf16包含了更多的杂七杂八的特定的规定。
总之,使用时,使用utf开头的一族就对了。另外,ucs是以byte为单位的,而utf是bit,所以有utf16=ucs2。

utf32/ucs4是与unicode的codepoint一一对应的,使用固定长度的4字节/32bit编码。
由于常用的字符往往在低位(即高2byte用不上)因此非常浪费空间。而且,它也不能完全覆盖unicode字符集。

utf16/ucs2比utf32稍节省空间,但是它有所谓的endian问题,即必须在相应的utf16文件开头两字节指明究竟是以和顺序存储(BOM)。
因此utf16也分为 utf16le和 utf16be.(在某些编辑器上,保存编码格式为Unicode(little endian)之类的,就是指utf16了)

utf8应该是最流行的格式,因为它是面向字节的,没有大小端的问题。
并且由于它是完全与ASCII兼容的,legacy的只能处理ascii的程序能对utf8的文件处理得很好!
另外,由于utf8是可变长编码(ascii占1字节,中文等占多字节,最多占4字节)
因此比起固定长度的utf32节省很多空间。可以说,utf8是unicode编码的实施标准。

更详细的猛击http://en.wikipedia.org/wiki/UTF-8

Tuesday, January 6, 2009

python下的unicode object是个好东西

一说到mbcs(multiple bytes character set),就让人心烦.
从开始学习程序设计以来就不停地要面对这个问题.
这貌似是个鸡生蛋蛋生鸡的问题.
学习程序设计要考虑到编码问题;而编码问题如何处理又要先学会程序设计.
字符集(character set)和编码(encoding)概念的混淆实在害了不少人.
区域(locale)和各种乱七八糟的标准(standard)也让大家一路上混乱得可以(ANSI,ISO,IETF,....).

首先是字符集和编码.我觉得很有必要严格分清楚这两样东西,即使有时它们的确是``相等''的,
正如1+1与4/2一般,虽然表示的是同一个意义,但是严格来说它们不是同一样东西.
字符集顾名思义表示的是一个字符的集合,定义了某种语言包含的字符,是一个抽象的东西,
表达的是一种无形的信息. 然而在计算机内要具体表示它们,则需要有一个具体的表示方法,
在当代的存储程序型计算机内,就是如何用0,1来表示一个集合内的东西.因此谈到字符集就要涉及到编码.
在计算机发展的最初阶段,根本就没有字符集与编码的概念。那时所有的东西,在计算机里都是用ASCII存储的。
后来有了ANSI标准(本地化),在不同区域(locale)的系统里,使用字符集中的字符到具体编码的映射关系,
来表示对应字符集的编码。
最常见的自然是iso8859-1字符集了. 由于这个字符集只有一种编码方式,也即是ASCII,因此它们经常被混用.
而对于中文,GB2312,GBK,GB18030表示的是不同的字符集,然而对应的编码原则是一致的,都是用二字节的ascii value来编码一个中文字.
它们与ascii编码是"兼容"的. 对于日语,韩语等,也有不同的字符集与编码,而这些字符集与编码往往名称相同,因此往往被等同地混用(interchangeable)
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
"编码"的概念就是把"字符"转化成"字节",字符是抽象层面上的信息,而字节是具体存储表示的方法

Unicode的引进解决了不同字符集之间的不兼容,即使用一个超大的字符集来把世界上的语言都包含进去,给予一个统一的"编号".
由于其表示的范围极大(1,114,112个编码位,0 to 10FFFF),因此现在还有许多空位,以备以后需要而添加(甚至火星语也能编进去.)
比如u963f这个4位16进制数表示了'阿'这种中文字符.(开头的u表示一个unicode字符的开始)
ascii是使用最广泛的编码,因此很多编码方式都尽量与之相容.
因此可以看到u0030-u0039与ascii的0-9的值是一致的.
Unicode中文一般译作'统一码'或者'万国码'(都很恶心).是由ISO和unicode consortium这两个组织共同制定的标准,亦称为ISO/IEC 10646.
由于unicode太大了,而且很多是空的,常用的只占了那么一部分,存储起来很浪费空间.
因此有不同的''mapping'',对unicode进行编码(并且一般只映射了unicode的一个子集),
常见的有utf8,utf16等.它们就是unicode的编码.
这俩个都是使用变长的编码来表示字符,而utf8是现在unicode的事实标准.
一个例子:
u963f表示的是'阿',
阿这个字,在utf8里是e9 98 bf,而在utf16里是3f 96.
(事实上,在python里面,utf16得到的是'ff fe 3f 96',前面的两个byte是用来定义一个utf16的串的字节顺序的标记,所谓的BOM问题,即byte order mark)
而在gbk和gb2312,gb18030里都是b0 a2.
这些数据与u963f这个东西有本质的区别.
u963f是'阿'在unicode里面的"位置",而以上数据则是在计算机里面的实际表示方法.
(有一个很好的例子,见下)

----------分割线说:其实以上是introduction......--------------

写了这么多,似乎反客为主了....
python里面的u'hello'就可以得到一个unicode object,
所有的字符都可以利用类似方法存储为unicode表示.
然而具体编码是不同的.
比如我在一个utf8的终端下,输入
a=u'阿'
就得到了一个'阿'的unicode对象,放在a里面.
使用
a.encode('utf8')
a.encode('gbk')
等,则分别得到它的utf8和gbk编码数据.\
(这里接上面,即使我们看到,a这个东西表示`u963f',但是这不代表a本身在机器里面是存储为`u963f'.
而应该是某种具体的编码方法,如utf8等.

使用unicode方法可以从某个具体编码的数值得到一个unicode object.
比如
unicode( a.encode('utf8'), 'utf8')
则表示把a先编码为utf8,得到'阿'的utf8编码数值,然后再根据utf8反查得到
'阿'的unicode编码位,即u963f.
同理,使用decode方法,可以某个具体的编码数值转换为unicode对象.
如:
'阿'.decode('utf8')
可以得到u963f.(如果在gbk的终端,则必须使用gbk)

这里有一篇更详细的文章介绍此特性.
http://lenciel.cn/docs/unicode-in-python/

摘录几段我觉得要背下来的....
1. 处理任何编解码问题时我们都要牢记,unicode是为世界上所有的字符分配了一个码位(code point)的概念,而不是实现(字符在内存或者文件中的存在方式)。unicode占16位是绝对错误的(世界上语言如此多,码位早就超过百万个了)。

2. 要对unicode对象进行保存或者打印前,你要对它进行编码(encode)才行。

5. 正确的做法是,尽量早正确的decode一个str为unicode对象(如读入一个文件的内容,返回一个网页的内容等),并在你的程序里面全部使用unicode相关操作,直到你需要打印或者是写入文件时,再去encode它。

6. python提供了codec来减少我们的代码行数,它不是你乱码的救星:

f = open('small.html', "r")
bytes_in=f.read()
unicode_in=bytes_in.encode(utf-8)

===> fileObj = codecs.open( "small.html", "r", "utf-8" )


关于unicode,顺便还推荐这篇.
http://lenciel.cn/docs/unicode-complete/

-- 外一篇:GBK/GB2312 Python JOKE一则 --

问:为什么国家推出了GB2312之后要推出GBK?
答:Python的输出(under utf8)给了我们答案。

>>> u'�'.encode('gbk').decode('gb2312')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 0-1: illegal multibyte sequence


Sunday, January 4, 2009

趣闻:什么是“锟斤拷”

http://zh.wikipedia.org/wiki/%E9%94%9F%E6%96%A4%E6%8B%B7

http://initiative.yo2.cn/archives/634636

锟斤拷是一种计算机软件系统内部错误编码导致的文字不正常显示的现象。

Unicode标准中定义了一个Replacement Character,标记为U+FFFD,作用为:

A character used as a substitute for an uninterpretable character from another encoding. The Unicode Standard uses U+FFFD replacement character for this function.

U+FFFD的UTF-8编码结果为"EF BF BD"。如果有一大段文字都是采用了"U+FFFD U+FFFD"作为占位符的话,那么这段字符的UTF-8流十六进制格式为"EF BF BD EF BF BD..."。

如果错误的放置于GB2312/GBK/CP936编码环境里显示的话,最终字符为锟斤拷,他们分别是锟(0xEFBF),斤(0xBDEF),拷(0xBFBD)。由于Web大量采用Gb2312和UTF-8混合编码,该现象在互联网十分普遍。据悉,该现象产生的原因是多方面的,一来是Microsoft、Sun等垄断公司对打广告投入大量资金,但是对编码问题这种细节做得不够细致,二来是PM经常克扣程序员工资,导致程序员代码激情和质量下降。[�源�求]


Thursday, January 1, 2009

转一篇P2P中博弈论应用的文章,刚学完,有感触……

http://blog.youxu.info/2008/12/31/tit-for-tac-and-p2p-software/

(过新年了, 找到了以前写的一篇没发出来的旧文章, 算作一篇贺岁吧)

最近日本著名演员饭岛老师去世了. 在我这个年龄段的人中, 熟悉饭岛老师的相信十有八九都是通过奇妙的叫做 bt 或者 电驴 的软件认识的. 今天我们就来八卦一下程序设计人员是如何设计这些客户端的策略使得您既能下载欣赏到饭岛老师的片子, 又不会浪费您太多的上传带宽的. 简单的说, 就是 P2P 软件的客户端的策略该如何设计, 使得整个系统能够帮助每个用户获得相应的利益最大化.

要研究这个问题, 我们得从博弈论谈起. 但是因为这个是给程序员看的八卦, 不是数学专业课, 我们不在这里说太多的数学, 而是用例子和八卦引入.

大家都知道, 1994 年的诺贝尔经济学奖给了一个数学家, 约翰.纳什 (电影"美丽心灵"为证). 纳什的理论工作是推广了冯诺伊曼开创的极大极小定理(博弈论的基本定理). 而在通俗的对博弈论的介绍中, 提到纳什, 一般都是着重在纳什均衡和囚徒困境上. 我们不具体深究纳什均衡的数学意义, 而是以下面一个具体的极其简化的例子来说明囚徒困境:

假设 BT 网络中两个节点 阿强(A) 和 B哥(B) 要交换文件. 文件很大, 我们假设需要非常多轮交换才能完成. 每一轮, 每个节点可以选择 平衡上传/下载 和 几乎不上传/贪婪下载两组策略. 我们按照博弈论的一般用语, 把第一种策略称为 C(合作), 第二种称为 D(叛变). 同时, 假设A, B 都是使用 ADSL 网络, 所以上传成本比下载成本要高很多, 我们在计算回报的时候考虑这样的不对称. 现在, 假设 A 和 B 各自有对方需要的文件, 那么, 如果 A, B 同时选择策略 C, 即平衡的上传和下载, 他们得到的回报都是 3, 如果其中一个人偷鸡选择 D, 即几乎不上传, 光下载; 而另一个节点选择 C, 则选择 D 的能够下载到所要的文件且几乎不需要付出上传的代价, 我们记回报为 5, 而另一个人付出了上传的费用, 却得到了一点点的下载, 可以把回报看成是0. 如果两个人都选择贪婪下载, 几乎不上传, 那么两个人都得到了一点点下载, 现在这样的下载量没有3多, 但是因为本身付出的上传成本也少, 我们把这时候两者的回报都定为 1.

说了这么多, 只是为了让问题更加的真实. 这些交代的条件的数学本质, 可用表格表示, 博弈论中称之为支付矩阵:

C D
C (3,3) (0, 5)
D (5,0) (1, 1)

现在的问题是, 阿强和B哥都是理性的, 也是自私的, 因此, 他们都认为, "假如我选 C, 对方可能选 C 或者 D, 那么我这个策略最糟糕的情况下收益是 0, 而假如我选 D, 最糟糕的情况下收益是 1″ 那么, 因为 D 下最糟糕的收益比 C 最糟糕的情况下收益要大, 理智的人肯定选D. 我们看到, 两者选择 D 都是理性的, 但是实际上从对两者的收益分析看, 两者都选择 C 才是更加优的. 这个表面上看上去很理智但是最后没有到达对双方最好的结果的困境, 就是所谓的囚徒困境. (看过这篇八卦, 您也可以叫做饭岛老师困境)

关于囚徒/饭岛困境的简单介绍就到这里, 现在我们看我们的原始问题. 我们知道, BT 交换文件是分成一块一块的, 也就是说, 是一次一次的交换的. 我们把每次交换叫做一轮的话, 整个系统是一个多轮的博弈问题(或者叫做多阶段的博弈问题). 这个博弈问题, 就显得好玩起来了. 为什么呢, 因为多阶段博弈, 居然能够让自私的A和B两个节点为了自己的利益, 进化出合作来.

我们先简单的说明一下多阶段博弈不必然的能跳出囚徒困境. 比方说, 如果 A 和 B 知道一共有 N 轮博弈, 那么最后一轮, 理智的他们肯定都陷入了囚徒困境, 在第 N 轮 的策略清楚之后, N 的问题就转化为 N-1 轮的问题. 所以, 必然的, A 和 B 在所有 N 轮上, 都会陷入囚徒困境 (好比奸商一辈子只和你做有限次买卖的话, 就会一直黑你, 不黑白不黑). 他们等到花儿也谢了, 也不能得到自己想要的内容. 但是, 问题的奥妙在于, 假如A 和 B 不知道一共多少轮, 或者有无限轮呢? 假如阿强在某轮选择平衡的上传和下载(C), 则可能正好碰上 B 哥 也选择"友好合作", 那么, 两个人都舒舒服服的交换了饭岛老师的片片. 所以, 对于一个设计良好的BT客户端, 问题的关键在于怎么选择自己的策略, 使得既能完成自己自私的下片目标, 又能注意和其他客户端良好的合作使得自己的收益最大, 而不在于在特定的一轮中自己的得失.

这里, 我们的目标是设计一个良好的策略. 通常, 在设计一个实践中性能良好的算法的时候, 数学家和计算机科学家在这里的方法就鲜明的分野了. 数学家, 会证明这样算法的存在性, 性能上下界, 和众多的必要条件, 以及算法之间在最理想的情况下的好坏比较. 而计算机科学家, 会像搭积木一样, 用不同的基本模块, 直接尝试不同的组合, 一一做实验, 看哪种方法最好. 在这里, 我仅介绍一种计算机科学家的方法: 通过让不同方法比赛, 取出赢家, 赢家的方法最好的方法. 其实准确的说, 这个就是达尔文的适者生存的方法. 而这个比赛本身又是一段非常有趣的八卦, 因此我着重花笔墨介绍一下.

在心理学和行为学领域, 有一本非常著名的书, 叫做<合作的进化>. 其作者, 记载了在80年代, 他组织的两次比赛, 叫做IPD (Iterative Prisoner's Dilemma, 多轮囚徒困境). 竞赛的目的是在一个多轮的囚徒困境中找出最好的策略, 参赛者自己写好算法程序, 然后由组织者让这些程序两两对弈, 看谁在多轮囚徒困境中得到最多的分. 在所有的数学家计算机科学家等提交的很多程序中, 表现最好的一个策略, 超乎寻常的只有四行简单的 Basic 程序. 这四行 Basic 程序, 勾勒出了一个叫做 "针锋相对" 的算法(Tit for Tac).  这个算法策略很简单, 一开始采用合作, 假如对方上一轮合作, 则本轮合作. 如果对方上一轮对抗, 则本轮对抗. 用中国人熟悉的话说, 叫做"人不犯我, 我不犯人; 人若犯我, 我必犯人". (四句话正好对应四行程序, 不是巧合). 其他的算法, 比如随机算法呀, 永远敌对的算法呀, 都比不过这个算法. 因此, 这个算法赢得了第一年的竞赛.

第二次, 各位吸取教训, 继续开发好算法. 猜猜第二次谁赢了? 居然还是那四行程序! 在合作的进化中, 作者从"宽容, 以牙还牙"等社会学的角度去解释为啥这四行程序会赢. 或许对人生有深刻思考的人会感叹, 这四行程序的确蕴含了深刻的智慧. 但是, 很不幸的是, 这个程序在现实中, 有一个非常大的漏洞, 而因为这个漏洞, 使得BT程序如果不修改策略, 先现实中会寸步难行. 这个看上去非常理智非常聪明的策略到底是怎样的大漏洞呢, 我先卖个关子, 下回分解.

(想看剧透的, 可以看 Wikipedia 的条目: Tit for Tac: http://en.wikipedia.org/wiki/Tit_for_Tat )

新年快乐!