据说这个游戏可以很快的增加你的内存

今天的主角名叫 N-back,起源似乎是某种心理测试,然后碰巧有人发现它碰巧和大多数智力游戏只能提高你在那一个游戏上的表现不同,N-back 的作用可以转移

关于 N-back,更多的细节可以看这里:N-back FAQ(作者貌似是个大牛),文中提到几点挺有道理的,比如说不管内存(Working Memory)和 IQ 是否有直接的关系,但至少对程序员来说,快速的记住几个变量及其类型、几个函数及其原型什么的可以很大的提高工作效率——也就是说内存这东西还是挺重要的。这个,是当然的了

下面提一下 N-back 怎么玩:

首先,N-back 中的 N 是一个变量,当 N==1 时,你需要判断上一个图形和当前的图形是不是一样的;当 N==2 时,你就需要判断上上个图形和当前的图形是不是一样的,依此类推。

当然,上一句提到的图形也可以不仅仅是图形,还可以是字母、数字、声音等各种刺激;而一种称作 Dual N-back 的变种则是同时施加两种刺激,你需要分别判断它们各自是否和之前匹配。

下面是我乱写的 Single N-back:

(BTW 我显然不会去考虑 IE 用户的感受,读者您自重吧)

其实我觉得 Dual N-back 更好玩一些啦,不过想不到有什么好的操作方式,所以没加上来(用 A,L 操作也实在太囧了吧!),iPhone 上的 IQ boost 的 2-back (Dual) 我已经可以玩到全对了,哈哈,炫一下。

哦对了,我的法线贴图做好了

看看这光影的变化,多漂亮啊:

其实那些凹凸并不在模型上,而只是通过一张图保存了平面的法线方向。

利用法线贴图上的法线信息计算光照的时候需要注意的是得要把它的坐标系(横着是 red, 竖着是 green, 立在平面上的是 blue)转换到空间上对应平面的坐标系中 —— 或者反过来,把光线和视线转换到法线平面的坐标系中(我就是这么做的),然后再那啥。

提一下,从网上搜到的信息来看这个的计算貌似挺复杂的,但是考虑线性变换其实也就是线性方程而已,从法线贴图的坐标系转换到空间坐标系,当前正在渲染的点所在的空间三角形的贴图的 u 坐标的正方向为 a,法向量为 n,不考虑位移(为什么可以不考虑位移?自己想),我们可以列出这么几个式子:

z = cross(a, n);

(1,0,0) -> (a.x, a.y, a.z)
(0,1,0) -> (n.x, n.y, n.z)
(0,0,1) -> (z.x, z.y, z.z)

 

有没有恍然大悟的感觉?

 

没有?再提示,设从法线贴图的坐标空间转换到空间三角形的坐标空间的矩阵为 M, 那么有

1,0,0            a.x, a.y, a.z
0,1,0  *  M ==  n.x, n.y, n.z
0,0,1  z.x, z.y, z.z

好了,点到为止。

 

俺用的漫反射贴图是这样的:

漫反射贴图

法线贴图:

法线贴图

高光贴图:(虽然用了而且计算了,不过光源放的位置似乎不太恰当以至于完全看不到高光的效果,囧~)

高光贴图

可执行文件:

binary

 

源码……还是不给,因为写得丑了……

 

PS. 想要运行着玩玩看的话你得把上面三个贴图和这个程序保存在一起,如果你有更靠谱的贴图当然更好(分别保存为 tex.png, tex_NRM.png, tex_SPEC.png)。

PPS. 在学 DX,所以上面用到的线性变换也是假设向量是行向量,所以对向量的变换就是用行向量左乘矩阵,不过换个方式用矩阵左乘列向量区别也不大。

PPPS. 扫了几本 3D 数学方面的书,发现还是这本比较靠谱: http://book.douban.com/subject/1400419/

PPPPS. 非常感谢老胡当年让我感受到了线性代数的好玩之处,不然现在在公司里可要遭鄙视了。

PPPPPS. 我写的 3dmath.h,如果你有兴趣看的话——含有一个向量类一个矩阵类,若干无趣的函数,唯一比较有意思的东西是透视和投影。

PPPPPPS. 好几次看见自己拍的照片,突然想到,哇,这阴影好逼真啊。

无营养两则

1. 最近在补 3D 知识,上周花很大的精力试着手动逐像素渲染 3D 图形,挺好玩的:

老大的要求是高光贴图和法线贴图也是要的,我也做出来了,只不过几个小时之前突然意识到一个非常重要的坐标转换没有做,所以那个法线贴图的结果其实很不靠谱,代码先不放了,省得丢人,等把法线贴图做对了再说吧。

这是阉割版的程序 —— 如果你有兴趣围观的话:rending.7z

哦对了,这里用到的数学运算俺也都自己实现了一遍,等有时间了会整理一下的,毕竟这个其实挺好玩。

 

 

2. 觉得 mataball 这东西挺有意思,结果就整出了个奇丑的:

任何试图画出有两个眼睛一个嘴巴的面部的尝试都会得到一个青蛙

程序和代码在这儿:metaball.7z

圈圈们默认是会动的,如果你只是想画青蛙,把 render 函数的第二个参数设为 0 就好了。

BTW Fast inverse square root 真是威武,有多威武你自己试试就知道了。

 

自己编译了一下 SDL

其实我只是想在 SDL 中使用 png 图像,但又不愿意加上 SDL_image.dll, libpngXX.dll, zlibXXX.dll 这大一堆东西,想起不知道什么时候见到的一句让我深以为然的话:糙快猛乃素王道,于是俺就把 SDL, zlib, libpng, SDL_image 分别编译了一遍,然后把它们的 .o 文件都放在一起(除了 SDL_win32_main.o),再然后 gcc --shared -o SDL--.dll *.o -lgdi32 -lwinmm 就成了。(怎么样,够暴力吧!)

SDL_image 有问题,需要改:

72a73,74
> #include <pngstruct.h>
> #include <pnginfo.h>
350c352
<       if ( setjmp(png_ptr->jmpbuf) ) {
---
>       if ( setjmp(png_ptr->png_jmpbuf) ) {

以后自己做点什么游戏原型,或者小游戏,或者寻路算法之类的东西时,可能就会用得上它。

 

你也需要吗?点这里: 动态链接库 静态链接库

 

PS. 这几个玩意儿的授权真乱,一个 GNU Lesser GPL,一个 GNU Library GPL,另外两个是自己的协议;简单的读了一下,不出意外的话和闭源程序动态链接起来应该都是不会有啥问题的,不过另一方面,混合起来之后应该怎么发布还真不知道,所以,我没有发布任何东西,上面的链接纯粹是你的幻觉。

用 hough 变换检测抛物线

告诉你某张图中很可能有某个东西长得很像开口向上的抛物线,想把它检测出来,嘿嘿,看上去很难吧?

其实呢,这种事情很适合用 hough 变换 来做:

假设抛物线方程为 y = a ( x - b )2 + c

先把图像边缘检测出来,对于图像边缘上的每一个点 (x,y), 可以有很多条抛物线穿过它,即有很多个 a, b, c 可以使上面的方程成立,于是, 你很暴力的把这些 a, b, c 都记下来! ,对于每个 x,y 点都记下一次,然后看看哪些 a, b, c 的组合被命中的次数比较多就好了。

当然,上面的那个过程有不少地方是可以优化的,比如说对于图片,b 和 c 肯定是整数值,而且 b 很可能在比较偏中心的位置,而 a 的取值范围可以预先设定,比如说如果你想检测下巴,保守的说 a 肯定就是小于 1 的(不然那下巴也太尖了吧!),如此这般等等——而 OpenCV 中用 hough 变换检查圆的代码中显然还有些其他的不那么容易理解的优化,反正我也用不着把它做得很高效,就没有研究了。

下面上图:

(图片均来源于 pixdaus.com ,如果碰巧它侵犯了您的版权,请与我联系)

 

再上代码——

 

(你确定要看吗??!!)

 

 

(警告!魔数出没!)

 

 

 

(警告!没有注释!)

 

 

 

 

(警告!诡异的变量名出没!)

 

 

 

 

 

(警告!你的大脑可能因为看到这种乱七八糟的代码受到永久性的损伤!!)

 

 

 

 

 

 

(你确定依然要看吗??!!!!)

 

 

 

 

 

 

 

// y = a/150*(x-b)^2+c;
struct Parabola {
    int a;
    int b;
    int c;
    Parabola():a(0),b(0),c(0){}
    Parabola(int a, int b, int c):a(a),b(b),c(c){}
    Parabola(Parabola const& p):a(p.a),b(p.b),c(p.c){}
};
struct Record {
    Parabola para;
    int      count;
    Record():para(),count(0){}
    Record(int a, int b, int c, int cnt):para(a,b,c),count(cnt){}
};
struct RecordCmp {
    inline bool operator()(Record const& a, Record const& b) {
        return a.count>b.count;
    }
};
std::vector<Parabola> detectParabolas( cv::Mat const& img,
                                       int minA=-1,
                                       int maxA=-1,
                                       int minB=-1,
                                       int maxB=-1,
                                       int minC=-1,
                                       int maxC=-1 )
{
    cv::Mat edges;
    // cv::imshow("debug", img);
    // cv::waitKey(3000);
    cv::Canny( img, edges, 50, 150 );
    cv::Mat_<uint8_t> img_(edges);
    // cv::imshow("debug", edges);
    // cv::waitKey(3000);
    int ***acc = 0;

    if(minA < 0)
        minA = 1;
    if(minB < 0)
        minB = img.cols/4;
    if(minC < 0)
        minC = img.rows/4;
    if(maxA < 0)
        maxA = 60;
    if(maxB < 0)
        maxB = img.cols/2+img.cols/4;
    if(maxC < 0)
        maxC = img.rows;

    acc = new int**[maxA+1];
    for(int i=0; i<maxA+1; ++i) {
        acc[i] = new int*[maxB+1];
        for(int j=0; j<maxB+1; ++j) {
            acc[i][j] = new int[maxC+1];
            for(int k=minC; k<maxC+1; ++k) {
                acc[i][j][k]=0;
            }
        }
    }

    printf("calculating ...\n");
    int percent=0;
    for(int y=0; y<img.rows; ++y) {
        for(int x=0; x<img.cols; ++x) {
            if(img_(y,x)) {
                for(int a=minA; a<=maxA; ++a){
                    for(int b=minB; b<maxB; ++b) {
                        int c=y+a*(x-b)*(x-b)/150;
                        if(c>=minC && c<=maxC) {
                            ++acc[a][b][c];
                        }
                    }
                }
            }
        }
        int p=int(round(double(y)/img.rows*100.));
        if(p!=percent) {
            percent = p;
            printf("> %02d%%\n", percent);
        }
    }
    printf("done!\n");

    sizedpq<Record, RecordCmp> pq(3);
    for(int i=minA; i<=maxA; ++i) {
        for(int j=minB; j<=maxB; ++j) {
            for(int k=minC; k<=maxC; ++k) {
                pq.insert(Record(-i,j,k, acc[i][j][k]));
            }
        }
    }

    for(int i=0; i<=maxA; ++i) {
        for(int j=0; j<=maxB; ++j) {
            delete[] acc[i][j];
        }
        delete[] acc[i];
    }
    delete[] acc;

    std::vector<Parabola> res;
    for(int i=0; i<3; ++i) {
        res.push_back(pq[i].para);
    }
    return res;
}

上面那段代码最诡异而需要说明的地方是 a ,我希望检测的是开口向上的抛物线,于是在计算机图像的坐标系统下 a 的取值应该是负数,但作为数组下标时它当然得要是正数,所以这里多绕了几个弯,下次我自己看到肯定也会忘记的,不过还好这算法相当简单,下次需要用到我肯定更愿意重写。

sizedpq 类是俺以前做马赛克拼接图像时折腾出来的一个固定大小的优先队列类,其实设计得并不好,不过接口挺好用,所以.. 就拿来用了。

 

PS. 速度与图像边缘数量关系很密切,不过通常情况下处理一张上面展示的那种大小的图像也就需要 3 秒钟左右吧,其实还可以接受。

PPS. 人果然还是逼出来的啊,今早老宋很客气的问我节日快乐,弄得我很不好意思的在两个小时之内把这个做好了……

PPPS. 上周末和室友去西湖边逛了一圈,在柳浪闻莺景区里,我刚拍完一张照,发现旁边飘过了一个长发白衣飘逸出尘的美女背影,我说哇美女,他说其实不是美女我已经观察很久了...... 唉,男人。

PPPPS. 谁能教我玩 DotA 啊!!!

流水帐+意识流 [2]

1. C++ 很变态

去网易面试了,一个 C++ 上的问题把我难到了:“多重继承的时候子类是不是要保存每个父类的虚函数表?”

我回答 “通常是的,但虚继承的时候好像 %#@$%#^&*” —— 其实我就记得虚继承的时候情况很有点乱,忘了究竟有多乱。

回来之后做了个实验:

#include <iostream>
#include <iomanip>
#ifdef _MSC_VER // 老兄,特立独行的永远是你!!
typedef unsigned int uint32_t;
#else
#include <stdint.h>
#endif

using namespace std;

class A
{
public:
    uint32_t x;
    uint32_t y;
    A():x(42),y(24){}
    virtual void f(){
        cout<<"A::f()    [this="<<this
            <<"]; this->x="<<x<<"; this->y="<<y<<";\n";
    }
};

class B:virtual public A
{
public:
    B():A(){}
};

class C:virtual public A
{
public:
    C():A(){x=21,y=12;}
    virtual void f(){
        cout<<"C::f()    [this="<<this
            <<"]; this->x="<<x<<"; this->y="<<y<<";\n";
    }
};

class D:public B, public C
{ 
public:
    D():A(),B(),C(){}
};

template<class T>
void showDetail(T const& v)
{
    int s=sizeof(T)/4;
    uint32_t const* p=reinterpret_cast<uint32_t const*>(&v);
    cout<<"  +-------------+\n";
    for(int i=0;i<s;++i) {
        cout<<"  | "<<setw(11)<<p[i]<<" | ";
        if(p[i]>10000) { // suppose it's a pointer
            cout<<" -> "<<*reinterpret_cast<uint32_t const*>(p[i]);
        }
        cout<<endl;
    }
    cout<<"  +-------------+\n";
}

int main(){
    A a;
    B b;
    C c;
    D d;
    cout<<"a:\n";
    showDetail(a);
    cout<<"b:\n";
    showDetail(b);
    cout<<"c:\n";
    showDetail(c);
    cout<<"d:\n";
    showDetail(d);
    return 0;
}

为的是看看虚继承的时候到底发生了什么,结果又是大出意外,GCC, M$VC, DigitalMars 编译出来的竟各不相同:

GCC

a:  +-------------+
    |     4666904 | -> 4324460
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4666924 | -> 0
    |     4666936 | -> 4324460
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4666956 | -> 4324764
    |     4666972 | -> 4631892
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4666988 | -> 4
    |     4667000 | -> 4324764
    |     4667016 | -> 4631892
    |          21 |
    |          12 |
    +-------------+

M$VC

a:  +-------------+
    |     4305572 | -> 4198672
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4305668 | -> 0
    |     4305664 | -> 4198672
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4305632 | -> 0
    |           0 |
    |     4305628 | -> 4217088
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4305684 | -> 0
    |     4305632 | -> 0
    |           0 |
    |     4305680 | -> 4217088
    |          21 |
    |          12 |
    +-------------+

DigitalMars

a:  +-------------+
    |     4448636 | -> 4205756
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4448640 | -> 0
    |     4581960 | -> 5856153
    |     4448636 | -> 4205756
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4448660 | -> 4205896
    |     4448648 | -> 0
    |     4448656 | -> 4203244
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4448660 | -> 4205896
    |     4448672 | -> 0
    |     4448664 | -> 0
    |     5838868 | -> 4500440
    |     4448680 | -> 4203252
    |          21 |
    |          12 |
    +-------------+

好吧,看这情景,我怎么回答都算不上错,因为哪有正确答案啊。

可惜把 Borland C++ Builder 和 Open Watcom 编译器都删了,不然如果看到 5 种不同的结果岂不是更欢乐~ 唉,难怪 C++ 木有 ABI。

另外,被问到虚函数表在对象的什么部位时我发烧似的答道“在尾部”,囧啊,谁教我的。(不过另一方面,可以诡辩一下的是, C++ 2003 标准甚至没有规定运行期多态一定要以虚函数表的形式实现,只是目前的编译器不约而同的采用了虚函数表而已,所以,嗯...)

Update:

Open Watcom 编译出来的结果是这样的:

a:
  +-------------+
  |          42 |
  |          24 |
  |     4240484 | -> 4198782
  +-------------+
b:
  +-------------+
  |     4240544 | -> 0
  |           8 |
  |          42 |
  |          24 |
  |     4240556 | -> 4198782
  +-------------+
c:
  +-------------+
  |     4240488 | -> 0
  |     4240500 | -> 4198952
  |          12 |
  |          21 |
  |          12 |
  |     4240508 | -> 4198866
  +-------------+
d:
  +-------------+
  |     4240520 | -> 0
  |     4240532 | -> 4198952
  |     4240512 | -> 8
  |          16 |
  |          21 |
  |          12 |
  |     4240540 | -> 4199045
  +-------------+

果然又不同——不过,惊喜啊,它还真把虚函数表放在了尾部。

2. 脑袋短路

还是网易面试,几个算法题都发挥得不够满意,甚至包括生成随机数的题,回头想想也有改进的空间。

嗯...不知道试题有没有涉及到某种需要保密的东西,所以暂时先不谈好了……

3. 春天来了

img_0059.jpg
ps. 实践表明,GCC 编译器会给类的成员函数添一个隐含的 this 参数,通常会自动赋值,然而用奇淫手段弄到了函数地址时也可以手动传对象地址去,就像在 python 中对待 self 那样;然而其他的 C++ 编译器似乎都没这么干,至少你把 void A::f() 当作 void f(A*) 来玩是不行的,可惜了,不然会挺好玩的。

一个想法:could

曾经在 Notepad++ 的官网上看到过一个关于 Linux 为何不和谐的笑料:

bash-3.2$ peace
bash: peace: command not found
bash-3.2$ harmony
bash: harmony: command not found
bash-3.2$ kill
kill: you need to specify whom to kill
base-3.2$ _


 

前几天不知为何又想到了这个笑话,突然有了个让他变得和谐一点的想法,为何不让命令行指令看上去人性化一点呢,比如说,在命令行输入 could you calculate 1+41 for me?

输出 Of course! The answer is 42 这样。

于是,俺就动手码了几行代码,写了个名为 could 的 python 脚本,which 可以把命令行参数分给不同的程序去做,在上面这个例子中,他所做的就是把 calculate 1+41 交给 calculate.py 处理。

虽然没啥技术含量,但是,等把它相关的功能实现得差不多了,应该会比较有趣吧——尤其对于那些不知道命令行是什么玩意儿的生物。

 

已经完成的代码在这儿,目前只能算是个 demo。

走过路过的你如果觉得有点意思,不妨留个爪印,咱可以一起整点好玩的。

标题不知道怎么取

回家了,睡觉,看书,编程,聚会,无聊的时候还做做 ACM 玩,最近就是这样在过吧……嗯,和 MM 闹别扭了,烦,下面是意识流。

 

稍稍 PS 了一下,作壁纸,可惜的是镜头不给力,闪光灯也不给力,于是乎只好用很高的 ISO 来拍这些玩意儿——于是乎噪点很多,木有办法。

 

 

另外,你应该也会被这样的问题折腾过吧:给你一个容量 3 升的杯子和一个容量 5 升的杯子,想要 4 升水,怎么办;一直觉得这问题完全是闲得蛋疼,但某日在 POJ 上面遇到了,倒是觉得有点好玩了,其实就是一个广度优先的搜索(求最优解),啥技术含量都没有:

杯子 1:(ml)
杯子 2:(ml)
目标:(ml)


您若是很无聊,不妨可以试试 11111, 130, 121 之类的变态数据,我看它有那么多个步骤就觉得很欢乐,可能是幸灾乐祸的感觉吧:叫你拿这玩意忽悠我,看我以后怎么忽悠你。( BTW, 做这种实验的时候建议用 Chrome )

Threat Space Search 其实是浮云啊~

黑先,求 VCT (连续冲三取胜):

 

 

 

 

 

 

 

 

解(之一):

对 threat space search 而言,第一步和第九步肯定都无比艰难吧~

threat space search 为了减少分枝数量做了一个很大胆的设定:如果黑成了三,那么允许白把它的两端都堵上,然后如果黑还能赢,那么黑当然是赢定了。

想法很吸引人,Victoria 的战绩也相当不错,但这算法没有考虑到左也是死,右也是死,但左右夹击就不会死的情况,就如同上面这题,多少感觉有些遗憾——不知道这种状况发生的概率大不大。

另外没记错的话他的论文中提到了算法可以有某种 draw-back 机制,但也只是提了一下而已,真不像话。

悲剧啊,只能混个纪念品玩玩

我的程序在这儿了: http://code.google.com/p/harmless/

很想把他叫做 Mostly Harmless,可是不能太狂啊,它明明就是 TOTALLY harmless。

另外眼下 svn 连接不上 google code, 不出意外的话原因应该就是众所周知的那个吧,我最近也懒得折腾了,所以代码先就在压缩包里看吧。

核心代码在这里: http://code.google.com/p/harmless/source/browse/trunk/main.cpp

Windows / Linux 可执行程序以及源码压缩包在这里: http://code.google.com/p/harmless/downloads/list

 

用到的技术有 alpha-beta, iterative deepening, transposition table, null move heuristic, bit board, threat space search (没写完而且不准的版本), principal variation ,然而这些算法中没有几个是容易调试的,再加上核心的评价函数依然是瞎掰的(试过用进化算法让它进化出一个比较靠谱的参数,然而结果是它进化到了一种自娱自乐的境界,和自己这种风格的AI竞赛倒还好,可实际棋力依旧不行),再加上整体框架上还有好些不成熟的地方,比如说我干嘛要记录下某个空位在四个方位的子力呢,记录下最好的两个方位就OK了啊——反正,改来改去,最后一次练习赛之后的版本棋力竟然越变越差,真伤心。

泥人五子棋里面似乎有好几个可以借鉴的想法,开局库可以多折腾几条记录进去,proof number search + threat space search + 开局库效果应该就会还可以了吧;另外可惜 db-search 接触得太晚了,它的思想也很吸引人。

寒假重写。

 

 

最后,附上两张几天前在厕所玩水的照片:

 

还有好多张,有时间了再传到网易去。

为了证明我还没死…这是我写的五子棋

完成度不到 40%,棋力相当差,最核心的在找不到必杀时的局面评价函数完全是瞎掰的……虽说如此当我小放水时它偶尔还是能赢的:

white-won

想在五子棋方面找自信的童鞋可以试试: gomoku.7z

 

竞赛毕竟是竞赛,技术细节源代码神马的就不透露鸟……何况还没写完呢……

BTW 为了保证逆向我的程序的成本比从头开始写一个五子棋程序要高,俺把 exe 文件加密鸟,如果杀毒软件抱怨的话(也不太可能抱怨啦,只是怕有这种可能性)——就请你做一个艰难的选择吧。

 

KMP 算法的复杂度和最差情况

此段已经加入到了俺写的 MP/KMP 算法详解 中了;缺少这一段正是它一直保持着草稿状态的主要原因;但由于害怕疏忽,目前也还不敢改变其状态。(囧)

好吧,下面开始正文

6   复杂度分析

至于为什么 KMP 算法的复杂度是线性的,我们再回头看看 另一种表示 一节中的算法主体:

int MP(char const* pattern, char const* text)
{
    int i=0,j=0,m=strlen(pattern);
    int F[m+1];
    calcF (pattern, F);
    for(;text[i];++i,++j) { // j 增大,i 增大
        while(j>=0 && pattern[j]!=text[i]) {
            j = F[j];       // j 减小
        }
        if(j>=m-1) {
            return (i+1-m);
        }
    }
    return -1;
}

i 只有增大的份,所以 ++i 最多执行 n 次,这个很显然。

j 初始值为 0,一共增加了 n 次,而 j>=-1 ,于是 j = F[j] 这一句最多也就执行了 n+1 次(否则就会出现 j<-1 的情况了)。

所以就是线性的了!

7   KMP 算法的最长停顿

为了说明 KMP 算法在文本串上的某一个字符上进行了很多次比较的极限情况(也就是所谓的停顿或者E文的 delay ),我们首先要介绍一下 “费波拉契串” —— 因为,很巧,它就是能使 KMP 算法达到最糟糕状况的模式串,一会儿我们会说到。

提到 “费波拉契” ,相信不少人会直接想到 1, 1, 2, 3, 5, 8, 13, 21, 34 ... ,是的,费波拉契串的定义也十分类似:

设 P 是个费波拉契串,那么:

P[0] = b
P[1] = a
P[i] = P[i-1]P[i-2]

所以:

P[2] = ab
P[3] = aba
P[4] = abaab
P[5] = abaababa
P[6] = abaababaabaab
P[7] = abaababaabaababaababa
P[8] = abaababaabaababaababaabaababaabaab
...

计算出 P[7] 的 F 数组和 Next 数组如下,我们一会儿要用到,你也可以先把它当作找规律的题看看:

P a b a a b a b a a b a a b a b a a b a b a  
n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
F -1 0 0 1 1 2 3 2 3 4 5 6 4 5 6 7 8 9 10 11 7 8
Next -1 0 -1 1 0 -1 3 -1 1 0 -1 6 0 -1 3 -1 1 0 -1 11 -1 8

费波拉契串有几个性质值得注意一下:

Note

文章前面已经假设了我的世界里费波拉契数列下标是从 0 开始的,这里再强调一遍。

  1. strlen(P[n]) == Fibonacci[n] (这个应该很容易理解吧)

  2. 设函数 c(t) 的作用是交换字符串 t 的最后两个字符,例如 c("abcdef") == "abcdfe",那么当 n>=2 时 P[n-1]P[n-2] == c(P[n-2]P[n-1]) :
    n == 2 时这是显然的;
    当 n>2 时可以用数学归纳法证明:
    c(P[n-2]P[n-1]) = P[n-2]c(P[n-1]) = P[n-2]P[n-3]P[n-2] = P[n-1]P[n-2]
  3. 由上面两条性质我们又可以推导出:
    Next[Fibonacci[n]-2] == F[Fibonacci[n]-2] == Fibonacci[n-1]-2, n>=2
    这是因为,可以把一个费波拉契串分解开:
    P[n] == P[n-1]P[n-2] == P[n-2]P[n-3]P[n-2] == c(P[n-3]P[n-2])P[n-2] == P[n-3]c(P[n-2])P[n-2] == ...
    具体以 P[7] 为例,
    P[7] == ab-ab-ba---ab------ba
    其中省略掉的部分根据 性质2 表现出的规律与前方相等,因此如果在 P[7] 的最后一个字符 b 处发生了不匹配,接下来应该在下列位置重新试着匹配:
    ab-a--b----a-------b-
    它们正好占据着 Fibonacci[2]-2, Fibonacci[3]-2, .. , Fibonacci[7]-2, ... 的位置。

因此,如果在费波拉契串的第 n 位,n == Fibonacci[k]-2 上发生了不匹配,接下来则还需要 k-1 次比较;

又因为 Fibonacci[k-1] == (φk - (-1)kφ-k)/sqrt(5) == round( φk / sqrt(5) ), 于是可以解得 k ~ logφ(n),其中 φ 是黄金比例 (1+sqrt(5))/2 == 1.618...

—— k 便是文本串上的一个字符的最多比较次数。

8   为什么费波拉契串这么神奇

为了证明为何费波拉契串就是使停顿时间最长的模式串,我们再看看 MP 算法的基本思想:将字符串已匹配的一个前缀和一个对应的后缀匹配。

假设字符串 S 有且仅有一个相等的前缀和后缀(设为 a ),那么 S 可以表示为

S = aB = Ca

再假设 a 本身也有且仅有一个相等的前缀和后缀(设为 e ),那么 a 也可以表示为

a = eF = Ge

对应 MP 算法,匹配 Ca 时若在 a 之后失败,则会将 aB 的 a 与其对齐:

Cax
Cay

==>

Cax
 aBy

若在 B 的第一个字符处再次失败,则下一次对齐是这样的:

CGex
 GeBy

==>

CGex
  eFBy

KMP 算法在这里还要求 F 的第一个字符和 B 的第一个字符不等(否则会跳过这一段)

我们很容易可以证明想要 KMP 算法在这个地方停留尽可能长的时间需要满足 |S| <= |e| + |a| :因为若 |e| + |a| > |S|,那么令 d = |e| + |a| - |S| 则 Ca = CGe = aB 算式中, a 和 e 将有长度为 d 的重叠,于是 B 的第一个字符等于 e[d];同理,在 aB = eFB = Ca 算式中,可以得到 F 的第一个字符为 a[d],由 a = eF 可以得到 a[d] = e[d],和 KMP 的要求不符。

于是 |S| == |e| + |a| 是使 KMP 算法的停顿时间达到最长的极限情况——很容易发现,满足这条件的便是费波拉契串了。

聊聊 memcpy

嗯 .. 提到 memcpy 你是不是就条件反射的想到了这样的东西呢:

void * memcpy(void * dst, const void * src, size_t size)
{
    size_t i=0;
    unsigned char * _dst = dst;
    const unsigned char * _src = src;
    for(; i<size; ++i) {
        _dst[i] = _src[i];
    }
    return dst;
}

其实吧,memcpy 里面还是很有学问的 ... 首先从一个新闻说起吧

最近某个由 memcpy 引发的血案似乎引起了不少人关注,事情大概是 glibc 使用了一个号称经过优化的 memcpy 函数,这个函数对源地址和目标地址 重叠 的情况没有处理(虽然实际上 memcpy 函数也不要求处理这种情况),于是 Adobe Flash Player 闹情绪了,原因在于那个 tricky 的 memcpy 函数作风比较诡异,在某些机器上它是把数据 从后往前 拷贝的,对此 Linus 的评价是:

That said - why the heck would you ever do memcpy() backwards? Things like automatic prefetchers usually work better for the "natural" patterns, so a backwards memcpy is generally a bad thing to do unless you have some active reason for it, like doing a memmove()

不过我其实更觉得这完全是 Adobe 的问题,因为如果从后往前拷贝它就会出问题,那么遇到下面这种情况,从前往后拷贝也可以照样整垮它:

A: bbbbbaaaaa
B:      aaaaabbbbb

memcpy(B,A,10);

Anyway,事情到现在已经基本上算是平息了: glibc 相信问题不在于 memcpy 而在于 Adobe 没有正确使用它,问题不再讨论;Linus 则给出了一个暂行方案,他的那个方案已经非常优秀了以至于想想很有可能破坏掉读者的挑战欲望,所以还是留着过一会儿再说吧……

现在我们回到主题上,memcpy,怎么样让它更快些呢。

首先, dst[i] = src[i] 这一句实际上的效率就相当于 *(dst+i) = *(src+i) , 这里的加法显得有点太过分了。稍微修改一下下:

void * memcpy(void * dst, const void * src, size_t size)
{
    unsigned char * _dst = dst;
    const unsigned char * _src = src, * _src_end;
    for(_src_end = _src+size; _src<_src_end; ++_src, ++_dst) {
            * _dst = * _src;
    }
    return dst;
}

Andorid 上面的 libc 似乎就是类似这样的实现了。

但是,其实还不够,拷贝 size 个字节,比较运算就执行了 size 次,可不可以少一点?

答案:Yes!

方案一:Duff's Device

Duff's Device 是个非常 tricky 的东西,以至于你第一眼见到很可能怀疑“这货真能通过编译?”

void * memcpy(void * dst, const void * src, size_t size)
{
    register n=(size+7)/8;
    register unsigned char * _dst = dst;
    register const unsigned char * _src = src;
    switch(size%8){
        case 0:     do{     *_dst++ = *_src++;
        case 7:             *_dst++ = *_src++;
        case 6:             *_dst++ = *_src++;
        case 5:             *_dst++ = *_src++;
        case 4:             *_dst++ = *_src++;
        case 3:             *_dst++ = *_src++;
        case 2:             *_dst++ = *_src++;
        case 1:             *_dst++ = *_src++;
        } while(--n>0);
    }
}

这样,被 switch 一次之后便陷入了 do while 循环;这里 case 0 - case 7 如果不够还可以再加, 此段代码相当于把上面的小于比较省掉了 7/8 。

方案二:不要一个字节一个字节操作,而要一个字一个字的处理:

通常一个机器字有好几个字节长 —— 比如 32 位机器上是 4 个字节,64 位机器上是 8 字节。

将每 n 个字节作为一个整体“超级字符”进行操作,在这种场合算是很常见的应用了:

void * memcpy(void * dst, const void * src, size_t size)
{
    void * orig = dst;
    asm volatile("rep ; movsq"
        :"=D" (dst), "=S" (src)
        :"0" (dst), "1" (src), "c" (size >> 3)
        :"memory");
    asm volatile("rep ; movsb"
        :"=D" (dst), "=S" (src)
        :"0" (dst), "1" (src), "c" (size & 7)
        :"memory");
    return orig;
}

解释一下,这里汇编代码的原理也就是先把 size/8 个字(size>>3)拷贝过去(movsq), 再把后 size%8 个字节(size&7)拷贝过去(movsb)

这个就是 Linus 给出的临时方案了。

另外这里一个字是八个字节,那是因为这里出现症状的机器是 64 位的。

方案三(其实不算方案三):结合以上二者

以字为单位,用 Duff's Device 操作…… 这个想想就觉得挺牛的,Newlib 似乎就是这么玩的了:

/* Nonzero if either X or Y is not aligned on a "long" boundary.  */
#define UNALIGNED(X, Y) \
  (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1)))

