2009年12月31日星期四

使用 jQuery 操作 SVG DOM

最近在尝试用svg构造一个小项目的前端,方式是inline svg,也就是把svg元素直接写进xhtml之中。像这样:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
      PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:svg="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <head>
  </head>
  <body>
    <svg:svg version="1.1"
             font-family="Courier New"
             font-size="14px">
      <svg:rect />
    </svg:svg>
  </body>
</html>

注意这里是xhtml而不是html,如果你希望你的页面能够被Firefox直接渲染,这是必须的。xhtml是xml文档,只是非常像html而已。Firefox的xml解析器和html解析器是不同的,所以如果用html作为这个页面文件的扩展名会导致Firefox直接把源代码显示出来,就像显示一般xml文件一样。

svg dom和html dom没有什么区别,大多数方法也是相同的,所以jQuery在一般情况下也是畅通无阻的。只有一点是需要特别注意的,应该使用需要指定命名空间的DOM方法。比如如果需要创建一个svg元素时,应该使用createElementNS而不是createElement。好在这样的DOM方法不多,只有这么几个:

















































createAttribute createAttributeNS
createElement createElementNS
getAttributeNode getAttributeNodeNS
getAttribute getAttributeNS
getElementsByTagName getElementsByTagNameNS (also added to Element)
getNamedItem getNamedItemNS
hasAttribute hasAttributeNS
removeAttribute removeAttributeNS
removeNamedItem removeNamedItemNS
setAttribute setAttributeNS
setAttributeNode setAttributeNodeNS
setNamedItem setNamedItemNS
这个问题在svg wiki和SVG Authoring Guidelines都讲到了。

这就带来一个问题,jQuery是不使用这些结尾是NS的DOM函数的。怎么办呢?笨办法是直接git clone出jQuery的源代码,想把所有相应的函数全部用NS结尾的函数替换并改写,然后重新编译出一个定制版的jQuery给自己用。我怎么知道的呢?因为这种蠢事就是我干的。还好在用脚本替换了这些函数之后我还是意识到了,转而把这些NS结尾的函数包装成jQuery的插件,比如像这样:

//包装hasAttributeNS
jQuery.fn.hasattrns = function(key) {
    var ele = this[0];
    return ele.hasAttributeNS(null, key);
}

这样包装完这些函数之后我就可以继续使用jQuery了,这些新函数和jQuery配合的还真不错:)

顺便说两句svg和canvas。从web的角度来看它俩都能在浏览器中创造图形元素,提供更好的用户体验。但从另一个角度来开,<canvas>是html5的一部分,是专门用于增强web页面的;而svg属于xml,它不局限于网页,还可以比如和xul结合起来增强Firefox应用程序本身的使用体验。正像我上面所说的,xml和html是两回事情。不过现在xhtml2的工作组被解散了,xhtml前途未卜,svg在网页之中的表现会如何呢?至少在目前我感觉canvas的应用日见增多,但svg却还是在原地踏步。

2009年12月28日星期一

我的 GoogleReader 订阅

今天和jessinio同学聊天,扯到RSS订阅的事儿,在这里分享一下自己的订阅:google-reader-subscriptions.xml

我喜欢技术,所以绝大部分订阅都是技术类的;我讨厌政治,所以我会拒绝政治类的分享。已经有同学因为分享太多和技术无关的内容而被我忽略了,比如双木成林。

我喜欢Firefox,喜欢Mozilla。我觉得所有这些RSS源中Mozilla Planet对我的帮助是最大的。当然,我也不排斥Google或者Microsoft,咱不搞意识形态挂帅,只要是好东西咱都愿意学习。

非技术类的订阅中我最推荐Matrix的数学Blog。只要你喜欢数学,还不想自己的脑筋生锈,就应该订阅它。

2009年11月20日星期五

Chrome OS

昨晚在公司熬夜看ChromeOS的新闻发布会直到四点,还好Google没有让我失望。ChromeOS和我想的基本一样,唯一的惊喜在于它的文件系统。ChromeOS的root是只读的,同时不允许安装任何二进制可执行程序。这是一个相当漂亮的设计,这样ChromeOS就只需要专注于浏览器的安全就可以保证系统的安全。

ChromeOS的发布意味着几件事情:
1、应用程序的在线化是大趋势,对于普通用户来说,桌面的消亡不可避免。但是我们的桌面上还有非常非常多的应用程序没有网络化,很多很多网站还没有将自己应用程序化。这是ChromeOS这样的操作系统普及的一道巨大障碍,同时也意味着一个巨大的蛋糕,一个巨大的机遇。
2、ChromeOS现在使用的都是google的服务,但谁愿意把自己的东西都交给google一家保管呢?也就是说,网络应用互相之间的接口标准化将不可避免,各种网站提供的标准接口的网络存储、邮件、歌曲等等服务会大大的丰富起来。

ChromeOS已经可以跑在上网本上了,PC还会远么?让我小小的憧憬一下未来的在线编译服务吧……

ps: 本来昨晚就应该写这么一篇,不过不出意料,那个时候google docs都挂掉了
ps: 很可惜http://chromium.org/国内访问总是被屏蔽,我要想办法checkout一份代码下来编译一遍才好。


2009年11月1日星期日

Patch Culture

      



今天在Mozilla Planet上看到了David Humphrey的一篇关于“为互联网打补丁(Patch the web)”的文章,想法有些新颖:
By patching the web I mean the HTML, CSS, and JavaScript I encounter.  “Sure, people have been doing this for years…greasemonkey, stylish, et al.”
That’s not what I do.  Rather than fixing this for me, or the people
who will download a script I write, I prefer to make the fix, and send
it to the website author directly.

      在开源的桌面的时代,当你找到了某个程序的bug怎么办?你可以写一个补丁然后把这个补丁通过程序的作者分享给所有人。但是现在的互联网并不是这样一个开放的世界。虽然任何人都可以“浏览源代码(View Source)”,但想要自我主动修正看到的错误却是不可能的。你可能会在某个网站上看到错别字,看到颜色或是排版的错误,但想要修改?只有找网站的管理员了。网站的源代码?No,其实你没有网站的源代码,你只是可以看到生成的页面html代码而已。在这个桌面应用向在线应用迁移的时代,原来在桌面上开源的软件到了互联网时代却变成了闭源的。从这个角度来说,互联网只是在公共信息上走向了开放,而从程序员和计算机的角度来说却是在走向封闭。
      Mike Hoye同学为此专门创建了一个小站www.PatchCulture.org。事情就像他说的那样,might work, might not.

最近抽空翻译了几篇文章:




Joel Spolsky:Capstone项目与时间管理 



David Humphrey from Mozilla:“谁是你的伙伴?”——有感于Joel Spolsky的博客




gregdek from Red Hat:Spacewalk项目:构建“补丁文化”





2009年10月18日星期日

