MP/KMP 算法详解 [草稿]

MP/KMP 算法详解

By If

1   Prelog

本篇文章主要针对的是对字符串匹配有兴趣的生物以及被某版本数据结构与算法教材中的 KMP 算法讲解弄得不知所云但与此同时却还难能可贵地保持着旺盛求知欲的不幸生在了错误年代的可怜童鞋,其他生物阅读本文前请慎重考虑因为它 可能对您的大脑(如果有)、小脑甚至包括脊髓都造成严重且不可恢复的创伤

2   Notations

下文可能会提到 “模式串”、“文本串”、“窗口” 这些词,它们的定义如下,如果这些文字使你头晕,请及时做好救治准备。

模式串、文本串
所谓 模式串 ,是指你想要找到(或者得到位置 &etc.)的字符串;而 文本串 ,则是指搜索的目标字符串。
比如说你要在 "lucky dog" 中寻找 "dog" ,那么 "dog" 是模式串, "lucky dog" 则是文本串;
而你若要在 "If is a lucky dog" 中寻找 "lucky dog" ,那么 "lucky dog" 便成了模式串, "If is a lucky dog" 则是文本串。

Understand?

窗口
无论用什么样的搜索算法,在搜索的过程中,总是需要将模式串与文本串进行比较,它们对齐的那部分区域,也就是们关心的那块区域,咱称为 窗口

3   Main Idea

MP/KMP 字符串搜索算法的思想精华在于利用已经匹配的部分包含的信息,加速搜索的过程。

嗯——已经匹配的部分包含什么信息?

它已经匹配了!

举例说,在某个字符串中查找子串 A B C D A B D A C 时,如果遇到 A B C D A B ,而紧跟其后的不是 D ,这时候我们可以将窗口右移四位(而不是一位),因为既然 A B C D A B 已经匹配了, 那么移动当前窗口之后 已经匹配过的地方 肯定需要保证 依然匹配 ,这里最好的做法即让 A B 相互对齐:

. . A B C D A B ? . . . . . .
    A B C D A B D A C

=>

. . A B C D A B ? . . . . . .
            A B C D A B D A C

因为,看呀,如果只右移一位,那么:

. . . . . . . . . . . . . . . // 先不用管这个字符串
    A B C D A B . . .         // 已经匹配的部分
=>    A B C D A B D A C
      |
    糟糕!

如上面所示, A B C D A B 匹配了,那么移动一位之后,第一个字符 A 就肯定会对着 B ,绝对不可能在这个地方找到匹配

右移两位、三位或者四位时发生的状况可以依此类推;而右移四位时就不同了:

. . . . . . . . . . . . . . . // 暂时还不用管这个字符串
    A B C D A B . . .         // 已经匹配的部分
=>          A B C D A B D A C

这个时候才 可能 成功匹配。

4   MP 算法

4.1   原理

MP 算法基于这样一种观察:

Note

注意了,这里以及下面所说的 前缀后缀 都是指 不包括自身 的“真”前缀或后缀 ( proper prefix/suffix )

发生了不匹配之后,移动窗口时,定要保证 将模式串已匹配部分的一个前缀和一个相同的后缀对齐,并使这个前缀尽可能长

什么意思?

首先让我们列出模式串 A B C D A B 的所有前缀:

0: /*Empty*/
1: A
2: A B
3: A B C
4: A B C D
5: A B C D A

我们再列出它所有的后缀:

0: /*Empty*/
1: B
2: A B
3: D A B
4: C D A B
5: B C D A B

发现前缀 A B == 后缀 A B ,将它们对齐(即,接下来直接从第 3 位开始比较),完美了,前两位不必重复比较了。

原理上说也不难理解: 从左向右移动窗口的过程就是用前缀去匹配后缀的过程 ,而第一次匹配成功的肯定是最长的相同前缀/后缀 —— 在上例中,两个空字串也相等,可是如果将它们对齐的话那可就“移过头了”。

