Random Tech Thoughts

The title above is not random

买了小提琴

练琴四个月了,终于买了自己的琴了。

跑了三家琴行,最后买了在第二家琴行看中的一把手工琴,价格可以接受,四根弦的声音都不错,A弦和E弦尤其灵敏,音色也很好,A弦三指的位置拉的时候感觉共振最厉害,拉一下之后手指按住也还会有余音,G弦也比较洪亮。可惜的是这把琴磕磕碰碰的留下的伤痕实在不少,拉弦板下面有一个可能在弦放松的时候弄出的一块不小的伤痕(漆都调了啊:–(),右侧琴身边缘也有几处撞伤,背面则是撞出来的一些小坑。除了这些伤痕的话我也是很满意了,琴行的人也很热情,还有个能整小提琴的师傅(虽然不是专业的,但是琴有问题要调的话总比我自己好太多了)。用这些伤痕打了个76折。(伤痕还真是不少,真应该再多打一点折……)上个学年的奖学金全花进去了,另外再贴了300大洋。好在小提琴买了也不会贬值,可以认为是投资行为,不过这些伤痕就得折价了。(不过付钱的时候感觉还是不爽的……)

晚上回来练习,感觉真是非常好,比同学那把30年的练习琴爽多了!(同学那把琴不是很好,保养也不注意,要不然 30 年了应该不错的)A弦E弦拉起来很舒服,声音一下就出来。琴弦比较软,很好按。弓比同学那把重一点,但是我喜欢,感觉好就是了。G弦倒是不像在琴行里拉的时候感觉那么好了,不过G弦我本来就拉不太好,应该是我自己的原因。总之很喜欢自己挑的这把琴,有伤痕是不完美,但是这个价钱的话想要完美的话恐怕很难。自己喜欢就好!(上次有同学说我有点完美主义,也许是有点,即使不看琴,那些伤痕还一直萦绕在我脑中,令人不快;而且如果不是那么挑剔的话应该会选一把更便宜的琴,也不会在看到中意的琴之后再去其他的琴行看)

新琴更加让我想学小提琴了,倒不是想拉到怎么好,就是作为自己的爱好,生活中的一点乐趣。小时候练电子琴的时候是那么的厌恶,没想到现在居然这样喜欢小提琴,而现在学小提琴的目的和当初父母让我学电子琴的目的是一样的。他们现在也支持我去学小提琴,只要我喜欢就行。不知道小时候如果学小提琴的话会怎样。现在想想即使小时候不喜欢连琴也还是要感谢父母(小时候恐怕还是因为别人在玩,看动画片而我却要连琴而感到厌恶吧),在我小时候,在一个小镇上能够想到让孩子练琴的父母还在少数,而且还舍得花了不少的钱去买琴,请老师,还要督促我。而且没有练电子琴的经历的话,我现在学小提琴进步的速度一定会比学过的要慢。

谢谢我亲爱的爸爸妈妈!(也只有在自己的 Blog 里才有勇气说这样的话,要是能当面说给父母听他们一定会很开心的)

转贴个笑话,高中时看过的一个 Flash 里面的

今天在 Google 的个性化主页里看到的,高中时可爱的地理老师曾经把这个笑话的 Flash 放给我们看,呵呵,当时全班爆笑!还有不少流氓兔的 Flash 也是地理老师给我们看的。

把那个全班爆笑的转贴到这里,可能看 Flash 会更搞笑(这里有一个),因为会有狼摸不着头脑的神态和声音 :–)