2009年哲思自由软件峰会


     今天非常的high,偶像级别的人物出现在了咱身边,那就是Richard Stallman!自由软件之父!

     我一个星期之前才知道这个事情,非常期待。俺们老大是这个活动的组织者,所以我有很多机会可以几乎是和Stallman独处。因为昨晚又是三点才睡觉,今天上午很无语的一直睡到中午十二点,错过了和Stallman在小小的视频会议室里聊天讨论问题的机会,很不甘心,于是从中午开始就干脆在演讲大厅的后台吃盒饭,准备守株待兔,和大牛近距离接触。

     下午的演讲很多人,金山十四楼的大会议室座无虚席。但是,大牛竟然迟到了,大概是中午和省市领导吃饭来着。我非常无语的看着他们直接从大厅走向座位,然后只能站在会议大厅里听演讲。

     此行来珠海的不止Richard Stallman一个大牛,还有一位叫Akira Urutashibata的日本小牛。这位日本友人出生在东京,在美国长大,很喜欢中文,能写简体汉字。他还送给我同事一本日本高中的汉语教材,内容包括《石壕吏》、《长恨歌》等经典。今天感觉他很友善,只是有些拘束。当时觉得可能是Stallman大神的气场太强大了,大家都无视他了,现在想起来可能日本人在中国还是有些不安或者负罪感的吧,特别是我们这种不问政治的技术人员。Akira第一个上台,讲的是黑客运动的历史。我心中期待着偶像的演讲,直接和同事去打乒乓球去了……

     Akira之后是WPS的技术总监杨刚,演讲的题目是WPS领导的开源项目UOF Open SDK。演讲结束之后据说反响非常热烈,很多人提问,多到以至于只能硬生生的掐断提问时间。当然,“据说”是因为我们在打乒乓球,无视了。

     Richard Stallman在珠海金山的演讲很精彩,给我印象深刻的有两点:



第一:什么是自由软件?


     大家平常都说开源,都说Linux,但是这个中有什么区别?在开讲之前我和两个同事还在讨论这个问题,但是都没有说出个所以然来。Stallman先生在
今天的演讲中着重提到了这一点,阐明了什么是软件的四种自由以及我们为什么需要它们;详细的介绍了什么是自由软件、自由软件运动、自由软件基金会和
GNU;说明了GNU和Linux的区别,FSF和OpenSource的区别。虽然看起来这只是字面上的不同,但背后却深深隐藏了理念上的不同。
Stallman先生对于软件自由的信仰感染了我,我相信也感染了在座的所有人。



第二:自由软件与教育


     这一点是戳到了中国的痛处了。Stallman先生打了一个比方,将微软让学生使用它的软件比作让人吸毒(当然,他没有指名道姓的说这是微软,但是大家都
知道),第一口都是免费的,让你上瘾,让你依赖,然后再向你收费榨干你。其实大家都知道微软的这种伎俩,也都感到了这种策略的危害,但是只有像
Stallman先生这样的大牛直白的指出来,很多人才愿意正视这个问题。在中国,99%的计算机用户都在使用Windows,绝大多数人都只知道
Windows。以中国现在的状态,微软今后即使什么都不干,光靠在中国打官司赢的钱都足够生存很久了。更可怕的是中国现在的以及未来的技术人员的思想都
陷入了以Windows为核心的微软生态系统的牢笼之中,这种遗毒的影响时间只会更长更广。


     演讲最后,Stallman先生化身为“英雄”,身边开启“自由软件之力”的光环,大家都被笼罩了,哇……(其实具体是啥样没去的同学自己想象吧,哈哈)


     Stallman讲话的语速很慢,用词也很平实,甚至简单,就是为了让更多的听众能够理解和接受他的理念。最后大家一窝蜂冲上去和偶像照了一个全家福。我手头现在没有任何照片,收集全了下次再贴。
     晚上所有志愿者和Urutashibata、Stallman一行去吃饭,我厚着脸皮跟去蹭饭成功。饭桌上Stallman对饭菜赞不绝口。只是后来大家聊的很高兴,几乎忽略了两位外国友人的存在,酒足饭饱才想起来把人家无视了,囧。

2009年10月7日星期三

用Screen和Vim进行结对编程

小廖同学在google reader上共享了一篇《Remote Pair Programming with Screen and Vim》,突然想起来这正是我们上个月在体验结对编程时使用的一个技巧。

Screen是个非常强大的终端工具。强大之处之一就在于有个 -x 参数,能够连接到已经存在的screen会话之中。那天老大从2009敏捷中国年会上回来,让咱们试试结对编程。我们没有Google或者Fog Creek那么好的条件,那就挖掘一下现有工具的潜力吧。

两个人用同一个用户ssh到一台工作机上。一个人用screen启动一个会话,另一个人用screen的 -x 参数连接到那个会话上去。这样,每个人的动作都会即时的反映到另外一个人的屏幕上去,没有主从关系,任何操作,也并不限于Vim。这里我们只是可以用Vim做结对编程,实际上用这个做一下shell的教学啥的也很不错。

这样做的好处是可以远程结对,没有空间限制。当然,结对编程的一个好处就是两个人在一起可以随时沟通,这个就是另外的讨论了。

2009年9月23日星期三

用Xming远程访问Linux下的图形界面程序

在Windows下我不怎么使用命令行,要用也是用PuTTY。当你ssh到一台远程Linux机器上然后在终端下输入gvim,会发生什么?一般情况下会是这样:

E233: Can not open display
Press ENTER to continue...

是的,你现在是ssh到一台远程的主机,事情和你坐在那台机器面前的时候有点不一样了。如果你手头只有一台Windows、没法回到那台主机身边(比如你被关小黑屋了)而你又真的很想运行其上的某个图形界面的程序该怎么办?Xming是一个选择。

Xming的安装很简单,在我的Windows7上也很顺利。安装完之后一个X Server就悄然无息的跑了起来。一般来说你不需要进行任何额外的设置,把鼠标移到任务栏的X Server图标上,会显示提示“Xming Server:0.0”。

OK,现在打开PuTTY,在“Connection -> SSH -> X11”中选中“Enable X11 Forwarding”,在“X display location”中填上“localhost:0.0”(有文章说是“localhost:0”,但是在我这里0.0才工作),像这样:
连接上远程主机之后,你最好去检查一下/etc/ssh/sshd_config这个文件中的“X11Fowarding”这个选项,别被注释掉了,值应该是yes。

设置部分就完成了。这个时候你在终端下键入gvim回车,几秒钟之后应该会弹出一个Windows窗口,而里面显示的正是你想要的gVim

比较幸运的是我没有碰到字体之类的问题。如果你能够打开gVim但是里面显示的都是些小方块,可以看看官方的文档,以及以下两篇blog:
Putty + Xming 方便的远程Linux GUI
Xming + PuTTY 在Windows下远程Linux主机使用图形界面的程序

当然,还有一些和Xming类似的商业软件,性能也比Xming要好一些。不过怎么看Xming这道免费的午餐都已经很不错了。

2009年9月7日星期一

使用svn的“属性”自动填写代码注释

我以前觉得自己对svn的运用还算比较熟练的了,一般的主干、分支和标签的管理,差异、合并、日志等等命令的运用也比较得心应手了。今天在cpyug上看到一个关于使用svn自动填写版本号等信息的帖子才发现自己完全把svn的“属性”遗忘了。svn的property能够干的事情真的大大超乎我的想象。

svn的svn:keywords属性能够自动把代码里的一些关键字替换成svn的一些信息,包括最后修改的版本号、修改人、修改时间、文件在版本库中的位置和文件在版本库中的id。这一下子就解开了我看很多其他项目源代码时的疑问,当时怎么也没想明白他们是怎么把这些填到代码注释中去的。这个在svn的在线电子书的Keyword Substitution一节里讲的很详细。

在文件的开头加上:
#$Author$
#$Date$
#$Rev$
#$URL$
设置svn:keywords属性并提交后这一段会变成:
#$Author: xie $
#$Date: 2009-09-06 18:32:58 +0800 (日, 2009-09-06) $
#$Rev: 15 $
#$URL: file:///home/xie/svnrepo/test.py $