这么看,我们发现,在模式串的每一个位置上,匹配失败之后能最大限度的将窗口移动多少位 —— 即,与什么位置对齐, 只与模式串在该位置前方的子串有关 ,与文本串无关,与模式串在该点之后的字符也无关。

于是,自然而然的就想到了,为什么不把这么个失败后对齐的位置存放在一个数组中呢,这样每次匹配失败之后就按照它的指示进行跳转。

F[i] == max{ j | pattern[0:j) == pattern[i-j+1:i+1) and 0<j<i } ,也就是当前位置上保证前缀等于后缀的最大长度。

对于模式串 A B C D A B D A C , F 数组如下:

Index 0 1 2 3 4 5 6 7 8
Pattern A B C D A B D A C
F 0 0 0 0 1 2 0 1 0

继续扯,在位置 i 匹配失败之后,可以将窗口继续右移一位,并从 F[i-1] 位置开始继续比较模式串与文本串(按照定义,pattern[ 0 : F[i-1] ) 已经保证匹配了),用代码表示的话就是这样:

int MP(char const* pattern, char const* text)
{
    int i=0,j=0,m=strlen(pattern);
    int F[m];
    calcF (pattern, F); // 计算跳转数组
    for(;text[i];++i,++j) {
        while(j>=0 && pattern[j]!=text[i]) {
            // 第一个字符不匹配则右移窗口、从 0 开始比较
            if (j==0) {
                j=-1;
                break;
            }
            j = F[j-1]; // 对齐
        }
        if(j>=m-1) {    // 找到了!
            return (i+1-m);
        }
    }
    return -1;
}

—— 多和谐呀!



对了,内个 F 数组怎么算?

4.2   跳转数组的计算

观察一下,希望求出拥有相同后缀的最长前缀,这个过程不也是一个字符串匹配的过程吗:

1.  A B C D A B D A C
    A B C D A B D A C
    |
    0  // 定义为 0

2.    A B C D A B D A C
    A B C D A B D A C
      |
      0 // 匹配部分的长度

3.      A B C D A B D A C
    A B C D A B D A C
        |
        0

4.        A B C D A B D A C
    A B C D A B D A C
          |
          0

5.6.        A B C D A B D A C
    A B C D A B D A C
            | |
            1 2

7.              A B C D A B D A C
    A B C D A B D A C
                |
                0

8.                A B C D A B D A C
    A B C D A B D A C
                  |
                  1

9.                  A B C D A B D A C
    A B C D A B D A C
                    |
                    0

如上面所示,将模式串向右移动,并与自身做比较,在位置 i 上,pattern[i:] 与 pattern 自身相匹配的部分的长度就是 F[i]

注意第6步到第7步! 为什么可以直接右移两位呢?

—— 因为 F[1] 已经算出来了!于是我们可以将之前 MP 算法中的思想用在这里,聪明的你想到了没有?

用代码来说就是这样的,看不懂的话我会很伤心:

void calcF(char const* pattern, int* F)
{
    int i=0,j=0;
    for(;pattern[i];++i,++j) {
        while(j>0 && pattern[j-1]!=pattern[i]) {
            j = F[j-1];
        }
        F[i] = j;
    }
}

4.3   另一种表示

对了有没有人觉得 MP 算法中对于第一个字符不匹配时的特殊处理感觉到很生硬的?

嗯,其实呢,考虑到第一个字符失败时的特殊情况其实也不怎么特殊,不如干脆把这种情况也放到 F 数组中去统一处理好了:

Index 0 1 2 3 4 5 6 7 8 9
Pattern A B C D A B D A C  
F -1 0 0 0 0 1 2 0 1 0

这样,MP 算法表达起来更简单了:

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) {
        while(j>=0 && pattern[j]!=text[i]) {
            j = F[j];   // 对齐
        }
        if(j>=m-1) {    // 找到了!
            return (i+1-m);
        }
    }
    return -1;
}

calcF 也并没有因此变复杂:

void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        F[i+1] = j+1;
    }
}

