2008年12月30日星期二

Family Tree Builder 3.0发布

收到MyHeritage.com的邮件通知:Family Tree Builder 3.0发布了,比之前29种语言又新增了5种,纵然如此,亦纵然很久之前就有了MyHeritage中文站,但是软件的中文语言包还是迟迟未出。我之前为2.0版汉化的语言包仍然还可以凑合着用,注意:一些新版里添加的词条,默认显示为TODO。

其他功能没有细查,不知道有什么改善,虽然之前的中文乱码问题仍然存在(还没有中文语言包的情况下,这个可以理解,而且乱码部分不是太多),不过安装程序倒比2.0“大气”了些,而且默认选中将MyHeritage.com设为主页的选项,很智能的,另外还默认选中自动将族谱/家谱发布到网站的选项也很智能。

2008年12月29日星期一

Javascript String 方法效率大比拼

最初是通过梅子梅花雪)关于大型字符串拼接效率(12)的研究得到启发,最近又看到never-online的从trim原型函数看js正则表达式的性能 ,里面有介绍正则表达式效率陷阱等问题,并提出解决方法。我向来对这些鸡毛蒜皮感兴趣,也开始对大型字符串各种方法实现的效率进行比较,并尝试提高这些方法的效率。

1. 大型字符串拼接
梅子所言,使用数组的join方法确实是最好的实现,可以根据这个思路设计StringBuilder, StringBuffer类。