但是这还很不够,因为:
1、需要自动为所有现有的python源代码文件的开头加上这样的注释
2、需要保证所有新增的python源代码文件的开头都有相同的注释
3、需要保证所有新增的python源代码文件都被加上了适当的svn:keywords属性

要解决第一个问题在Linux下并不难,因为我们有sed。脚本很简单,在所有.py文件的第二行加上四行注释:
#!/bin/sh
for x in $(find ./ -type f -name "*.py")
do
    sed '2 i\#\$Author\$\n#\$Date\$\n#\$Rev\$\n#\$URL\$' $x > $x.new
    mv $x.new $x
done

第二个问题有点麻烦。我的第一感觉是如果在提交的时候能有脚本为所有python源代码文件插入这段注释就好了,不过事实上不行。svn有钩子(hook)的概念,可以在特定事件发生的时候执行某个脚本。这个脚本不应该修改提交的任何内容,而应该根据一系列规则检查提交的内容。如果提交的东西不合适,就让提交失败,这样提交者就会根据提示信息去修正并再次提交。svn很厚脸皮的把工作还是扔给了苦命的程序员。这里我们不希望没有这段注释的源代码提交成功,就需要利用pre-commit事件,在提交之前检查提交的内容,如果提交的内容不含有我们需要的关键字那就提示错误信息并使提交失败。

pre-commit事件执行的脚本的环境是shell,也就是说你可以用你喜欢的任何语言,比如perl、python甚至javascript(如果你在服务器上安装了SpiderMonkey的话)来写这段检查脚本。任何一个新建的svn版本库的hooks目录下都有一陀例子脚本,svn的官方网站上也有一些示例脚本,但都不及一位叫Thomas Guest的同学的博文说的明白。我对照着他的脚本稍微修改了一下就很快写出了一个符合我们需要的脚本,来检查版本库的某个特定目录下的所有文件名符合某个模式的文件是否符合一定的规则。

第三个问题无法在服务器端解决,原因和第二个问题一样。合适的做法就是在pre-commit脚本中检查提交的文件是否含有适当的svn:keywords属性。如果属性不正确,就拒绝提交。

以上两个限制强制程序员在提交的时候为需要提交的文件加上svn:keywords属性并在文件开头添加适当的注释,但是人总是不完美的,肯定有忘记的时候。怎么办?这就需要程序员配置自己的svn客户端,打开“自动属性”(auto-prop)选项。通常,配置文件是~/.subversion/config,编辑这个文件,将enable-auto-prop设置成yes,然后在最后那个小节中为某个类型的文件设置svn:keywords属性,这样所有新增到版本控制的文件都会自动拥有这个属性了。

当然,现有的源代码还是没有这个属性,我们得批量的设置一下,一行命令就搞定了(比如我们的目标是当前目录下的所有python源代码文件):
find ./ -type f -name "*.py" | xargs svn propset svn:keywords "Author Date Revision HeadURL"

2009年9月4日星期五

给非IT码农的版本控制初级教程

我女朋友使用Subversion来管理她的论文,然后觉得非常好用,因为Word支持svn diff。她是学城市规划的,我和她发现版本控制这东西虽然在IT领域已经成为不可缺少的工具,但在其他行业似乎还是完全的一片空白。程序员的工作成果是源代码,建筑师的工作成果是设计图纸,这些都是纯智力的劳动成果。尽管图纸是二进制文件,但是这并不是对它们进行版本控制的障碍。

为此我特意为这些领域的朋友们写了一篇非常粗浅的subversion教程,希望能够帮助他们管理自己的图纸,不用再把它们备份的到处都是:)

Here we go:
http://docs.google.com/View?id=dc274zp8_49cm6x6bff

2009年8月17日星期一

庖丁编程

庖丁解牛的程序员版(学学这英文……啧啧……取自《The Tao of Programming》 4.4节 by Geoffrey James)


Prince Wang's programmer was coding software. His fingers danced upon
the keyboard. The program compiled without an error message, and the
program ran like a gentle wind.

``Excellent!'' the Prince exclaimed, ``Your technique is faultless!''

``Technique?'' said the programmer turning from his terminal, ``What I
follow is Tao - beyond all techniques! When I first began to program
I would see before me the whole problem in one mass. After three
years I no longer saw this mass. Instead, I used subroutines. But now
I see nothing. My whole being exists in a formless void. My senses
are idle. My spirit, free to work without plan, follows its own
instinct. In short, my program writes itself. True, sometimes there
are difficult problems. I see them coming, I slow down, I watch
silently. Then I change a single line of code and the difficulties
vanish like puffs of idle smoke. I then compile the program. I sit
still and let the joy of the work fill my being. I close my eyes for
a moment and then log off.''

Prince Wang said, ``Would that all of my programmers were as wise!''


Code-Illuminated for Python

最近在关注Bespin的时候发现了一个有意思的小东西,Code-Illuminated项目。Mozilla Ubiquity项目组为了更好的注释代码而创建了它,Atul Varma在Beautifully Documented Code之中详述了它的前生今世。

我第一眼就喜欢上了这个小玩意儿,因为我觉得他比之doxygen等传统代码文档生成工具有以下几个优点:
  1. 形式简洁:左注释右代码,一目了然
  2. 表现力丰富:使用CroeleWiki作为注释格式,注释中的代码量更少,表达能力更强
  3. 免编译:纯web表现,无需额外工具,速度也非常快

兴奋了一下之后我的第二个念头是,它能用在Python上面么?貌似不行,现在它只能处理Javascript。不过没关系,毛爷爷说“自己动手,丰衣足食”,开源的好处就在这里。CI的逻辑很简单,小改一下就好了(中文注释也没有问题)。


正如Atul的博文中所说,这东西还有很大的潜力可以挖掘。Bespin作为一个在线的源代码编辑应用程序,已经集成了Mercurial的一些版本控制命令。如果Bespin再集成上这么一个在线代码生成和查看的组件将会如何?嘿嘿……

2009年8月15日星期六

SICP 第二章(下)

和上一章一样,附上我写的习题程序,仅供参考。想要做对这些习题可不太容易,经常是做到下一题的时候才发现上一题甚至几题的解法有缺陷,又回头去改,反反复复,不知道什么时候才是完美。2.4和2.5节的习题我只在Eli Bendersky的blog上看到了,不过是用clisp写的,我的版本是MIT Scheme。这两节的题目可以看做是一个大作业,很难很复杂,但是如果不做,可以说第二章就相当于没看了。

2.4/5节也”路过“了一个计算机语言设计中很重要但不太引人注目的问题,那就是”类型“。程序语言的类型系统的设计和构建并不是看起来那么简单的问题。有兴趣的同学可以看看Benjamin C.Pierce《Types and Programming Languages》,国内译作《类型和程序设计语言》,不过貌似翻译的并不太好。

http://dl.getdropbox.com/u/1584769/chapter2.tar