理解之后就会觉得,这种表示方法比较 “省事”;下面咱就都用这种表示得了。

Nevertheless, 其实它们是一码事。

4.4   可是...

我们再停下来看看 “暴力” 搜索算法 —— 有没有可能暴力算法比 MP 算法还快呢?

答案是 “Yes!”

( 哈哈!你输了吧! )




让我们想象一下在一个字符集很大的串 —— 比如说 UTF-16 字符串吧,中寻找一段模式串;而模式串的第一个字符出现在文本串中的频率根本就不大,那么看看第一次匹配失败时它们两者工作的流程吧:

MP 算法 暴力算法
  1. i>=n?
  2. j>=0? ( 此时 j == 0 )
  3. 比较 pattern[j] 与 text[i]
  4. j = F[j]
  5. j>=0? ( 此时 j == -1 )
  6. j = j+1
  7. j >= m?
  8. i = i+1
  9. 到第 1 步
  1. i>=n?
  2. j = 0
  3. j < m?
  4. 比较 pattern[j] 与 text[i]
  5. i = i+1
  6. 到第 1 步

嗯,所以说,这个地方是有优化空间的,Knuth、Morris、Pratt 的论文 [1] 中有提到,俺就不展开了 —— 因为真正牛B的优化在下面:




5   KMP 算法

其实 MP 算法的效率还有提升的空间,不过从模式串 A B C D A B D A C 中看不明显;我们试试这样一个模式串: A B A B A B C

假设在 A B C A B C A B A B A B C A C 中查找 A B A B A B C ,按照 MP 算法的思想,先算出 F 数组:

Pattern A B A B A B C  
F -1 0 0 1 2 3 4 0

于是查找的过程就是这样的:

1.  A B C A B C A B A B A B C A C
    A B A . . . .

2.  A B C A B C A B A B A B C A C
        A . . . . . .

3.  A B C A B C A B A B A B C A C
          A B A . . . .

4.  A B C A B C A B A B A B C A C
              A . . . . . .

5.  A B C A B C A B A B A B C A C
                A B A B A B C

从第 1 步到第 2 步、从第 3 步到第 4 步,我们发现,字符 AC 的不匹配导致了第一次失败,然后紧接着又直接导致了第二次失败。

如此,我们又惊喜的发现,在 A B 之后若是遇到不是 A 的字符,我们完全可以跳三步!因为跳两步的话算是把 A 对齐了 —— 可是它们会被对齐到一个不是 A 的、将会导致匹配失败的字符上面去。

这样的规则有什么规律呢?我直接放出代码吧:

// 这是我们计算 MP 算法中的 F 数组的函数:
void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        F[i+1] = j+1;
    }
}

睁大眼睛准备找茬喽:

// 只需要改一句话:
void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        // !这里!
        F[i+1] = pattern[j+1] == pattern[i+1]?
                   F[j+1]:
                   j+1;
    }
}

为什么呢?因为对于同一个字符导致的失败,失败在前面应该跳到哪里,到后面就还是应该跳到哪里。

另外,这个时候咱似乎就比较喜欢把这个数组称作 next 数组了 —— 其实还是同一回事。

那么, A B A B A B Cnext 数组如下,请您欣赏:

Index 0 1 2 3 4 5 6  
Pattern A B A B A B C  
Next -1 0 -1 0 -1 0 4 0

6   PS

对了,如果我要找出模式串在文本串中所有的出现怎么办?

提示一下:目前为止 F 数组(或者 next 数组)的最后一个元素我们还没有用到过是不是?

7   References

[1]KNUTH D.E., MORRIS (Jr) J.H., PRATT V.R., 1977, Fast pattern matching in strings, SIAM Journal on Computing 6(1):323-350.
[2]http://www-igm.univ-mlv.fr/~lecroq/string/node8.html#SECTION0080
[3]http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=stringSearching

About KMP

发现很多地方把 KMP 算法介绍得不完全正确,说