有一天,狼要吃三只小猪。三只小猪有两只在门口,有一只在屋顶。(猪A和猪B在门口,猪C在屋顶上。猪A的名字叫“谁”,猪B的名字叫“哪儿”,猪C的名字叫“什么”。) 于是:狼:“你是谁?”猪A:“对!”狼:“什么?”猪A:“‘什么’在屋顶上。”狼:“我是问你的名字叫什么?”猪A:“我叫‘谁’,‘什么’在屋顶上!”狼又问猪B。狼:“你是谁?”猪B:“我不是‘谁’,他是‘谁’(指只猪A)。”狼:“你认识他?”猪B:“恩!”狼:“他是谁?”猪B:“是的。”狼:“什么?”猪B:“‘什么’在屋顶上!”狼:“哪儿?”猪B:“‘哪儿’是我。”狼:“谁?”猪B:“他是‘谁’。(又指着猪A)”狼:“我怎么知道。”猪B:“你找‘谁’?”狼:“什么?”猪B:“他在屋顶上。”狼:“哪儿?”猪B:“是我。”狼:“谁?”猪B:“我不是‘谁’,他是‘谁’”狼:“天哪!”猪A猪B:““天哪“是我们的爸爸!”狼:“什么,是你们的爸爸?”猪B:“不是!”狼受不了了,仰天长叹:“为什么?”猪A,B,C:“你认识我们的爷爷?”狼:“什么?”猪A:“不是,我们的爷爷是‘为什么’”狼:“为什么?”猪A:“是!”狼:“是什么?”猪A:“不,‘为什么’。”狼:“谁?”猪A:“我是‘谁’?”狼:“你是谁?”猪A“:对,我是‘谁’。”狼:“什么?”猪A,B:“他在屋顶上”

Linux 下转音乐格式的工具 Audio-convert

使用其他工具来转音乐文件格式的工具,用图形界面来输入需要使用的参数,很方便,不用自己再去搞明白那些命令行参数了。安装对应格式的编码器以后可以支持多种格式互相转换,,包括 APE。(用 Monkey Audio Codec non-win32 port 为编码器 )

开始试用 SciLab 和 Octave

这个学期有 DSP,老师会让用 Matlab 做作业,但是懒得去弄 Student version 的来了,所以找 Matlab 的替代软件,发现还不少。除了 SciLabOctave 意外还有 FreeMat,不过那个 Arch 上提供的包里面的没法运行,所以就懒得弄了。

SciLab 的 Linux 版现在是用 tk 做的 GUI,感觉不太爽,5 里面用 Java 的话但愿用 swt 来做界面,swing 的话也还是不太爽,不过是明年 10 月的事情了。Octave 没有图形界面,但对 Matlab 的兼容性比较好,它的主页上说是 “most compatible with Matlab”,又要比较一下了。

圣诞夜对我来说还是没有什么特别的啊……回去看 DSP 去。

CJK 4.7.0 对 UTF8 的支持真是太棒了!

最近把 UTF8 字体的生成和配置搞定了,换用 CJK 4.7.0 了。发现它对 UTF8 的支持比以前好多了,终于 TeX 源文件也可以全部使用 UTF8 编码了。好处是 pdf 的中文书签处理起来很方便,只要在 hyperref 包的选项里面使用 unicode,beamer 也是添加 unicode 即可。更爽的是 pdftex 生成的 pdf 文件也支持复制粘贴了!

感谢 Werner Lemberg !!!

这里是一个用来从 True Type 字体生成 UTF8 编码字体的脚本,根据你自己的情况修改一下吧。最好在一个空目录下运行,需要三个参数,字体路径,字体内部名字,源文件里面使用的字体名字。(根据自己的需要做修改吧)我感觉 ttf2pt1 用参数 -OHub 比较好,subhinting 会感觉字脏,没有 Hinting 会感觉字发虚,这个比较折中。

SRGP 在 Linux 下的编译和 Sample 的运行

计算机图形学导论中的 SRGP 图形包的 Mac 和 Linux 版本可以在这里找到,是 Brown University 的 CS 的。

编译时需要注意的是如果发现 ld 抱怨说库不兼容时先把 lib/libsrgp.a 删除,这个库文件是软件包里面原来就有的,把 make 生成的 src/srgp/objects/libsrgp.a 移动到 lib 下在 make 就应该没用问题了。有一点要注意的是默认提供的 Makefile 里面为了与其他编译器兼容使用了 -fpcc-struct-return 这个选项,如果你的二进制文件要与 libsrgp.a 进行链接的话也需要使用这个选项。