2.53
(list 'a 'b 'c) = (list (quote a) (quote b) (quote c)) = (a b c)
(list (list 'george)) = (list (list (quote george))) = ((george))
(cdr '((x1 x2) (y1 y2))) = ((y1 y2))
(cadr '((x1 x2) (y1 y2))) = (y1 y2)
(pair? (car '(a short list))) = #f
(memq 'red '((red shoes) (blue socks))) = #f
(memq 'red '(red shoes blue socks)) = (red shoes blue socks)

2.55
1 ]=> (car ''abracadabra)
;Value: quote
其实为什么会这样作者已经在注释中解释了
“'”等价于quote函数,单引号都会被翻译成(quote x x x),就好像C语言中的宏替换一样
替换之后,在最外层的quote作用下,内层的quote就变成一个符号而不起函数的作用了
就好像'(quote abracadabra)一样

2.56
该小节中给出的选择函数还是有问题的
    (deriv '(x) 'x)
会出错
原因很简单,cadr的参数不够长

2.60
函数                复杂度
element-of-set?     O(n)
union-set           O(n)
adjoin-set          O(1)
intersection-set    O(n^2)
相比原来,adjoin-set的复杂度明显降低了,其他不变。
显然,这种允许重复的集合更加适合频繁增加元素的场合。

2.63
a) tree2list-1显然是把树变成“左中右”的表,相比之下tree2list-2就不是那么明显了,不过结果是这两者的结果是相同的。
b) 两者都是O(n)的复杂度

2.64
这个函数只是生成的平衡二叉树,并没有经过排序(左小右大)
比如,(list->tree (list 3 1 5 7 9))只会生成(5 (3 () (1 () ())) (7 () (9 () ())))
该函数依赖于排好序的参数才能产生符合要求的二叉树
不过,假设2.63-2.65的输入都是已经排序的也无妨
a) tree->list总是平衡的,因为它往一侧放多少个元素,就会往另一侧放相同多的元素
b) 复杂度为O(n),每个元素只调用了一次tree->list

2.65
因为2.62以及“集合作为排序的表”(sets as ordered list)一节已经实现了O(n)的union-set和intersection-set
而这里的list->tree以及tree->list也都是O(n)的,所以只需要把树先转成表,然后合并表,最后把表转成树
这样的函数的复杂度也肯定是O(n)的
注意,能够这样的前提条件是输入的树(表)都是已经经过排序的

2.67
左0右1
(0 1 1 0 0 1 0 1 0 1 1 1 0) -> (a d a b b c a)

2.68
(a d a b b c a) -> (0 1 1 0 0 1 0 1 0 1 1 1 0)

2.69
验证生成的树的办法就是把上两题用这棵树编码和解码
不过,编码解码的结果和上两题不同是完全有可能的,因为Huffman树不是唯一的

2.70
生成的树:
((((((leaf get 2) (leaf job 2) (get job) 4) (leaf sha 3) (get job sha) 7) (((leaf boom 1) (leaf wah 1) (boom wah) 2) (leaf a 2) (boom wah a) 4) (get job sha boom wah a) 11) (leaf yip 9) (get job sha boom wah a yip) 20) (leaf na 16) (get job sha boom wah a yip na) 36)
编码(84位):
(0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 0 1 0 0)
如果使用定长编码,字母表中有8个符号,每个符号长为3位
消息中共有36个符号,总长需要36*3=108位

2.71
这样的树有n个元素,频度为2^k次方的符号需要k+1位来表示
这样,出现最频繁的符号只需要一位,而最不频繁的需要n-1位

2.72
member-of?函数的复杂度为O(n)
对于出现最频繁的符号,在将其编码的过程中,只会用member-of?检查一次根节点的symbol(其中包含n个元素)
因此将出现最频繁的符号编码的复杂度为O(n)
对于出现最不频繁的符号,在将其编码的过程中,会使用member-of?检查所有中间节点的symbol
最坏情况下的比对次数为n + (n-1) + (n-2) + ...
因此将出现最不频繁的符号编码的复杂度显然是O(n^2)

2.73
要顺利跑起来这一题,记得从SICP的官方网站把第二章的代码下载下来,里面有get/put函数,它们都是第三章才实现的
另外还需要从2.56拿一部分代码过来
注意:重写的deriv是有问题的,实际上并不需要使用operands函数
d) 理解了<op>/<type>表,改动很简单
    --- 2.73d.scm    2009-08-08 15:31:56.000000000 +0800
    +++ 2.73b.scm    2009-08-08 15:31:17.000000000 +0800
    @@ -86,9 +86,9 @@
                                       (deriv (base expr) var)))
             ;; interface to the rest of the system
             (define (tag x) (attach-tag 'deriv x))
    -        (put '+ 'deriv sum)
    -        (put '* 'deriv multiply)
    -        (put '^ 'deriv exponentiation)
    +        (put 'deriv '+ sum)
    +        (put 'deriv '* multiply)
    +        (put 'deriv '^ exponentiation)
             'done)
     
     (define (variable? expr) (symbol? expr))
    @@ -100,7 +100,7 @@
                   ((variable? expr) (if (same-variable? expr var)
                                         1
                                         0))
    -              (else ((get (operator expr) 'deriv) expr var))))
    +              (else ((get 'deriv (operator expr)) expr var))))
     
     (install-deriv-package)
     (newline)

2.74
a) 题目明确说了名字是主键,那就使用key方法来取得一条记录的主键值
    (define (key record) (car record))
    (define (content record) (cadr record))
    (define (get-records division)
                ; god knows how to get them
            )
    (define (get-record name division)
            (define (get-record-internal name records)
                    (cond ((null? records) ())
                          ((eq? name (key (car records))) (content (car records)))
                          (else (get-record-internal name (cdr records)))))
            (get-record-internal name (get-records division)))
b) 从指定的部门取某个人的salary信息,就要先拿到这个部门的get-salary方法,再拿到这个人的记录
   这些操作对记录的结构并没有特殊的要求,只要能够取得相应的get-salary方法就好
    (define (get-salary division name)
            ((get 'get-salary division) (get-record name division)))
c) 好像并没有什么特别之处……
    (define (find-employee-record name divisions)
            (if (null? divisions)
                ()
                (let ((record (get-record name (car divisions))))
                     (if (eq? record ())
                         (find-employee-record name (cdr divisions))
                         record))))
d) 新公司必须有唯一的部门标识符,必须要注册自己的一系列访问函数

2.76
显式分派是最糟糕的设计。数据导向的设计会使加入新的新的操作比较容易,消息传递方式的设计会使加入新类型比较容易。
只要看看那张<op>/<type>表就很清楚了。

2.77
开始之前,先期
(install-rectangular-package)
(install-polar-package)
(install-scheme-number-package)
(install-rational-package)
(install-complex-package)
先定义z这个复数:
(define z (make-complex-from-real-imag 3 4))
-> lookup ('make-from-real-imag, 'complex) from the (<op>, <type>) table
-> ('complex (make-from-real-imag 3 4)), from complex package
-> lookup ('make-from-real-imag, 'rectangular) from the (<op>, <type>) table
-> ('complex 'rectangular 3 . 4), from rectangular package
然后求它的模:
(apply-generic 'magnitude z)
-> lookup ('magnitude, 'complex) from the (<op>, <type>) table
-> error: No method for these types -- APPLY-GENERIC (magnitude (complex))
原因:操作类型表中只存在('magnitude, 'rectangular)和('magnitude, 'polar),并不存在('magnitude, 'complex)
      搜索一下代码很容易就可以看出这一点
为install-complex-package函数添加了题目中的代码之后,运行的过程应该是这样的:
(apply-generic 'magnitude z)
-> lookup ('magnitude, 'complex)
-> (apply-generic 'magnitude ('rectangular 3 . 4))
-> lookup ('magnitude, 'rectangular)
-> magnitude method from rectangular package
-> 5
注意:apply-generic方法在查找操作类型表时,都是查找的方法所在的表
      例如,参数是'magnitude,即查找magnitude方法时,实际查找的是'(magnitude)
      因此,在构造操作类型表时需要特别注意这一点,否则会找不到方法

2.80
此题和2.79需要注意的地方一样,除了scheme-number,其他包中都不能简单的使用等于零来判断,而应该复用通用的=zero?

2.81
(define put-coercion put)
(define get-coercion get) ;这样就可以调试了
a) 会陷入complex->complex的死循环

2.82
不知道是不是我对Scheme还不熟的原因,调试程序实在是让我头大
反例还是很好举的,例如(add rational complex scheme-number)(这里只写出了类型)
而操作类型表之后只有rational->complex和scheme-number->rational
就会出现这样的情况:
    因为没有complex->rational,没法将所有加数都转化为有理数
    因为没有scheme-number->complex,没有办法将所有加数都转化为复数
    因为没有rational->scheme-number和complex->scheme-number,没有办法将所有加数都转化为一般数
    所以加法就算不了了
实际上,用scheme-number->rational之后这个加法还是可以算的,但此时apply-generic无法识别这种情况
另外,要测试这一题还要另外修改一下你希望测试的方法,因为所有的方法的参数个数是一定的

2.83
raise是通用的,不需要为每一种类型添加一个raise操作,但两个类型之间的互相转换是必不可少的
这里为了简单起见,认为scheme-number就是整数

2.84
还是局限在两个参数的情况比较好处理
type-gt?和type-lt?的道理是一样的,就是t1和t2在比赛
从最低级的开始,先碰到t1,说明t1比t2小,反之亦然

2.85
只实现了复数的部分,其他类似

2.86
因为复数是塔的顶端,所有其他类型的数都能转化为复数。
2.84和2.85的apply-generic结合之后可以比较完美的自动化解决类型上下转换的问题
把他们应用到2.81的代码中去就好

2.88
在2.80的基础上引入polynomial-package
在polynomial包内部,不能使用=zero?作为内部函数的名称
因为=zero?是一个全局函数,而且用在了polynomial包的内部
这里用的是is-zero,防止出现命名冲突

2.89
这一题和2.90和在一起做了,程序太长了受不了了,没有vim的折叠帮忙实在驾驭不了

2.91
做出来2.89和2.90的话这一题就很简单啦,再说大部分程序书上都给你写好了
不过书上只要你加上div-terms,如果你向我一样手痒的话,也可以顺带加上div-coeffs
这两个除法函数都只应该返回一个多项式,那就是商,所以呢我们还应该有mod-terms/coeffs,来返回余数多项式……
这个代数系统也不是没有问题的,比如=zero?的结果,按理说应该是一个布尔值,但是布尔值的类型应该是布尔,而不应该是多项式

2.92
要把这个题调通,首先要把前面写的类型转换的代码集成进来,为了方便,我们还是只处理两个参数的情况
类型转换树是complex <- rational <- scheme-number
想了半天没想出来,怒了,去Eli Bendersky的blog翻到2.5.3看2.92的解答,结果只有一个词:skiping……囧……
继续Google,搜到纽约大学一个博士后的blog上面有他的2.92的程序,看得一知半解
觉得他的测试数据有问题,至少没有测到我最关心的几种特殊情况
黔驴技穷了……

2.93
2.91的时候提了一下=zero?应该返回布尔类型的值,这一题就遇到这问题了,还好不是很难改
之前手比较贱,一不小心把这一题要做的东西差不多都改完了,现在要加的就是一个通用型的gcd
我的程序里的general-gcd比2.93题目中的更加一般,不仅仅局限于稀疏型的多项式,这样看起来2.93的代码已经把2.94也做完了
有个比较有趣的地方就是题目给的分子x^3+1(即('x '((3 1) (0 1))))和分母x^2+1(即('x '((2 1) (0 1))))的最大公约数并不是1
而是(polynomial sparse-poly x (0 2))。这样,由于在构造有理数(rational)的时候使用了general-gcd,最初构造出来的rf就是:
    (rational (polynomial sparse-poly x (3 1/2) (0 1/2)) polynomial sparse-poly x (2 1/2) (0 1/2))
而rf + rf则是:
    (rational (polynomial sparse-poly x (3 1) (0 1)) polynomial sparse-poly x (2 1/2) (0 1/2))
幸运的是,这个结果至少没错。

2.95
一开始这个结果太难看了:(polynomial sparse-poly x (2 1458/169) (1 -2916/169) (0 1458/169))
没人不会怀疑它是不是错了,不过从高考那种专门给难看数据的考试得出的经验,也许我们不应该怀疑MITScheme解释器,因为它之前都工作的好好的
这个体系另一个缺陷的地方是对于有理数,复数使用的sqrt/square/atan这几个函数是绕不过去的
也就是说,如果你试着用实部或者虚部是有理数的复数进行乘除运算,那肯定会出错
如果你更手痒一点,把这个问题也解决了吧……我累了,为'(scheme-number polynomial)写了一个乘法函数,作为一点小小的妥协
另外,我死都没法把drop/project集成进apply-generic,即使我做了正确的修改
这两个东西的问题在于有可能被非类型塔的类型调用,比如complex下面的rectangular/polar,或者polynomial下的sparse/dense-poly
2.95.scm实际上已经把2.96a做了



2009年8月9日星期日

SICP 第二章(上)

第二章比较长,习题也多很多,所以还是分两次贴比较好。完成这一章的习题要花一些功夫,比如需要DrScheme来画2.2.4那一节的小人儿,2.5节也需要额外代码的支持。习题做到最后,代码都很长了,一个文件里有三百多行。Scheme代码的可读性和条理性在没有注释的情况下比其他语言要差多了,希望下一章能够解决这个问题。

2.6
这个题目比较好玩,使用到了Church Numerals。
第一眼看到这个问题马上让我想起了离散数学,因为他们都定义了一套运算体系。
上wiki查了一下Church Numerals,说的很详细,让我理解了一些基本概念。
one和two的定义用zero和add-1可以比较容易的推导出来。
看了wiki上的说明以及one和two的定义可以看出,n的意义就是对参数x应用n次参数f。
知道这个再写加法就简单了,特别是add-1可以看作n和one的加法。我基本是像搭积木那样把add方法搭出来了。
(define (add a b) (lambda (f) (lambda (x) ((b f) ((a f) x)))))

(add one two)
(lambda (f) (lambda (x) ((two f) ((one f) x)))
(lambda (f) (lambda (x) ((f (f ((one f) x))))))
(lambda (f) (lambda (x) (((f (f (f x)))))))

2.9
(define x (make-interval 6.12 6.8))
(define y (make-interval 4.47 4.9))
(newline)
(display (= (width-interval (add-interval x y)) (+ (width-interval x) (width-interval y))))
很遗憾,上面这段程序执行的结果是:#f
我很不解,用display分别打印了这两个结果,显示的都是0.555,于是我就更不解了。
郁闷了一小会儿,想起来自己默认是用的guile,于是改用MIT Scheme,还是#f。
但是MIT Scheme分别打印出来的两种方式的运算结果是:
    ;Loading "2.7.scm"...
    #f
    .5549999999999997
    .5550000000000002
    ;... done
这就清楚了,原来他们真的“有点”不一样。

2.13
数学上比较简单。
假设第一个区间的中点是v1,误差是t1;第二个区间的中点是v2,误差是t2。v1 t1 v2 t2都大于零。
乘积的区间是[v1*v2*(1-t1-t2-t1*t2), v1*v2(1+t1+t2+t1*t2)],显然误差就是(t1+t2)/(1+t1*t2)
由于t1和t2都非常小,t1*t2就成芝麻了,忽略,结果就成了一个非常简单的t1+t2。
对于v1 t1 v2 t2的值分布的其他情况同理可证。

2.14
确实不一样,为什么呢?

2.15
要解释2.14的问题,需要回过头来看练习2.9。
2.9表明了两个区间进行加减的时候,结果区间的误差也是参数区间用同样运算符进行运算得到的结果。
区间相加,误差也相加
但是这个结论对于其他运算(减法、乘法和除法)竟然都并不成立。
对于减法,区间相减,误差还是相加。
沿用2.13的变量,两个区间相乘的结果的误差t=(t1+t2)/(1+t1*t2)。算出除法的结果误差有点麻烦,但结果也是t=(t1+t2)/(1+t1*t2)。
因此,对于par1和par2,需要对每一步的运算的误差进行计算,才能得到t(par1)和t(par2)。(好消息是t(one)=0)
步骤省略,结果是:
t(par1)=(t1+t2)(2+t1*t2)/(1+3*t1*t2+t1^2+t2^2) ~ 2*(t1+t2)
t(par2)=(t1+t2)
显然,按照这种计算方法,t(par2)只有t(par1)的一半不到。

2.16
为什么等价的代数表达式可能导致不同计算结果?
这本来就是一个伪命题。既然他们是等价的,计算结果就不应该不同。
唯一的可能就是,我们使用的方法不正确,也就是说,误差的计算是不能传递的。
要证明这一点很容易,很显然,除法就是误差传递的障碍。
没有缺陷的区间算术表示方法也许存在,但至少我不知道,嘿嘿。

关于区间运算,其实还有很多有意思的东西。下面这个链接就是一个入口,等我重新温习《具体数学》的时候再回头看吧:
http://wiki.drewhess.com/wiki/SICP_exercise_2.16

2.19
coin-values的排列顺序和最后结果无关,组合部分先后顺序。

2.22
(cons 4 ())和(list 4)等价,因为表的特征就是最后一个cons的后半部分指向nil
但是(cons () 4)产生的并不是(4),而是(() . 4)。
为什么?这个序对的后半部分不是指向nil,当然和(list 4)完全不同。

2.23
如果想在一个条件分支中写多个语句,记得用cond

2.24
(define x (list 1 (list 2 3))) -> (1 (2 3)) -> (cdr x) = ((2 3))
(define y (cons 1 (list 2 3))) -> (1 2 3)   -> (cdr y) = (2 3)
(define z (cons 1 (cons 2 3))) -> (1 2 . 3) -> (cdr z) = (2 . 3)
为什么会有这样的不同呢?最关键的地方在于(list 1 2)和(cons 1 2)是不等价的。
(list 1 2)等价于(cons 1 (cons 2 nil)),表(list)的最后一个元素总是空(nil)。
这样就比较好画方块图和树了。

2.26
把list转化为cons就好

2.29
程序我没有测试,因为书上没有给测试数据,而且测试数据构造也太麻烦了,就pass了。
至于d),修改非常简单,把多余的car干掉就好

2.32
程序没错,去google了一下别人的填空,一样,但是不知道为什么scheme就是跑不出来,guile也报错 -_-

2.42
这个题真的难了我一下,不过也可能是我头天晚上去K歌的缘故
卡住我的主要是棋盘的表示方式,其实只需要把棋盘看作一个栈,把行号逐一压进去就好
不过safe?函数的k参数迷惑了我,让我以为需要把行号和列号作为一个序对压栈
后来回头google了一下,还真的有人用序对的方法作出了这个题
不过这样的话效率肯定是比不过只用整数的咯

2.43
Louis把嵌套映射的顺序改变后,程序变成了完全的深度优先搜索,没有剪枝的效果,程序要走完整个棋盘的所有组合,也就是8^8次方种可能性……
因此Louis的程序的复杂度很显然是O(n^n),因为queen-cols函数被他执行了n^n遍
相比之下2.42的程序的复杂度是O(n),度量的准绳就是queen-cols函数被调用的次数
O(n^n)/O(n)并不能改变O(n^n)的本质,Louis的程序比老师的慢了O(n^n)个数量级

2.44
比较可惜,没法测试这个程序

2.49
鬼知道wave是由什么坐标连起来的……


2009年8月4日星期二

代码边线

今天闲逛的时候看到Intel官方blog上的一篇帖子《给VS2005的编辑器添加右边界线》,很有意思,因为好的代码风格是代码成功的一半。

Vim能不能也有相同或者类似的功能呢?google之,Vim果然不负我望(http://stackoverflow.com/questions/235439/vim-80-column-layout-concerns):

 highlight OverLength ctermbg=red ctermfg=white guibg=#592929
 match
OverLength /\%121v.*/


这段代码设置了一个新的配色方案叫OverLength,背景红色前景白色,第二句话将其和每行超出120个字符的部分进行匹配。什么意思呢?换句话说,在执行了以上代码之后,Vim会用红色高亮你编辑的文本每行中超出120个字符的部分,像这样:

虽然不像Visual Studio里面用线标出界限那么显而易见,但和textwidth等选项一起用起来也足够了:)

2009年8月2日星期日

Valerie Aurora

标题并不是什么电影明星的名字,她是一位Linux Kernel Hacker,一位女性。她的主页在这里

我也不知道怎么转到了她的blog,反正这两天我就在各位大牛的博客中游荡,好像刘姥姥进了大观园,觉得什么都新鲜。Aurora为Linux内核的主要贡献在文件系统方面,btrfs/zfs什么的,我能找到的和她唯一的共同点就是非常喜欢漫画Calvin&Hobbes:)

Aurora另一个让我关注的地方就是她在2002年写了一篇文章《HOWTO
Encourage Women in Linux
》,并且是LinuxChix社区的创始人之一。LinuxChix.org旨在帮助女性了解和使用Linux并使她们更轻松的融入开源社区。事实上在国内,大多数人在进入开源社区时遇到的困难和Aurora在文章中提到的女性融入开源社区的困难非常类似,很多情况下语言的障碍和性别的障碍一样让人望而却步。

BTW: 上个星期的荷包岛之旅,我的脚底板被晒伤了……

2009年7月30日星期四

为什么你不应该在键盘和鼠标上吝啬

在我的个人电脑上(Windows系统)装了一个叫WinOMeter的小东西,它可以统计你的键盘击键次数、鼠标击键次数以及鼠标移动的里程。我也不记得是怎么装上了这个东西,可能是从小众软件看到的吧。它在我的系统里从三月二十四号开始静悄悄的运行着,到现在已经有四个月了。这四个月我都干了什么?看看数据就知道了:

键盘敲击总数:1159010
键盘敲击接近116万次。我在上班的时候一般把这台笔记本打开,扔在一边,让它开着QQ接收一些个人信息放个歌啥的,总之很少动它。下班的时候一般会看在上面看看网页啥的。即使这样,在四个月中,它接受的键盘敲击仍然超过100万次。我在工作机上要写程序,对键盘的操作至少是对这台笔记本的五倍以上。可以想象,这四个月中我对键盘的敲击肯定超过了500万次!

鼠标移动总里程:79.444526km
我对鼠标的需求是很少的,两台机器上都几乎只使用Firefox(Vimperator)和终端(putty)。即使这样,鼠标同志也很辛苦,四个月跑了接近80公里。普通的Windows用户的鼠标肯定要比我的鼠标不幸的多,承受的压力更大,跑的路应该是我的五倍以上,即80*2*5=800km!

鼠标点击(左中右):427878,168458,162137
这里面有一部分的功劳是Starcraft的。

开机时长:81d
三月二十四日到现在一共才98天,这台本本的运行时间达到了81天,我是不是对它太残酷了……其实我“基本上”每天晚上都关机的……这样算下来它平均每天的休息时间大概是(98-81)*24/98~=4.16个小时。我不喜欢睡觉,它也跟着倒霉。城门失火,殃及池鱼啊。

这就是我愿为键盘鼠标花一千块钱的原因。

2009年7月29日星期三

Cheat-Sheet for Vimperator

今天看到一个有意思的网站www.cheat-sheets.org,在上面找到了Vimperator的cheat-sheet,竟然还有三种。

中间的一张最传统,我也最喜欢:http://vi.shiar.net/vimperator(它竟然不是图片!)

2009年7月21日星期二

SICP 第一章

大四的时候就听说《Structure and Interpretation of Computer Programs》这本书很不错,但一直没有时间拿来看看。正巧一个同事正在看这本书,于是我也来凑个热闹,大家可以一起讨论,不亦乐乎?

习题程序:http://files.getdropbox.com/u/1584769/chapter1.tar

1.5
(test 0 (p))
使用应用序求值时,会先对参数进行求值,即将这个表达式中的(p)展开为(p),然后就死循环了,根本没有计算if表达式谓词的机会。

1.6
错:把if替换成为new-if之后就不是尾递归了,然后就堆栈溢出了
对:解释器的对于用户自定义函数的解释方式是应用序的,new-if也不能例外。
这样的后果就是new-if在被展开之前,它的参数就需要被计算,然后就死循环了。

1.7
试了一下,精度是16位。使用原有的good-enough?函数会出现的问题是当根的值达到16位这个极限的时候出现溢出。
当n很大的时候,根的平方减去n之后得到的是负数,那肯定小于给定的阈值咯。
使用新的good-enough?之后,对于很大的数和很小的数也能得出一个平方根的近似值。
当阈值(波动幅度)小于0.1%时,这个根的平方减去n的差值和n的比值大概在e-5这个数量级。

1.9
(define (+ a b)
(if (= a 0)
b
(inc (+ (dec a) b)))
)
(+ 4 5)的执行过程应该是这样的:
- (+ 4 5)
- (if (= 4 0)
- 5
- (inc (+ (dec 4) 5)))
- (inc (+ (dec 4) 5))
- (inc (+ 3 5))
- (inc (if (= 3 0)
- 5
- (inc (+ (dec 3) 5))))
- (inc (inc (+ (dec 3) 5)))
- (inc (inc (+ 2 5)))
- .
- .
- .
- (inc (inc (inc (inc (+ 0 5)))))
- (inc (inc (inc (inc 5))))
- (inc (inc (inc 6)))
- (inc (inc 7))
- (inc 8)
- 9
递归

(define (+ a b)
(if (= a 0)
b
(+ (dec a) (inc b))))
(+ 4 5)的执行过程应该是这样的:
- (+ 4 5)
- (if (= 4 0)
- 5
- (+ (dec 4) (inc 5)))
- (+ (dec 4) (inc 5))
- (+ 3 6)
- ...
- (+ 2 7)
- ...
- (+ 1 8)
- ...
- (+ 0 9)
- 9
迭代

1.10
(define (f n) (A 0 n)) == (f(x, y) = 2y)
(define (g n) (A 1 n)) == (g(x, y) = 2^y)
(define (h n) (A 2 n)) == (h(x, y) = 2^2^... (n次))

1.20
应用序:
(gcd 206 40)
(gcd 40 (remainder 206 40))
(gcd 40 6)
(gcd 6 (remainder 40 6))
(gcd 6 4)
(gcd 4 (remainder 6 4))
(gcd 4 2)
(gcd 2 (remainder 4 2))
(gcd 2 0)
remainder执行了4次

正则序:
(gcd 206 40)
(gcd 40 (remainder 206 40)) ; gcd(40 6)
(if (= (remainder 206 40) 0) ; +1
40
(gcd (remainder 206 40) (remainder (remainder 206 40) 40)))
(gcd (remainder 206 40) (remainder (remainder 206 40) 40)) ; gcd(6 4)
(if (= (remainder (remainder 206 40) 40)) ; +2
(remainder 206 40)
(gcd (remainder (206 40) 40) (remainder (remainder 206 40) (remainder (remainder 206 40) 40))))
(gcd (remainder (206 40) 40) (remainder (remainder 206 40) (remainder (remainder 206 40) 40))) ; gcd (4 2)
(if (= 0 (remainder (remainder 206 40) (remainder (remainder 206 40) 40))) ; +4
(remainder (206 40) 40)
(gcd (remainder (remainder 206 40) (remainder (remainder 206 40) 40))
(remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40)) (remainder (remainder 206 40) 40))))
(gcd (remainder (remainder 206 40) (remainder (remainder 206 40) 40))
(remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40)) (remainder (remainder 206 40) 40)) ; gcd(2 0)
(if (= 0 (remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40)) (remainder (remainder 206 40) 40))) ; +7
(remainder (remainder 206 40) (remainder (remainder 206 40) 40)) ; +4
(gcd (remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40)) (remainder (remainder 206 40) 40))
(remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40))
(remainder (remainder (remainder 206 40) (remainder (remainder 206 40) 40)) (remainder (remainder 206 40) 40)))))
remainder执行了18次