text:    a b a b b a b b b . . .
pattern: a b a b a c b

这一次匹配尝试失败之后下一步尝试是:

text:    a b a b b a b b b . . .
pattern:     a b a b a c b

不过 KMP,至少根据 “FAST PATTERN MATCHING IN STRINGS” 论文本身的说法以及 这里 的介绍,比较聪明——它会发现右移两位之后出现在不匹配位置的还是同样一个不匹配的字符。

 所以,实际上可以右移五位:

text:    a b a b b a b b b . . .
pattern:           a b a b . . .

这才是 KMP。

而上面的,貌似是 MP 算法。

 

 

PS,强烈建议看看 Fast Pattern Matching in Strings 这篇论文,写得可好了——18页之后还有个大大的惊喜: Knuth 爷爷在 Postscript 中很详细的介绍并分析了 Boyer-Moore 算法。

 

有人注意到上海科技馆的男厕标识很奇特吗??

几天前翻照片玩,找出了09年10月7号在上海科技馆拍的一张照片,当时我就不理解,目前我还是不理解。

 

iiiiif 拍攝的上海科技馆男厕。

 

如图,求解。

 

——对了,俺这儿 Flickr 有访问障碍,所以看不到图而且又不想那个啥的话:网易 || 原图

也可以考虑围观一下我那个只谈风月的blogbus :: 那一次的上海之行

非空 Young 氏矩阵:O(log(n)^2) Can Do

<UPDATE>
说明,由于数据规模是 n*m, 所以复杂度是 O(log(n) * log(m*n));不妨设 n >= m,则 O(log(m*n)) = O(log(n*n)) = O(2*log(n)) = O(log(n)) —— 突然想到 m 时我还以为自己错了呢,其实没错,诶。
</UPDATE>

 
正在长大的忆向童鞋某次讨论了关于判断 Young 氏矩阵 中元素存在性的问题,当时我就觉得可以在 O(log(n)) 时间内得出结论——后来发现错了,但 O(log(n)^2) 也还是够了。
 
先抄一段关于 Young 氏矩阵的介绍,介绍得不好别怪我因为不是我写的。

一个 m*n 的 Young 氏矩阵(Young tableau) 是一个 m*n 的矩阵, 其中每一行的数据都从左到右升序, 每一列的数据都从上到下升序. 所以, Young 氏矩阵可以用来存放 r<= mn 个有限的元素.

我们要做的事情是,在一个 Young 氏矩阵中找出某个值的位置,或者判断它是否存在。
 
搜了一下,发现还真没有谁提出可以用亚线性的时间复杂度完成这样的工作。
 
为了证明俺是对的,我把代码给码出来了,然后实际操作了一下,先看结果:
yo
横坐标:矩阵规模(为了方便画图,这里用的是 n x n 矩阵)
纵坐标:比较次数
红线:直线 y = x
绿线:实际“有效”的比较次数
蓝线:加上参数合法检验等判断在内总共的比较次数
 
说下思路:在对角线上面找出和要找的数(设为 x 吧)最接近且小于等于 x 的一个值,如果这个值不等于x,那么可以这个值为界,丢掉包括它在内的左上角和不包括它的右下角,原因很显然嘛——然后递归在剩下的部分中找就是了。
——于是一次能丢掉 1/2 左右的数据。
上面在对角线上寻找最接近值的过程又可以用二分法以 O(log(n)) 复杂度得到。
于是总体复杂度就是 O(log(n)*log(n)) = O(log(n)^2) 了。
 

简单拟合了一下, 绿线: 5 ln(n)^2,紫线: 15 ln(n)^2 —— 多么和谐啊!
 
代码如下,感兴趣的拿去玩吧,版权还没没想好。
 

from numpy import *
from random import *

def genRandomYoung( rows, cols ):
    y = zeros( (rows, cols), int );
    t = randrange( 0, 10 );

    for c in range( cols ):
        t = y[0][c] = randrange( t, t+10 );
    for r in range( 1, rows ):
        t = y[r-1][0];
        for c in range( cols ):
            if t < y[r-1][c]:
                t = y[r-1][c];
            t = y[r][c] = randrange( t, t+10 );
    #print(y);
    return y;