/* How many bytes are copied each iteration of the 4X unrolled loop.  */
#define BIGBLOCKSIZE    (sizeof (long) << 2)

/* How many bytes are copied each iteration of the word copy loop.  */
#define LITTLEBLOCKSIZE (sizeof (long))

/* Threshhold for punting to the byte copier.  */
#define TOO_SMALL(LEN)  ((LEN) < BIGBLOCKSIZE)

_PTR
_DEFUN (memcpy, (dst0, src0, len0),
        _PTR dst0 _AND
        _CONST _PTR src0 _AND
        size_t len0)
{
#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__)
  char *dst = (char *) dst0;
  char *src = (char *) src0;

  _PTR save = dst0;

  while (len0--)
    {
      *dst++ = *src++;
    }

  return save;
#else
  char *dst = dst0;
  _CONST char *src = src0;
  long *aligned_dst;
  _CONST long *aligned_src;

  /* If the size is small, or either SRC or DST is unaligned,
     then punt into the byte copy loop.  This should be rare.  */
  if (!TOO_SMALL(len0) && !UNALIGNED (src, dst))
    {
      aligned_dst = (long*)dst;
      aligned_src = (long*)src;

      /* Copy 4X long words at a time if possible.  */
      while (len0 >= BIGBLOCKSIZE)
        {
          *aligned_dst++ = *aligned_src++;
          *aligned_dst++ = *aligned_src++;
          *aligned_dst++ = *aligned_src++;
          *aligned_dst++ = *aligned_src++;
          len0 -= BIGBLOCKSIZE;
        }

      /* Copy one long word at a time if possible.  */
      while (len0 >= LITTLEBLOCKSIZE)
        {
          *aligned_dst++ = *aligned_src++;
          len0 -= LITTLEBLOCKSIZE;
        }

       /* Pick up any residual with a byte copier.  */
      dst = (char*)aligned_dst;
      src = (char*)aligned_src;
    }

  while (len0--)
    *dst++ = *src++;

  return dst0;
#endif /* not PREFER_SIZE_OVER_SPEED */
}