1.21 - 1.28
由于guile的current-time只能精确到秒,程序无法得到实际运行的时间(只能得到0)。因此,这几个习题可能今后使用clisp或者MIT Scheme来实现。

1.34
(f, f)
-> (f, 2)
-> (2, 2)
出错:Wrong type to apply
把一个常数当作一个函数使用

1.36
启动数值a
a = 2.0时
不用平均阻尼:40步
使用平均阻尼:37步
a = 10.0时
不用平均阻尼:39步
使用平均阻尼:38步
a = 100.0时
不用平均阻尼:42步
使用平均阻尼:42步
a = 500.0时
不用平均阻尼:43步
使用平均阻尼:44步

1.37
a)和b)的解法都是偷懒的,因为D和N都等于1。这种办法到1.38就不行了。

1.41
(double)从表面上看是x2的关系,实际上是^2的关系,这从代码中可以看出来。
(define (double f)
(lambda (x) (f (f x))))
函数f第二次作用的对象是第一次作用的结果。
因此,由于(double double)函数的意义是将参数函数应用四次,那么当(double double)的参数是自身的时候,那就是4x4=16次了。

1.45
题目要求导出计算n次方根时使用平均阻尼的一般规律,可惜我没做出来。
在求五次方根时,两次平均阻尼虽然可以收敛,但是结果不正确。显然,收敛的方向出现了偏差。
求更加高次方根时,两次平均阻尼竟然都可以收敛,但是结果均不正确。
赶时间,没有仔细做下去。以后也许有机会。