def mySuperBinarySearch( young, rect, what ):
    x = rect['x'];
    y = rect['y'];
    rows = rect['rows'];
    cols = rect['cols'];
    if rows==0 or cols==0:
        return None;
    k = (cols+0.0)/(rows+0.0);
    pos = lambda n: (int(y+n), int(x+n*k));
    if (k > 1):
        pos = lambda n: (int(y+n/k), int(x+n));
    get = lambda p: young[p[0]][p[1]];
    n = rows if k<1 else cols;
    if n==1 and get(pos(0)) != what:
        return None;
    #for i in range(n):
    #    print( get(pos(i)) )
    f = 0;
    b = n;
    m = 0;
    m2 = -1;
    while m != m2:
        m2 = m;
        w = get(pos(m))
        if( what < w ):
            b = m;
        elif( what > w ):
            f = m;
        else:
            return pos(m);
        m = (f+b)/2;
    m = (f+b)/2;
    if( get(pos(m)) > what and m>0):
        m = m-1;

    p = pos(m);
    w = get(p);
    if( w == what ):
        return p;
    rect = {'x': x, 'y': p[0]+1, 'rows': y+rows-p[0]-1, 'cols': p[1]-x+1 };
    r = mySuperBinarySearch(young, rect, what);
    if r!=None:
        return r;
    rect = {'x': p[1]+1, 'y': y, 'rows': p[0]-y+1, 'cols': x+cols-p[1]-1 };
    return mySuperBinarySearch(young, rect, what);

class youngMatrixSearcher:
    def __init__(self):
        self.compareOp = 0;
        self.algebraicOp = 0;
        self.call = 0;
        self.binSearchCmp = 0;

    def search( self, young, rect, what ):
        x = rect['x'];
        y = rect['y'];
        rows = rect['rows'];
        cols = rect['cols'];
        if rows==0 or cols==0:
            return None;
        self.compareOp += 2;

        k = (cols+0.0)/(rows+0.0);
        self.algebraicOp +=2;

        pos = lambda n: (int(y+n), int(x+n*k));
        if (k > 1):
            pos = lambda n: (int(y+n/k), int(x+n));
        get = lambda p: young[p[0]][p[1]];
        n = rows if k<1 else cols;
        self.compareOp += 1;

        if n==1 and get(pos(0)) != what:
            return None;
        self.compareOp +=2;

        f = 0;
        b = n;
        m = 0;
        m2 = -1;
        self.algebraicOp += 4;
        while m != m2:
            m2 = m;
            w = get(pos(m))
            if( what < w ):
                b = m;
            elif( what > w ):
                f = m;
            else:
                return pos(m);
            m = (f+b)/2;
            self.compareOp += 4;
            self.algebraicOp += 9;
            self.binSearchCmp += 2;

        m = (f+b)/2;
        self.algebraicOp += 2;

        if( get(pos(m)) > what and m>0):
            m = m-1;
            self.algebraicOp +=2;
        self.compareOp +=2;

        p = pos(m);
        w = get(p);
        self.algebraicOp += 5;

        if( w == what ):
            return p;
        self.compareOp += 1;

        rect = {'x': x, 'y': p[0]+1, 'rows': y+rows-p[0]-1, 'cols': p[1]-x+1 };
        self.algebraicOp += 6;

        r = self.search(young, rect, what);
        self.call += 1;

        if r!=None:
            return r;
        self.compareOp += 1;
        rect = {'x': p[1]+1, 'y': y, 'rows': p[0]-y+1, 'cols': x+cols-p[1]-1 };
        self.algebraicOp += 6;

        r = self.search(young, rect, what);
        self.call += 1;
        return r;