另外 SRGP 程序在 X Window 下运行的话需要把 Color Depth 设置为 8 (改 xorg.conf 就可以了)。这一点参考了这篇文章

内部迭代器的限制

内部迭代器确实很方便,但是它也有一些限制。

一个是内部迭代器只能作用在容器的数据上,而不能作用于容器本身。以 Java 为例来说,你想将容器中满足某些条件的成员从容器中除去,如果你使用外部迭代器那么很简单只要在迭代到满足条件的成员时调用 Iterator 的 remove 方法。但如果你使用增强的 for 循环,因为你操作的是数据,所以你没用办法直接把它从原来的容器中除去。

对于这种情况其实还是很好解决的,只要创建一个新的容器,然后把需要的成员添加到这个容器里就可以了。这样这个新的容器中包含的就是“过滤”出来的成员,把原来的容器赋值为这个新的容器就起到剔出某些成员的目的了。由于容器里存放的都是引用而不是新创建对象,所以这里增加的开销并不会很大。

其实这种方式在 Scheme 中一直都在使用。通用的思想是一个“过滤器”接受一个输入序列,输出一个过滤过的序列。拿上一篇 “ Callback 与内部迭代器” 的文章里的 Scheme 的例子来看,给 map 过程不同的过程为参数实际上就构造出了不同的过滤器,items 就是过滤器的输入,而 map 过程的返回值就是过滤过的序列。由于 Scheme 的每一个函数都有返回值在加上 Scheme 使用序列作为通用的数据结构,过滤器的思想在 Scheme 中用起来非常顺畅,不像 Java 一样要另外创建一个容器,还要将原来的容器赋值为新的容器。

第二个限制是要同时遍历多个容器时用内部迭代器无法做到。(至少我没用想到有什么办法。)Ruby 虽然大量使用内部迭代器,但是它可以通过 Generator 来得到外部迭代器,这样在需要同时遍历多个容器的时候会非常有用。

磁盘的 0 柱面是在磁盘外圈还是内圈?

我在安装 Linux 分区的时候都喜欢把 Linux 放在尽可能靠近 0 柱面的分区,因为记得磁盘靠近 0 柱面的地方性能会比较好。我原来以为 0 柱面是在磁盘最内圈,但是想想的话应该是磁盘靠外圈的性能比较好(毕竟那里的线速度快,而磁盘的 sector 的线密度是一定的),于是一直奇怪为什么是靠近 0 柱面的地方性能好。

最近修改分区,google 以后弄明白了这个问题。其实很简单,0 柱面是在磁盘的外圈。真不知道当初是怎么有了 0 柱面在磁盘内圈的错误映像的,也奇怪自己怎么就没有转过弯来怀疑这个错误的概念。

搜到了一篇很不错的介绍硬盘的文章

Callback 和内部迭代器

最近用 C 做编译作业的时候用了 GLib 里面的 hash table,终于用到了 C 中 Callback。函数指针以前没有怎么用过,现在觉得是个不错的东西,还有就是 void 指针真是个让人即爱又恨的东西,而更恶心的东西莫过于 void 指针的指针了(能避免这样的东西还是避免吧,我的智力还是不够去处理那样“邪恶”的东西)。

因为看到 GSList 同时提供了外部迭代器和内部迭代器,所以想到了更其他语言作一下比较。同时也看看利用 callback 来实现内部迭代器的方法。