方案四,有请硬件牛

翻出 glibc 2.9 的代码,它的代码看上去倒是不长:

void *
memcpy (dstpp, srcpp, len)
     void *dstpp;
     const void *srcpp;
     size_t len;
{
  unsigned long int dstp = (long int) dstpp;
  unsigned long int srcp = (long int) srcpp;

  /* Copy from the beginning to the end.  */

  /* If there not too few bytes to copy, use word copy.  */
  if (len >= OP_T_THRES)
    {
      /* Copy just a few bytes to make DSTP aligned.  */
      len -= (-dstp) % OPSIZ;
      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

      /* Copy whole pages from SRCP to DSTP by virtual address manipulation,
     as much as possible.  */

      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);

      /* Copy from SRCP to DSTP taking advantage of the known alignment of
     DSTP.  Number of bytes remaining is put in the third argument,
     i.e. in LEN.  This number may vary from machine to machine.  */

      WORD_COPY_FWD (dstp, srcp, len, len);

      /* Fall out and copy the tail.  */
    }

  /* There are just a few bytes to copy.  Use byte memory operations.  */
  BYTE_COPY_FWD (dstp, srcp, len);

  return dstpp;
}

从注释可以看出,现在它在玩的都是些硬件相关的 trick 了 (更别提 Linux Kernel 中的那些对应不同处理器的汇编实现),于是我发现我似乎不太适合再继续挖下去了。