2. 大型字符串trim
其实never-online在他的文章里有一些说的不准确的地方,代码也不算很精炼。既然这是鸡毛蒜皮的小事,这些零碎东西当然要斤斤计较了。
我很久以前有收集到这样一些实现,I:
String.prototype.trim = function(){
return this.replace(/(^\s+)(\s+$)/g, '');
};
为了避免正则表达式使用括号带来的消耗,可以写成这样,II:
String.prototype.trim = function(){
return this.replace(/(?:^\s+)(?:\s+$)/g, '');
};
另外有一套实现是这样的,III:
String.prototype.lTrim = function(){
return this.replace(/^\s+/, '');
};
String.prototype.rTrim = function(){
return this.replace(/\s+$/, '');
};
String.prototype.trim = function(){
return this.lTrim().rTrim();
};
其实调用函数也会多少有一点消耗,写成这样或许会快一点点(开个玩笑,这样写会带来一些冗余代码,这时候就需要基于效率(时间)、代码量(空间)和可维护性方面的考量了),IV:
String.prototype.trim = function(){
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
后来我对正则表达式有了更多的了解,知道了贪婪与非贪婪匹配,于是自作聪明写了这一段:
String.prototype.trim = function(){
return this.replace(/^\s*(.*?)\s*$/, "$1"); // 两端空白字符贪婪匹配,中间字符非贪婪匹配。
};
我曾经为这段代码自鸣得意了好长一段时间,不过后来想到点号不包括换行符,字符串中间有换行符时,返回值就不正确了,于是不情愿的改成这样(多行模式效率也很低),V:
String.prototype.trim = function(){
return this.replace(/^\s*((?:.\n)*?)\s*$/, "$1");
};
谁知,这样的代码遇到大家伙时效率会一落千丈,哎,失败。

原以为String.replace方法比String.substr、String.substring效率低,于是想,只使用正则表达式获得两头(或者一头)的索引位置,然后使用substring方法取出子串,VI:
String.prototype.trim = function(){
var l=this.length;
/^[\s]*/.test(this);
// /(?=[^\s])/.test(this);// -- never-online
var s = RegExp.lastIndex;
if(1==s && !Char.isBlank(this.charAt(0)))s=0;
if(s==l){return '';}
// /\s*$/.test(this);
// var e=RegExp.index;
var e=$lastIndexOf(function(c){return !Char.isBlank(c);});
e=-1==e?l:e+1;
return this.substring(s,e);
};
而最土的方法,莫过于两头都使用循环获得索引了,VII:
String.prototype.trim = function(){
var f=function(c){return !Char.isBlank(c);};
var l=this.length, s=this.$indexOf(f), e=this.$lastIndexOf(f);
if(-1==s)s=0;
e= -1==e?l:e+1;
return this.substring(s, e);
};
代码里老是for啊for的一大串,为了节省字节,而且有可能的话,也准备再优化一下循环,就实现了$indexOf和$lastIndexOf两个方法,可以传递一个返回boolean值的函数作为参数(本来还想也支持正则表达式参数的,想想以前扩展indexOf和lastIndexOf方法后的效率,就算了),这样就可以求得第一个非空白和最后一个非空白字符的位置了。
String.prototype.$indexOf = function(f){
for(var i=0,c,l=this.length; i=0; i--){
c=this.charAt(i);
if(f(c)){return i;}
}
return -1;
};
String.prototype.$lastIndexOf = function(f){
for(var i=this.length-1,c; i>=0; i--){
c=this.charAt(i);
if(f(c)){return i;}
}
return -1;
};
至于说要扩展到支持更长子串和起始索引,以后有需要再说了(顺便说一下,子串越长,有优化算法可以得到更高效率)。
另一个辅助方法:
var Char = {
isBlank:function(c){
//return /\s/.test(c);
return ' '==c '\t'==c '\r\n'==c '\n'==c '\r'==c;
}
};
到了永不在线(Google Translate翻译为“永远在线”)的算法,VIII:
String.prototype.trim = function(){
var s = this.replace(/^\s+/, '');
var l=s.length, e=l;
if(0==l){return '';}
for(var i=l-1; i>=0; i--){
if(!Char.isBlank(s.charAt(i))){
e=i+1;
break;
}
}
return this.substring(0,e);
};
最初这个算法让我很兴奋,直觉上,感觉这样效率肯定要高,不过事实并不是这么简单。

说到这些实现的效率,无法一概而论,因为不同的字符串,它们的效率比也大不同,甚至异乎寻常。

影响trim方法效率的,主要与字符串的总长度,前面空白字符串长度,后面空白字符串长度,以及前中后的比例有关。详细的效率对比表有时间再上,这里只简要提一下:

对于较小的字符串,各种实现都有不错的表现,而对于大型字符串,则实现III, IV表现较为稳定,甚至可以处理超大型字符串(修正:之前有误写成I,II两个较为稳定)。




3. 大型字符串字节长度
即双字节长度为2。注意:这个提法其实也不正确,Javascript是使用Unicode字符集的,所有的字符都(有可能)是双字节字符。将汉字等转换为双字节长度主要是为了某些应用。
最土的方法还是循环遍历所有字符,I:
String.prototype.bytes = function(){
var l=this.length, r=l, n=0xff;
for(var i=l; i>=0; i--){
if(this.charCodeAt(i)>n){
r++;
}
}
return r;
};
这里判断字符是否双字节有很多方法,效率较高的之间相差(大概)不大。

另一种实现则看起来很轻灵,寥寥几行,II:
String.prototype.bytes = function(){
return this.replace(/[^\x00-\xff]/g,"xx").length;
};
多动脑子,则想法愈多(也常把简单的事情复杂化),我想如果可以快速取得表达式(双字节/单字节)匹配次数,两值相加应该比较高效,III:
String.prototype.bytes = function(){
return this.length+this.replace(/[\x00-\xff]/g,"").length;
};
IV:
String.prototype.bytes = function(){
return this.length+(this.match(/[^\x00-\xff]/g)"").length;
};
另外看到梅花雪用数组能提供字符串拼接速度,也想:把字符串split为数组,不想对大型字符串而言,这split一步就慢得不行。

bytes方法的效率:使用Javascript脚本循环大型字符串(I),确实远不如内置的replace方法(II)快,而使用正则表达式match方法(IV)又比replace方法(III)稍快,排名第二。

总结:
1. replace方法因匹配而被替换的子串愈长,效率愈低。
2. 根据目标字符串,选择合适的实现。

2008年12月24日星期三

foobar2000 Ctrl+Alt+Z 之灵异

最近发现一个怪异的事情,按Ctrl+Alt+Z键,不能够调出TM(QQ)的对话框,并且,当焦点在编辑器等可输入文本的区域时,按这个组合键会粘贴出一行字符(后来确认是Foobar2000当前播放曲目信息),Google搜到唯一的一个谈论这个的帖子,结论好像是说卸载(非禁用)Foobar2000的foo_amip插件就可以了。

p.s. 这个插件挺有用,卸载了可惜,还是该TM的快捷键算了。

2008年12月9日星期二

2=1?

花了好长时间才解出一个初中生都会的算术题,囧啊。

微趣上看到一组数学漫画,里面有这样一张:
最初一步步看下来,貌似还真的没有错,,,但是题解结论却得到一个驳论。回头想了好久,才看出端倪。

这里的算式是有条件的,即:a!=0, a!=b,但是这里的前提条件戏剧性的又是a=b,所以最长的那个表达式两边是不可以化简的(a-b=0,两边=0,0=0)。。。

2008年12月4日星期四

囧:梦见心脏掉出来了。。。

这两夜均是通宵过来而白天睡觉(生活太没规律),但是今天做了一个邪乎的怪梦:

大概是看《倚天屠龙记》的缘故,梦见了张三丰(已成仙),而且我帮他做了一件什么事,并(一厢情愿的)称他为师父。
后来,大概是床上什么东西顶住了心脏部位,我觉得心脏不对劲,轻轻掀开T袖,发现心脏滑出来了。
我怕心脏完全掉出来,也不敢有大动静,甚至都没看胸口现在是什么样子了。
我隔着衣服轻轻托着白萝卜一样的心脏,心急,四处寻找师父。
好久,终于在一个小池子旁边寻见师父,连连喊师父救命。
师父把我之前胡乱塞在胸口的萝卜型心脏取出来,又植入进去。
后来醒来,并没有发现能顶住身体的物件,难道是自己的拳头?
直到现在,还觉得心脏有点异样。

口才文笔不好,这么好的题材,都不能讲出好故事,甚至连文字都不流利,只能记流水帐了,惭愧。