2009年7月9日星期四

Chrome OS

Google终于亮出了底牌,遮遮掩掩许久之后才宣布Chrome会变成一个操作系统。半年多前和lvscar等几个同事争论的话题今天变成了事实。Firefox,你能赶上这班车么?

ChromeOS的消息只是宣布而已,并没有多少实质性的内容放出。不过就这个消息已经足以引起大家的联想了。在浏览器取代桌面的这种趋势之下:
1、国内那些各种定制的山寨浏览器之流,你们的生存空间在哪里?傻眼了吧?
2、Linux下的应用会空前繁荣,输入法、显卡驱动、网卡驱动等等这些基础问题看到了解决的曙光。

Chrome之下的新的"windowing system"也是很让人期待的一个东西。X的构架从1987年以来就没有发生大的变化,总该引入点竞争了:)

2009年7月5日星期日

Search Marker plugin for Vimperator

Vimperator是个非常棒的Firefox扩展,但是我一直希望它能在搜索页面的时候能够和Search Marker或者XUL/Migemo一样,在页面右侧显示一个标记栏。很可惜的是这两个扩展和Vimperator都不兼容。既然没有人为Vimperator写这么一个插件,咱们就自己动手吧:)

插件下载地址:http://www.mozdev.org/pipermail/vimperator/attachments/20090705/6df4ab76/attachment.js