文章最后,我想以高中语文考试作文体结尾,非常感谢所有这些 —— hacker 也好 geek 也好 —— 做了这么多有意思又有意义的工作, 让我们有 Linux 这么优秀的操作系统可以用;正是由于这样的细节,这样的竞争气氛,这一点一滴的积累,才有了( 我自己的测试中 )比 Win7 快上近 1/3 的速度,让人没有理由再质疑开源的力量。

最长回文子串 [后缀树应用之三]

为什么有很多人讨论这个问题呢——仅仅因为它的学术价值吗?

 

通过后缀树查找最长回文子串应该说是很容易的:将字符串 s 和 inverse(s) 加入到后缀树中,找出最深的、在正反两个字符串中出现的位置相匹配的节点就是了。

实际玩了一下发现不是那么有意思。

《银河系漫游指南》中的最长回文子串:

mmmmmmmmmmmmmmmmmmmmm

《宇宙尽头的餐馆》 - 

rrrrrrrrrrrr

《生命,宇宙,以及一切》 - 

mmmmm mmm mmmmm

《魔戒》 -

e did i did e

《飘》 - 

t now i won t

《1984》 -

ton did not

...

 

大概就是这样吧。

关于 iPhone WebApp

总的说,写起来还是挺方便的,就拿上篇的记忆游戏来说,加上

<meta name="viewport" content="width=320,user-scalable=false,device-width=320,minimum-scale=1,maximum-scale=1" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