def test():
    rows = 12;
    cols = 12;
    y = genRandomYoung( rows, cols );
    print(y);
    rect = {'x':0,'y':0,'rows':rows,'cols':cols}
    searchFor = y[randrange(0,rows)][randrange(0,cols)];
    print "searching for", searchFor;
    s = youngMatrixSearcher();
    p = s.search( y, rect, searchFor );
    print "find at", p;
    print "with ", s.algebraicOp, "algebraic operations; ";
    print "     ", s.compareOp, "comparisions; ";
    print "     ", s.binSearchCmp, "comparisions purely for searching;";
    print "     ", s.call, "calls.";

    if( y[p[0]][p[1]] != searchFor ):
        print 'oops';
    else:
        print ' - am i genius?'

def benchMark():
    r=range(20,201);
    ns = array([i for i in r]);
    cs = array([0 for i in r]);
    bcs = array([0 for i in r]);
    for n in r:
        avgc = 0;
        avgbc = 0;
        for i in range(10):
            y = genRandomYoung(n,n);
            w = y[randrange(0,n)][randrange(0,n)];
            s = youngMatrixSearcher();
            rect = { 'x':0, 'y':0, 'rows':n, 'cols':n };
            s.search(y,rect,w);
            avgc += s.compareOp;
            avgbc += s.binSearchCmp;
        avgc /= 10;
        avgbc /= 10;
        cs[n-ns[0]] = avgc;
        bcs[n-ns[0]] = avgbc;
    print ns;
    print cs;
    print bcs;

#test();
benchMark();

 

瞎扯几句

1.

上篇提到的某AES加密装置代码:main.c.exe

加密了,双击会有密码提示;然后看了那些破烂代码你也自然就会知道如何把二进制文件剥离出这个 main.c.exe 了。

嗯,写得很草,自己玩玩而已——事实是,画图标的时间不见得比码代码的时间短。

 

2.

复习没状态啊没状态。

 

3.

几张照片

 

IMG_0028

某晚某神秘的小生物来袭。

 

IMG_0059

某晚雷雨交加,想拍几条闪电可惜天空不给力,长时间曝光就成这样鸟。

 

IMG_0115

我喜欢用草做桌面

 

IMG_0184

这货真的不是我,放在这儿只是为了炫耀一下我的偷拍技术大有长进。

 

4.

还是C++对我的胃口啊,以前玩ACM实现过不止一个 trie-tree,没有一个这么简单的:

class TT{
public:
    map<char,TT*> t;
    void insert(char const*s){
        if(s[0]!=0){
            if(t[s[0]]==0){
                t[s[0]]=new TT;
            }
            t[s[0]]->insert(s+1);
            ++t[0];
        } else {
            ++t[1];
        }
    }
    TT(){}
    ~TT(){
        for(map<char,TT*>::iterator i=t.begin();i!=t.end();++i)
            if(i->first>10)delete i->second;
    }
};

于是POJ2001的代码一共就这么长:

#include<map>
#include<string>
#include<vector>
#include<iostream>
#define C char
using namespace std;struct T{map<C,T*>t;void x(C const*s){if(
*s){if(t[*s]==0)t[*s]=new T;t[*s]->x(s+1);++t[0];}else++t[1];
}T(){}~T(){for(map<C,T*>::iterator i=t.begin();i!=t.end();++i
)if(i->first>10)delete i->second;}}; int main(){T*p,t;vector<
string>P;for(C s[24];cin>>s;P.push_back(s))t.x(s);for(int i=0
,I=P.size();i!=I;++i){p=&t;cout<<P[i]<<' ';for(int j=0,J=P[i]
.size();j<J;++j){C h=P[i][j];if(p&& (p->t[0]-1>0||p->t[1])&&p
->t.find(h)!=p->t.end()){cout<<h;p= p->t[h];}else break;}cout
<<endl;}return 0;}

——某晚感到非常无聊做的这题。BTW,这个代码长度排不到第一页,shame。

 

5.

END;

 

自解压不神奇

小时候总以为自解压文件肯定用到了某种诡异的 PE 格式上的 trick,要不然 Linux 下为什么没怎么见过自解压的东东呢。

 