这个插件的大部分代码都是直接从Search Marker里拿过来的,经过了简单的修改。XUL/Migemo的效果太炫了,我不想把事情弄的很复杂,因为Vimperator本身就已经很复杂了。Search Marker看起来很不错,但是缺少了一样东西,那就是在高亮了所有搜索结果之后在右侧的标记栏中显示用户当前选中的搜索结果。幸好定位用户选中的文字在Firefox中还是很简单的。

Vimperator修改了Firefox内部的很多东西,例如这里的页面搜索。这使得它的排他性变得非常强烈,幸好Vimperator足够强大。不过话说回来,在自我封闭性上Vimperator还真是和Vim一脉相承。在这一点上我更加喜欢Emacs,虽然我不是很会用。难道是Vim的行为模式注定了它在设计和代码层面上就必须这么封闭么?有时间的话我希望去看看Emacs的结构,看看Lisp是如何将一个编辑器变成一个操作系统的。

2009年5月28日星期四

踢球

来金山半年多了,第一次和公司的同事踢球。

一直看到新闻组的周末踢球召集帖,我都没有参加。主要是因为使用Linux系统,没法用RTX,和组织无法联系。林照师正好也踢球,而且他是用Windows的,竟然成了我加入“寂寞踢波”的介绍人。

下午下雨,不过不大,因此踢球还是继续。场地是那种小学初中的250米操场,和铁四院的一样,感觉很熟悉。人来的陆陆续续,一共大概有二十多个,分成三队轮换上场。我今天竟然进了一个头球,后脑勺蹭的,从门梁下沿擦着进了,应该是很精彩的,可惜大家都鄙夷的看着我,因为是个乌龙球,害得大家通通下场,呵呵。