这样的标记,然后处理一下 rotate 事件,保存到主屏幕,就已经是个像模像样的 app 了——除了必须得要联网。

 

通过这样那样的途径,我碰巧又发现了其实 HTML 5 具备了离线使用的潜质,于是很迫切的希望让我的程序获得这样的超能力。

好消息是:经过这样那样的努力,我实现了这个愿望:(看!灰行模式!)

webApp

webApp'

坏消息是:由于需要在 .htaccess 或者 apache 的配置文件中添加 text/cache-manifest 的 MIME 类型,而 110mb 不让我这么做;app engine 则似乎根本不让 host HTML 文件,于是目前能使用这个酷功能的有且仅有我自己一人;anyway,你还是可以使用 iSaveWeb 实现同样的效果,Please enjoy: 

http://ifyu.110mb.com/memory-game-iPhone.html

 

最后记几个笔记:

  • 在局域网中无法通过 ip 地址访问 apache 服务器是怎么回事呢,原来除了把防火墙好好调教一番之外还需要在 httpd.conf 中加上 Listen 192.168.XXX.XXX:PORT (这么设计有嘛好处?)。
  • 用于 host 简单的 Web 页面,AnalogX SimpleServer 是个好东西,比 python -> import SimpleHTTPServer -> SimpleHTTPServer.test() 还要方便。

最长公共子串 [后缀树应用之二]

1. 我干了什么

我写了个小程序,用 1.276 秒找出了《银河系漫游指南》和《宇宙尽头的餐馆》的最长公共子串:

the history of every major galactic civilization tends to pass through three distinct and recognizable phases, those of survival, inquiry and sophistication, otherwise known as the how, why and where phases.

用 1.266 秒找出了《宇宙尽头的餐馆》和《再见,谢谢所有的鱼》的最长公共子串:

in the uncharted backwaters of the unfashionable end of the western spiral arm of the galaxy

用 8.671 秒找出了《魔戒》和《1984》的最长公共子串:

. it was inevitable that they should make

用 6.728 秒找出了《飘》和《基本无害》的最长公共子串:

more than anything else in the world.

...

我发现这是件很好玩的事情~

2. 怎么做的

Again,有了 后缀树 ,这样的工作太简单了:

/*
 * Author  : If
 * Date    : Oct/16th/2010
 * License : Boost License v1

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license  (the "Software")  to use,  reproduce,  display,  distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include "suffixtree.h"
#include <algorithm>
#include <iterator>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <ctime>
using namespace std;

static void ticToc_(bool s)
{
    static clock_t start_ = clock();
    if(s) {
        start_ = clock();
    } else {
        clock_t end_=clock();
        std::cerr
            << (end_ - start_) << " ticks"
            << "    (" << (end_ - start_) * (1000.)/CLOCKS_PER_SEC <<"ms)"
            << std::endl;
    }
}

inline void tic()
{
    ticToc_(true);
}

inline void toc()
{
    ticToc_(false);
}

struct Info
{
    vector<int> belongTo;
};

typedef Node<char, Info>       NodeT;
typedef SuffixTree<char, Info> STree;

// 初始化节点的从属属性。
void initInfo(NodeT& leaf)
{
    NodeT* node = leaf.parent;
    int    nth  = leaf.belongTo;
    while ( node->parent!=node && (
            node->info.belongTo.empty() ||
            node->info.belongTo.back()!=nth )) {
        node->info.belongTo.push_back(nth);
        node=node->parent;
    }
}

void printPath(NodeT const* node)
{
    if(node->parent!=node)
        printPath(node->parent);
    copy(node->str, node->str+node->len,
         ostream_iterator<char>(cout));
}

// 将连续的空白字符转换成单个空格;将大写全转换为小写。
void filter(char * text, int & len)
{
    char*p=text,*q=text;
    for(;q<text+len;++p,++q){
        if(*q>='A'&&*q<='Z') {
            *p=*q-'A'+'a';
        } else if(isspace(*q)) {
            *p=' ';
            while(isspace(*q))
                ++q;
            --q;
        } else {
            *p=*q;
        }
    }
    len = p-text;
}

struct LetsRock
{
    NodeT const*& result;
    int   longestNow;
    int   nStrings;
    LetsRock(int n, NodeT const*& root):
        result(root),
        longestNow(0),
        nStrings(n)
    { }
    void operator()(NodeT const& node) {
        if (node.depth + node.len > longestNow &&
            node.info.belongTo.size()==nStrings) {
            longestNow = node.depth + node.len;
            result     = &node;
        }
    }
};

int main(int c,char**v)
{
    STree tree;
    tic();
    for(int i=1;i<c;++i) {
        cerr<<"reading "<<v[i]<<" ...\n";
        FILE*  file = fopen(v[i],"r");
        string content;
        char   buf[8192];
        int    bytesRead=0;
        int    bytesTotal=0;
        while((bytesRead=fread(buf,1,8192,file))==8192) {
            filter(buf,bytesRead);
            content.append(buf,bytesRead);
            bytesTotal += bytesRead;
        }
        if(bytesRead) {
            filter(buf,bytesRead);
            content.append(buf,bytesRead);
            bytesTotal += bytesRead;
        }
        fclose(file);
        cerr<<"done. ( " << bytesTotal <<" bytes )\n";
        cerr<<"adding to suffix tree ...\n";
        tree.addString(content);
        cerr<<"done.\n";
    }
    NodeT const* ans = tree.root();
    tree.eachLeaf(initInfo);
    LetsRock fun(c-1,ans);
    tree.dfs(fun);
    printPath(ans);
    cout<<endl;
    toc();
    return 0;
}

代码压缩工具原型 [后缀树应用之一]

嗯,对我来说是代码压缩工具原型;对大多数地球生物来说这其实是最长不重叠重复子串问题

1. 我干了什么

我写了个程序,运行时内存占用峰值为 323 MB,耗时 6.582s,从 2,827,372 字节的《魔戒》全本中找出了不算空格、大小写敏感的情况下出现两次或更多的最长子串:

All that is gold does not glitter,
Not all those who wander are lost;
The old that is strong does not wither,
Deep roots are not reached by the frost.
 
From the ashes a fire shall be woken,
A light from the shadows shall spring;
Renewed shall be blade that was broken,
The crownless again shall be king.

耗时 7.022 秒,内存占用峰值 351 MB,从 3,872,493 字节的 sqlite 3.6.22 的合并 c 文件中找到了最长重复子串:(忽略空格)

.c****************//**************Beginfileos_common.h********************
****** *************//***2004May22****Theauthordisclaimscopyrighttothissou
rcecode.Inpla ceof**alegalnotice,hereisablessing:****Mayyoudogoodandnotevi
l.**Mayyoufindforgiv enessforyourselfandforgiveothers.**Mayyousharefreely,
nevertakingmorethanyougive.  *********************************************
*********************************** ****Thisfilecontainsmacrosandalittlebi
tofcodethatiscommonto**alloftheplatform-sp ecificfiles(os_*.c)andis#includ
edintothose**files.****Thisfileshouldbe#includedb ytheos_*.cfilesonly.Itis
nota**generalpurposeheaderfile.*/#ifndef_OS_COMMON_H_#de fine_OS_COMMON_H_
/***AtleasttwobugshaveslippedinbecausewechangedtheMEMORY_DEBUG* *macrotoSQ
LITE_DEBUGandsomeoldermakefileshavenotyetmadethe**switch.Thefollowingc ode
shouldcatchthisproblematcompile-time.*/#ifdefMEMORY_DEBUG#error&quot;TheME
MORY_DEB UGmacroisobsolete.UseSQLITE_DEBUGinstead.&quot;#endif#ifdefSQLITE
_DEBUGSQLITE_PRIVATE intsqlite3OSTrace=0;#defineOSTRACE1(X)if(sqlite3OSTra
ce)sqlite3DebugPrintf(X)#de fineOSTRACE2(X,Y)if(sqlite3OSTrace)sqlite3Debu
gPrintf(X,Y)#defineOSTRACE3(X,Y,Z) if(sqlite3OSTrace)sqlite3DebugPrintf(X,
Y,Z)#defineOSTRACE4(X,Y,Z,A)if(sqlite3OST race)sqlite3DebugPrintf(X,Y,Z,A)
#defineOSTRACE5(X,Y,Z,A,B)if(sqlite3OSTrace)sqli te3DebugPrintf(X,Y,Z,A,B)
#defineOSTRACE6(X,Y,Z,A,B,C)\if(sqlite3OSTrace)sqlite3D ebugPrintf(X,Y,Z,A
,B,C)#defineOSTRACE7(X,Y,Z,A,B,C,D)\if(sqlite3OSTrace)sqlite3D ebugPrintf(
X,Y,Z,A,B,C,D)#else#defineOSTRACE1(X)#defineOSTRACE2(X,Y)#defineOSTRA CE3(
X,Y,Z)#defineOSTRACE4(X,Y,Z,A)#defineOSTRACE5(X,Y,Z,A,B)#defineOSTRACE6(X,
Y, Z,A,B,C)#defineOSTRACE7(X,Y,Z,A,B,C,D)#endif/***Macrosforperformancetra
cing.Norm allyturnedoff.Onlyworks**oni486hardware.*/#ifdefSQLITE_PERFORMAN
CE_TRACE/***hwti me.hcontainsinlineassemblercodeforimplementing**high-perf
ormancetimingroutines.* //**************Includehwtime.hinthemiddleofos_com
mon.h****************//******* *******Beginfilehwtime.h*******************
***********************//***2008May27 ****Theauthordisclaimscopyrighttothi
ssourcecode.Inplaceof**alegalnotice,hereisab lessing:****Mayyoudogoodandno
tevil.**Mayyoufindforgivenessforyourselfandforgiveo thers.**Mayyousharefre
ely,nevertakingmorethanyougive.*************************** ***************
******************************************Thisfilecontainsinlinea smcodefo
rretrieving&quot;high-performance&quot;**countersforx86classCPUs.*/#ifndef
_HWTIME_ H_#define_HWTIME_H_/***Thefollowingroutineonlyworksonpentium-clas
s(ornewer)proce ssors.**ItusestheRDTSCopcodetoreadthecyclecountvalueoutoft
he**processorandreturn sthatvalue.Thiscanbeusedforhigh-res**profiling.*/#i
f(defined(__GNUC__)||defined( _MSC_VER))&amp;&amp;\(defined(i386)||defined
(__i386__)||defined(_M_IX86))#ifdefined(__GN UC__)__inline__sqlite_uint64s
qlite3Hwtime(void){unsignedintlo,hi;__asm____volati le__(&quot;rdtsc&quot;
:&quot;=a&quot;(lo),&quot;=d&quot;(hi));return(sqlite_uint64)hi&lt;&lt;32|
lo;}#elifdefined(_MS C_VER)__declspec(naked)__inlinesqlite_uint64__cdeclsq
lite3Hwtime(void){__asm{rdt scret;returnvalueatEDX:EAX}}#endif#elif(define
d(__GNUC__)&amp;&amp;defined(__x86_64__))_ _inline__sqlite_uint64sqlite3Hw
time(void){unsignedlongval;__asm____volatile__(&quot;r dtsc&quot;:&quot;=A
&quot;(val));returnval;}#elif(defined(__GNUC__)&amp;&amp;defined(__ppc__))
__inline__ sqlite_uint64sqlite3Hwtime(void){unsignedlonglongretval;unsigne
dlongjunk;__asm__ __volatile__(&quot;\n\1:mftbu%1\n\mftb%L0\n\mftbu%0\n\cm
pw%0,%1\n\bne1b&quot;:&quot;=r&quot;(retval) ,&quot;=r&quot;(junk));return
retval;}#else#errorNeedimplementationofsqlite3Hwtime()foryour platform./**
*Tocompilewithoutimplementingsqlite3Hwtime()foryourplatform,**youcan remov
etheabove#errorandusethefollowing**stubfunction.Youwilllosetimingsupportfo
r many**ofthedebuggingandtestingutilities,butitshouldat**leastcompileandru
n.*/SQLI TE_PRIVATEsqlite_uint64sqlite3Hwtime(void){return((sqlite_uint64)
0);}#endif#endi f/*!defined(_HWTIME_H_)*//**************Endofhwtime.h*****
********************** *******************//**************Continuingwherew
eleftoffinos_common.h******** **********/staticsqlite_uint64g_start;static
sqlite_uint64g_elapsed;#defineTIMER_ STARTg_start=sqlite3Hwtime()#defineTI
MER_ENDg_elapsed=sqlite3Hwtime()-g_start#de fineTIMER_ELAPSEDg_elapsed#els
e#defineTIMER_START#defineTIMER_END#defineTIMER_EL APSED((sqlite_uint64)0)
#endif/***IfwecompilewiththeSQLITE_TESTmacroset,thenthefo llowingblock**of
codewillgiveustheabilitytosimulateadiskI/Oerror.This**isusedfort estingthe
I/Orecoverylogic.*/#ifdefSQLITE_TESTSQLITE_APIintsqlite3_io_error_hit=0 ;/
*TotalnumberofI/OErrors*/SQLITE_APIintsqlite3_io_error_hardhit=0;/*Numbero
fnon -benignerrors*/SQLITE_APIintsqlite3_io_error_pending=0;/*Countdowntof
irstI/Oerro r*/SQLITE_APIintsqlite3_io_error_persist=0;/*TrueifI/Oerrorspe
rsist*/SQLITE_APIi ntsqlite3_io_error_benign=0;/*Trueiferrorsarebenign*/SQ
LITE_APIintsqlite3_diskfu ll_pending=0;SQLITE_APIintsqlite3_diskfull=0;#de
fineSimulateIOErrorBenign(X)sqli te3_io_error_benign=(X)#defineSimulateIOE
rror(CODE)\if((sqlite3_io_error_persist &amp;&amp;sqlite3_io_error_hit)\||
sqlite3_io_error_pending--==1)\{local_ioerr();CODE;}st aticvoidlocal_ioerr
(){IOTRACE((&quot;IOERR\n&quot;));sqlite3_io_error_hit++;if(!sqlite3_io _e
rror_benign)sqlite3_io_error_hardhit++;}#defineSimulateDiskfullError(CODE)
\if( sqlite3_diskfull_pending){\if(sqlite3_diskfull_pending==1){\local_ioe
rr();\sqlit e3_diskfull=1;\sqlite3_io_error_hit=1;\CODE;\}else{\sqlite3_di
skfull_pending--;\ }\}#else#defineSimulateIOErrorBenign(X)#defineSimulateI
OError(A)#defineSimulateD iskfullError(A)#endif/***Whentesting,keepacounto
fthenumberofopenfiles.*/#ifdefSQ LITE_TESTSQLITE_APIintsqlite3_open_file_c
ount=0;#defineOpenCounter(X)sqlite3_ope n_file_count+=(X)#else#defineOpenC
ounter(X)#endif#endif/*!defined(_OS_COMMON_H_) *//**************Endofos_co
mmon.h*******************************************//** ************Continui
ngwhereweleftoffinos_ 

正如之前在 后缀树的介绍 中说过的那样,俺想要弄出个能压缩 C 程序代码的工具,而上面的就是原型了:找出出现多次且较长的子串, #define 一下,重复多次,差不多了。

2. 怎么做的

有了后缀树,这样的工作可真是简单啊:

/*
 * Author  : If
 * Date    : Oct/14th/2010
 * Version : 0.0.1
 */
/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license  (the "Software")  to use,  reproduce,  display,  distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include "suffixtree.h"
#include <algorithm>
#include <iterator>
#include <iostream>
#include <string>
#include <cmath>
#include <ctime>
using namespace std;