然而昨天翻 7-zip 代码的时候惊讶的发现了真相——

你应该已经注意到了,7-zip 可以打开自解压文件。

——所以?

 

 

自解压文件其实就是一个精简版的 7-zip 紧连着对应的 7z 压缩文件,它把自己作为压缩包打开,然后解压,就是这样。

 

 

 

根据这个原理,挖出某公有领域的 rijndael-alg-fst.c,某 gpl 的 sha256.c 以及 zlib 中的 crc32.c,然后码了 800+ 行代码,俺自己也成功的做了个可以自解密的 AES 加密工具 —— Windows/Linux 通用,支持大文件,CBC 模式,自己挺喜欢的,哈哈,改天放出。

 

各位订阅者麻烦改一下下 feed 地址,多谢了

现在的 feed 地址是:

http://feeds.feedburner.com/if-yu (推荐)

http://feed.feedsky.com/if-yu (墙内,不建议)

 

 

—— feed 这东西的资源消耗量惊人,App Engine 有点抱怨了诶 ……

 

回到 ubuntu

以前为了尝试 Win7 把 Fedora 给 X 掉了,不久之后很有点后悔,因为很多程序只有在 Linux 下才好玩;后来在虚拟机里面又装了个 Fedora 、给了他 1G 的内存,慢得跟乌龟似的。
考虑到电脑上所有的磁盘都有不方便移动位置的重要文件,所以, ubuntu。

下面记几条感受:

  • 像我一样需要代理才能连接网络的同学,首先需要设置一下 /etc/apt/apt.conf 才能装东西:
    Acquire::http::proxy "http://ip:port";
    Acquire::ftp::proxy "ftp://ip:port";
    Acquire::https::proxy "https://ip:port";
  • 然后,有些东西安装时会调用 wget 下载些别的依赖项之类的东西,所以还得配置一下 ~/.wgetrc
    (否则你就哭吧):
    http_proxy=http://ip:port
    use_proxy=on
  • 不敢想象 Empathy 这么一个不支持 proxy 的破玩意儿也能够作为默认的 IM 程序。
  • iBus 自带的几个输入法都非常差劲,不过 iBus pinyin 还不错: sudo apt-get install ibus-pinyin
  • 装 code::blocks 10.05 的时候很戏剧,双击打开下载下来的 .deb 包安装,依赖项总是有奇特的冲突,直接 dpkg *,也是一堆错误信息——然而再看 Applications 菜单的时候,code::blocks 已经在那儿了。
    ——哦对了,注意要升级 wxgtk2.8 ,不然 code::blocks 跑不欢。
  • google chrome 很好用哇。
  • 看大图的时候只有用 XnViewMP (beta) ,其他什么都太慢。

 
Update I:

  • 感觉很神奇,也不知道他们是怎么想的, su 不能用,但是 sudo su 就可以了,汗。
  • 默认的中文字体挺好看的。

 
Update II:

  • AppEngine 骗了我,它 export http_proxy="http://ip:port" 然后就可以使用代理来 update,然而我试了不行。
    想知道问题出在哪儿, grep -r -i http_proxy * 得到的是——nothing。

 
Update III:

  • 突然想到,可能,python 的 urllib 或是 httplib 之类的底层库已经实现了连接代理,于是很暴力的直接 grep -r -i http_proxy /usr/lib/py* ,以及 grep -r -i http_proxy /usr/share/py*
    ——依旧没有一个看上去哪怕有一点点儿靠谱的可能出现问题的文件

 

出门拍照玩

然后发现有个看上去很壮的家伙正扛着小小白拍荷花,自卑死了,遂遁走。

 

IMG_0418

 

IMG_0417

 

IMG_0410

 

 

第一次飞

IMG_0273

 

IMG_0270

 

IMG_0282

Thanks for scrolling down

But STOP! NOW!

There is nothing down there!

Really!

See, I've warned you, little boy.