为什么要内部迭代器?通常在对容器作迭代时,对容器中的每个对象所做的操作都是一样的。迭代器隐藏了容器内部的实现,对外提供遍历容器内部的数据的方法。外部迭代器虽然做到了这一点,但是实际上迭代的过程还是要程序员自己来写。你得得到一个迭代器,然后在 for 循环里判断有没用到终点,没用的话执行循环体,然后让迭代器向后移动。实际上大多数的情况下迭代的不同只是在循环体而不是那个 for 循环,所以完全可以将这个 for 循环封装在对象内部,而将循环体通过某种方式传入,然后 for 循环通过某种方式去调用这个循环体就可以达到遍历的目的了。这样的迭代器就是内部迭代器了,一个明显的好处就是避免了容器使用者写错 for 循环的可能。现在问题的关键就在于循环体如何传入?一种比较自然的想法就是将函数作为参数传入,这样 for 循环只要调用这个函数就可以了,也就是 callback 了。下面就看看我所知道的各种语言使用 callback 来实现内部迭代器的方式。

  1. 先看 C。举个例子,GSList 的 g_slist_foreach 用来遍历一个链表。
    1
    
    void g_slist_foreach( GSList* list, GFunc func, gpointer user_data);
    
    三个参数分别为:需要遍历的链表,callback 函数(也就是要作用在每一个链表中元素上的函数),传给 callback 函数的数据(gpointer 实际就是一个 void 指针)。其中 callback 的函数的原型如下:
    1
    
    void callback(gpointer data, gpointer user_data);
    
    在将函数指针传给 g_slist_foreach 的时候要将它转型为 GFunc。GFunc 应该是一个 typedef 吧。
    1
    
    typedef void (*GFunc) (gpointer data, gpointer user_data);
    
    其中 data 是 list 中元素,而 user_data 是调用 g_slist_foreach 时传入的。 其实 GSList 可以使用外部迭代器来遍历,方法如下:
    1
    2
    3
    4
    5
    
    GSList* iter;
    for (iter = some_gslist; iter; iter = iter->next) {
        gpointer data = iter->data;
        ......
    }
    
    所以 g_slist_foreach 可以像这样来实现(我没有看过它真正的实现,真正的实现可以接受只有一个参数的函数指针,只要在调用的时候把 NULL 传给 user_data。)
    1
    2
    3
    4
    5
    
    void g_slist_foreach(GSList* list, GFunc callback, gpointer user_data) {
        GSList* iter;
        for (iter = list; iter; iter = iter->next)
            callback(iter->data, user_data);
    }
    
    这样 g_slist_foreach 相当于是一个内部迭代器了。对于复杂的遍历另外定义一个函数再通过 g_slist_foreach 来遍历代码会比较好看吧,另外也避免了写错 for 循环的可能性。对于简单的遍历要新定义一个函数感觉还是不太方便的。
  2. 再看 Java。Java 里面的 callback 的话可以通过 interface 或者 abstract class来实现。同样以链表为例吧,代码就随便一点了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    public interface ListIter {
        public void func(Object data, Object userData);
    }
    
    public class ListIterImpl implements ListIter {
        public void func(Object data, Object userData) {
            // do something on the data
        }
    }
    
    public class List {
        private class Node {
            public Node next;
            public Object data;
        }
    
        private Node head = new Node();
    
        public foreach(ListIter callback, Object userData) {
            Node iter = null;
            for (iter = head.next; iter != null; iter = iter.next)
                callback.func(iter.data, userData);
        }
    }
    
    // Use the list
    List list = new List();
    list.foreach(new ListIterImpl(), null);
    
    这样,对于 List 的对象要遍历它只要创建一个类并实现 ListIter 接口,然后创建这个类的对象并将它传给 foreach 函数就可以。如果 ListIter 定义为 abstract class 的话那么可以让 ListIter 的对象来携带数据以避免传递用户数据了。这样其实就构造了 List 的一个内部迭代器了。但是 Java 的 Collection 中并没有采用这种方式,我想原因在于对于每一种遍历方式都要去创建一个类实在是不方便。 不过注意 Java 5.0 中有个增强的 for 循环,利用它可以非常简单的来遍历一个链表,不需要接口,也不需要外部迭代器。
    1
    2
    3
    
    for (Object data : List) {
        // do something on the data
    }
    
    个人认为这也可以看作是一种内部迭代器,只不过迭代的过程由 Java 编译器完成了。自从知道这个增强的循环以后我就从来不使用 Java 的外部迭代器了。当然,List 的实现里面要提供一些东西以支持这样的遍历。不过遗憾的是我现在还不知道究竟要提供什么,Java 5.0 的文档里面似乎只提到了这个 for 循环用于 Collection 的对象上,而没用具体说类的实现是否有什么特别的要求。(有看到过文章说这是 continuation,不过这个我就不懂了) Callback 在 Java 中当然还是有用的,只不过不是用来作内部迭代器而已。以前用过 JavaSVN(现在名字叫 SVNKit),它里面就用了不少的 callback。
  3. 下面是 C++。我对 C++ 不是很熟悉。不过我想在 C++ 中实现 callback 的话即可以使用函数指针,也可以使用类似 Java 的方式。另外也可以使用函数对象,TCPPPL 中说一个定义的好的对象通常比一个函数工作的更好,而且函数对象执行起来也更快(用对象来保存对象的话避免了函数参数的传递吧,还有什么其他的原因呢?)。举个大概的例子吧,但愿不要有错误。 假设在 List 的 C++ 实现里已经有了迭代器,提供了 begin(), end() 方法得到起始和末尾,那么可以使用 foreach 来遍历 List 的对象。大概的代码是这样的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    class Callback {
    public:
        // Saves user supplied data
        Callback(Data user_data) {}
    
        void operator()(Data data) {
            // do some thing on the data
        }
    }
    
    foreach(list.begin(), list.end(), Callback(user_data));
    
    for_each 实际上将 List 中每一个对象传给 Callback 类对象的重载过的 () 运算符,Callback 类本身又可以携带数据,所以就避免了在 callback 函数中另外再传用户数据的麻烦。
  4. 再看看 Scheme 就会觉得比较有趣了(Scheme 是 Lisp 的一种方言)。Lisp 淡化数据和过程的差别,传递数据和传递函数完全是一样的方式,过程不仅可以返回数据,也可以返回过程!所以这里的 callback 不是那么明显,而 Lisp 程序中传递过程这样的事情实在是太多了。我对 Scheme 的了解仅限于看 SICP 时学到的那些,请有经验的 Scheme 程序员不吝指教。这里就不以 List 为例子了,因为 Lisp 这样的语言中使用表作为通用的数据结构得到的威力远远大于专门构造一个链表(另外我承认我从来都没有自己尝试过在 Scheme 中创建一个链表,要写的话还要花一点时间呢) 先解释一下表的概念吧。表实际上由有序对组成,可以使用 (cons x y) 来构造一个有序对,对这个有序对使用 (car (cons x y)) 的到的就是 x,而使用 (cdr (cons x y)) 得到的就是 y。一个表可以用下面的方式构造:
    1
    
    (cons x (cons y nil))
    
    这样就构造了一个只有 2 个元素的表 (x y)。注意表一定是以 nil 结尾的。(nil 表示空) 了解了表以后这里就以遍历一个表为例子吧。类似于使用外部迭代器,你可以自己使用 car, cdr 来遍历一个表。由于表的构造方式,使用递归是非常方便的。这里不使用赋值(我对 Scheme 中的赋值的理解还不够),在遍历完之后返回一个对每一个元素做过特定操作的新的表。(Scheme 的实现里对 tail-recursive 的计算的空间开销是常量,所以这里不用担心递归的开销,跟迭代没用差别)方便起见,分号之间的作为注释。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    (define list (cons x (cons y (cons z nil))))
    
    (define (iter items)
      (if (null? items)
        nil
        (cons (;; do something on (car items);;)
            (iter (cdr items)))))
    
    ;; iterate over list;;
    (iter list)
    
    这个遍历过程是很通用的,除了对表中元素的操作不同以外其他的代码在对其他表遍历的时候也可以使用。为了复用这些代码,也使得程序更简洁,可以将这个遍历过程抽象成一个过程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    ;; From the famous and interesting book SICP;;
    (define (map proc items)
      (if (null? items)
        nil
        (cons (proc (car items)
          (map proc (cdr items)))))
    
    ;; use map to iterate ove list
    (map (lambda (x) (;; do some thing on x ;;)) list)
    
    这里 map 过程的 proc 参数是一个过程,它接受一个参数。在调用 map 过程应用于表 list 的时候使用 lambda 定义了一个匿名过程,这样就可以避免另外单独定义一个函数了。(C++ 的 Boost 库中的 lambda 就是作这个事情的吧,不过没有尝试过)跟前面没有抽象成过程的例子比较一下,注意对表中每一个元素的操作位置的改变。 可以将 map 过程看作一个内部迭代器。但是 Lisp 的威力在于这个 map 过程可以作用在任何的表上(注意表在 Lisp 中是通用的数据结构),而表可以用来存放任何类型的数据,所以 map 过程只需要一个!变化的东西只有表中存放的元素和如何对那些元素进行操作。而 C++/Java 这样的语言对每一个不同类型的类都需要提供这样的一个迭代器。你可以尝试定义一个通用的遍历过程作用于不同的容器,但是对不同类型的容器如何遍历?使用迭代器?那么这个迭代器当然得由容器类来提供了。(其实 C++ 中的 for_each 就可以看作是一个可以作用于不同容器的通用迭代器,但是如果容器不提供迭代器,for_each 又如何实现?)
  5. 最后是实现 callback 最爽的 Ruby 了。大概的代码样样子如下(Ruby 没有怎么正式使用过,写的不好请不要笑话):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    class List
      class Node
        attr_accessor :next, :data
      end
    
      @head = Node.new
    
      def foreach()
        iter = @head.next
        while (iter != nil)
          yield iter.data
          iter = iter.next
        end
      end
    end
    
    注意 yield 这个关键字,它会调用附加给函数 block (简单来说就是一段代码,Ruby 会将这段代码转为一个特殊的对象 Proc 传入),yield 把链表中的元素传给这个 block。在要遍历 List 对象的时候可以像这样:
    1
    2
    3
    4
    5
    6
    
    list.foreach {|data| # do something on the element }
    
    # or like this
    list.foreach do |data|
      # do something on the element
    end
    
    不需要接口,不需要函数指针,要对元素做什么操作直接把代码写在 block 中。跟我在文章开头说的将一段代码传入 for 循环的想法最相近吧。实际上 Ruby 中只要 include 几个 module ,实现几个特定的函数就可以得到像 foreach 这样的内部迭代器,不过 Ruby 中习惯把这个函数的名字叫做 each 。 Ruby 中大量使用这样的内部迭代器,同时利用 block 可以非常方便的实现 callback 机制。

最后的一点想法。一个函数接受另一个函数(或者说一段代码)作为参数可以改变已经定义好的函数的行为。是不是可以把这个调用其他函数的函数看作是一个 template 呢?不管怎么说,将函数作为参数来传递真的是一种很有用的技术吧。

开始用 Fcitx ,感觉不错

原来一直都没有怎么试过 fcitx,最近因为编辑器换用了 Vim,而 Vim 下的 latex-suite 和 scim 有冲突,上网搜也没有解决,最后就直接试用 fcitx 了。跟 latex-suite 冲突的问题没有了,而 fcitx 的配置也真实够 UNIX like 了,就一个配置文件,没有提供图形化的界面修改配置。好在配置文件很简单,就是一些“键-值”对,而且配置文件里键用的是中文,大多数内容一看就明白。而我对 fcitx 的表现也很满意,输入窗口的跟随比 scim 好很多,而拼音的词组顺序也很不错,感觉跟紫光比较像,所以我感觉更习惯 fcitx 一点。

不过只有一点不是很好,那个输入状态条做的实在有点难看,可惜我做美工这种事不在行,要不然真想帮作者把状态条弄漂亮一点。