static void ticToc_(bool s)
{
    static clock_t start_ = clock();
    if(s) {
        start_ = clock();
    } else {
        clock_t end_=clock();
        std::cerr
            << (end_ - start_) << " ticks"
            << "    (" << (end_ - start_) * (1000.)/CLOCKS_PER_SEC <<"ms)"
            << std::endl;
    }
}

inline void tic()
{
    ticToc_(true);
}

inline void toc()
{
    ticToc_(false);
}


struct Info {
    int cv,depth,isLeaf;
    Info():cv(-1),depth(-1),isLeaf(0){}
};

typedef Node<char,Info> NodeT;

void initInfo(NodeT& node) {
    node.info.depth = node.depth + node.len;
    if(node.branches.empty()){
        node.info.isLeaf = 1;
        node.info.cv = node.leafNum;
    } else {
        node.info.isLeaf = 0;
        NodeT* p = node.branches[0];
        if(p->info.cv==-1) {
            initInfo(*p);
        }
        node.info.cv = p->info.cv;
    }
}

void printPath(NodeT const* node)
{
    if(node->parent!=node) {
        printPath(node->parent);
    }
    copy(node->str, node->str+node->len, ostream_iterator<char>(cout));
}

struct LetsHaveFun
{
    NodeT const*& result;
    LetsHaveFun(NodeT const*& root):result(root){}
    void operator()(NodeT& node){
        if(!node.info.isLeaf &&  // more than once
            node.depth != 0 &&   // not root
            node.info.depth > result->info.depth && // better
            abs(node.info.cv - node.branches.back()->info.cv) >=
                node.info.depth  // do not overlap
        ) {
            this->result = &node;
        }
    }
};

int main(int c,char**v) {
    if(c>2)
        return 1;
    SuffixTree<char, Info> stree;
    NodeT const* ans = stree.root();
    LetsHaveFun fun(ans);
    string source;
    if(c==2&&v[1][0]=='-') {
        if(v[1][1]=='S') // do not skip white space
            cin>>noskipws;
        else if(v[1][1]=='s')
            cin>>skipws;
        else
            return 1;
    } else {
        return 1;
    }

    copy(istream_iterator<char>(cin), istream_iterator<char>(),
         back_inserter(source));
    tic();
    stree.addString(source);
    stree.dfs(initInfo);
    stree.dfs(fun);
    printPath(ans);
    cout<<endl;
    toc();
}

3. Question

这次有问题顺便想要请教一下高人们,在上面的代码中有一段让我很不爽:

struct LetsHaveFun
{
    NodeT const*& result;
    ...

我的本意是想用 NodeT const* result ; 然而那么做的话 stree.dfs(fun) 这句话结束之后 fun.result 又回到了 root (LetsHaveFun::operator() 中 result 的确会变的!),百思不得其解,似乎碰到了某个语法的黑暗角落;然而在这个问题上 VC2008 和 GCC 4.5.0 竟然表现得出奇的一致以至于我开始怀疑自己了,谁来救救我?

4. PostScript

suffixtree.h 全部内容在我的 后缀树的介绍 中。

5. Update

Ubuntu 威武,同样的代码,同样的文件系统,同样的计算机配置,同样的编译器,同样的编译选项,在 Ubuntu 上面,此处(Win7)耗时 6.5 秒的程序变得只要 4.5 秒了,Astonishing

CKEditor Plugin For Micolog

个人比较喜欢 CKEditor, 所以就给 micolog 整了个 ckeditor 插件: 

http://micolog.appspot.com/plugins/view?key=agdtaWNvbG9nchsLEgZQbHVnaW4iD0NLRWRpdG9yIHBsdWdpbgw

 

其实 tinymce 也还可以啦,只是我做好这个插件之后才发现原来在 /views/admin/entry.html 中把 tinymce 的初始化代码改成这样,它也完全可以接受了:

(主要是它之前总是自作主张的把 URL 改成相对路径 —— 而且还是个错的,还有 html 的预览把 <p> 弄没了,结构一塌糊涂)

<script type="text/javascript">
$('#content').ready(function() {
    $('.editor-toolbar').show();
    var editor = null;
    $('#edButtonPreview').click(function(){if(editor);else initTinymce();});
    $('#edButtonHTML').click(function(){if(editor)tinymce.execCommand('mceRemoveControl',false,'content'); editor=null;});

    function initTinymce(){
        editor = $('#content').tinymce({
            script_url : '/tinymce/tiny_mce.js',
            theme : "advanced",
            
            plugins : "safari,pagebreak,save,advhr,advimage,advlink, inlinepopups,media,directionality,visualchars,nonbreaking,fullscreen,preview,autoresize",

            theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,bullist,numlist,blockquote,|,forecolor,backcolor,|,justifyleft,justifycenter,justifyright,justifyfull,|,link,unlink,image,code,preview,|,fullscreen",
            theme_advanced_buttons2:"styleselect,formatselect,fontselect,fontsizeselect,|,pastetext,pasteword,removeformat,|,media,charmap,|,outdent,indent,|,undo,redo",
            theme_advanced_buttons3:"",
            theme_advanced_buttons4:"",

            theme_advanced_toolbar_location : "top",
            theme_advanced_toolbar_align : "left",
            theme_advanced_statusbar_location : "bottom",
            theme_advanced_resizing : true,
            
            content_css : "/tinymce/wordpress.css",
            extended_valid_elements : "textarea[cols|rows|disabled|name|readonly|class],script[src,type],canvas",
 
            file_browser_callback: 'micolog_file',
            relative_urls: false
        });
    }
    initTinymce();
});
function micolog_file(field_name, url, type, win){
   if (type=='image')
       {ext='jpg|png|jpeg|gif|svg';}
   else if (type=='mdeia')
       {ext='swf|wmv|avi|wma|mp3|mid|asf|rm|rmvb|flv';}
   else
       {ext="*";}

    tinyMCE.activeEditor.windowManager.open({
        file : '/admin/uploadex?ext='+ext,  
        title : '{%trans "Files"%}',
        width : 420,
        height : 200,
        resizable : "no",
        inline : "yes",
        popup_css: false,
        close_previous : "no"
    }, {
        window : win,  
        input : field_name  
    });
    return false;
}
</script>

50 题

写了个游戏,基本上和 iPhone 上的 >=< 一模一样吧——由于我很喜欢后者,所以自己也做了个,便于在电脑上玩。 玩法很简单:小于 10 的式子丢到左边,大于 10 的式子丢到右边,等于 10 的则丢到下面,看谁速度快。一起来试试吧:

下载 swf


  还有个更具有挑战性的版本,玩的时候建议把音量开到最大:

下载 swf

 

Update:

其实,还有个比较符合宅男品位的:

下载 swf

 

Update Oct/23th/2010:

  • 多谢 枪毙自己 童鞋帮我找到的链接(AS3输入法的切换与限制),俺现在加上了号称能关闭输入法的代码——只是由于俺自己压根就没能在游戏界面中打开过输入法,所以效果无从验证了,希望大家玩得愉快,有问题欢迎反馈,而如果像枪毙自己童鞋这样还帮我找到了解决方案的话我会感动得一塌糊涂的。

  • 另,也欢迎大家把 flash 游戏下载下去玩;flash 的独立播放器看上去似乎下载不到了,但那是假象,其实只是被 adobe 藏起来了而已,它在这里:
    http://download.macromedia.com/pub/flashplayer/updaters/10/flashplayer_10_sa.exe

  • 最后,我自己目前的最好成绩是 16.65 秒全对,欢迎挑战。

Update Oct/24th/2010 :

Suffix Tree 学习笔记 I

在这篇文章中你将会看到:





* 什么是后缀树

* 后缀树有啥用

* 如何构建后缀树

* 构建后缀树的算法复杂度分析

* 具体实现的源代码

* 以及更多









文章本身很长……还是点进来看吧…… ..more