其他的战绩就是踩伤一个,踢翻一个,扑出了一个点球,以及自己摔了一跤,把衣服都弄湿了,满身都是碎草,干脆脱掉赤膊上阵。没有女生,所以不要紧。

今天比较失策的是没有带钱,还是找同事借钱买的水。租场地的钱是公司出,很棒。大家的水平一般,有高有低。今天下雨地滑,发挥都不怎么好。接下来两天我应该会浑身酸痛,说不定都没法走路。下个星期再踢的时候体力应该会好些。再者很久没踢,带球传球起球都是一塌糊涂,丢脸了。

踢到最后小腿肚子有点抽筋,不过不严重,就是跑的时候自个儿在那儿乱抖,示威似的,让我有力跑不动。也罢,今天的运动量很大了,可乐都喝了两瓶,还中奖得了额外的一听。

2009年5月22日星期五

陈景润诞辰

上学的时候,徐迟的报告文学《哥德巴赫猜想》也曾让我无限崇拜。今天是陈景润的诞辰之日,让我们用这简单的“1+2”深深的纪念他,也纪念自己童年的梦想。

2009年5月10日星期日

Vim与中文分词

Vim的"w"、"e"等一系列快捷键无法支持中文已经在vim_use和vim_dev中讨论过多次了,但是貌似没有开发者愿意给Vim写一个关于此问 题的补丁。我前一段时间研究了一下,发现让Vim在这方面支持中文确实是不现实的。在这里写一下原因,如果有人想继续研究这个问题,至少可以参考一下。

解决这个问题最直观的想法就是让Vim调用外部的中文分词库分词,然后把光标移动到正确的地方。相应的困难有两点:Vim不愿意和外部进程通信;Vim集成中文分词器很困难。

Vim的众多开发原则中,有一点是非常重要的,那就是可移植性。Vim运行在很多平台上,不止是*nix、Mac和Windows,还有VMS甚至 QNX。任何一个Vim的特性补丁能否被检入,不止取决与这个特性的紧急程度,还在很大程度上取决与这个特性的可移植性。Vim不愿和外部进程通信也正是 基于这个考虑,因为不同操作系统的通信机制千奇百怪,要设计和维持一套统一的协议是很困难的。没有和外部进程通信的渠道,让Vim调用外部分词库也就无从 谈起了。

使Vim用外部的分词器是最好的解决方案,因为在统一接口的条件下,这样可以保持很高的灵活性,在多个分词器之间切换。既然这样不行,那分词器可以集成进 Vim么?可惜,答案是很困难(不是不可能)。Vim关心的另一个非常重要的原则就是性能。Vim运行的环境可以是有图形界面的,也可以命令行。特别是在 命令行下,延迟是不可忍受的。幸运的是现在的中文分词库的性能都还不错,一般都有上百kb/s的处理速度,每秒都在十万字以上。另外,既然是集成进 Vim,那在众多中文分词库中应该选用哪一个呢?还有一个不利的因素就是现在中文分词都都需要词库,而这个词库是很大的。这么大的一个词库如果集成进 Vim,那下载起来会很困难。再者词库是需要更新的(程序会自己学习新词么?嗯,也许会吧,十年之后……),这就是大麻烦了,还要和网络通信,大大的不 行。

即便有个牛人把上面的麻烦都解决了,写出来了一个可用的补丁,和Bram他们讨论的差不多了,那也需要在一个branch中测试个一年半载来尽可能的找出所有bug(可以参见vim_dev的patches页面)。那时候,任何一个开发者应该都已经被磨的没脾气了。

看起来万事俱备,实际上满是窟窿 T-T

这个问题最好的参考,也是前车之鉴,就是Vim的拼写检查部分。在Vim中输入:h develop-spell可以查看帮助文件中关于拼写检查当年的讨论结果。最终拼写检查没有使用现成的Aspell,而是Vim自己实现了一份。Vim和Emacs的不同之处很明显,这是Vim的特色,但是在这种情况下这种自给自足的小农思想也成了Vim的一圈桎梏吧。

第一篇

以前也写过一些东西,散落在LiveSpace、忙吧、新浪或者歪酷等一些地方。看过pongba同学的《为什么你应该(从现在开始就)写博客》之后觉得也许自己应该对这件事认真一点了。Anyway,这个blog会专注于我现在手头的工作。我会把自己的心得分享在这里,并且希望通过这里交到志同道合的朋友。