Apocalypsa

Rumination Introspection

  博客园 :: 首页 :: 联系 :: 订阅 订阅 :: 管理
  13 Posts :: 1 Stories :: 116 Comments :: 0 Trackbacks

公告

昵称:_Luc_
园龄:3年5个月
粉丝:28
关注:2

搜索

 
 

常用链接

我的标签

最新评论

阅读排行榜

评论排行榜

推荐排行榜

置顶随笔 #

摘要: 与.Net大师Jeffrey Richter面对面交流——TUP对话大师系列活动回顾(多图配文字)上周末很有幸参加了CSDN举行的TUP活动,不但获得了带有Jeffrey签名的图书一本,而且还得到了和Jeffrey面对面交流的机会。会上也见到了很多牛人,博客园上的金旭亮老师,图灵丛书的主编刘江老师以及微软的测试总管方敏先生,激动之余,做一些回顾。首先是图灵丛书的主编刘江老师致开幕词。然后是北理工的...阅读全文
posted @ 2011-03-21 16:04 _Luc_ 阅读(3568) 评论(55) 编辑

2011年7月10日 #


Lucida——一门函数式程序设计语言的诞生


起因

自己从本科那会就开始想写一个编程语言,不过那会缺乏编译的功底。自从阅读了DSL和Language Implementation Patterns,并写了一个一定规模的Parser之后,对编译有了一个比较全的认识,大概也能想清一个语言的运行机理和实现过程。而且到现在自己用过的语言也不少了,过程的有C,OO的有C++、C#、Java,脚本的有Javascript、Python,函数式的有Scheme、Haskell,看的书更是数都数不清,以至于到后来都觉的没什么意思了。

正好现在很多人都在热衷F#、Scala什么的函数式程序设计语言,说实话,与其去学这些东西,不如自己写一个出来,那这比学这些玩意要深刻的多。倒不是追求reinvent the wheels,我只是觉得现在的wheels未免太多了些,学完这个再学那个,这辈子也学不完。与其被这些东西搞的晕头转向,不如自己弄一个出来,以后再出个什么新东西也不至于会被弄的措手不及。

设计


经过短暂的思考,我决定以Scheme、Haskell和Python为基础,在尽可能短的时间内弄一门函数式编程语言出来,支持函数式编程的所有基本特性。此外,我需要为这个语言提供一个易用的interactive interface,就像GHCi或是IDLE那样。

为了便于构建解析器,我几乎毫不犹豫的选择了Scheme的RPN(逆波兰表达式)语法,尽管这个语法有些诡异:

    (def (f a) (* a a))
    (f 3) => 9

有了Scheme这个参考,构建语言的词法分析器和语法分析器就相当简单了,用了不到两个小时就搞出了Tokenizer和Parser,前端的部分就搞定了。

由于对代码生成不熟悉,况且我要弄的是一门解释型语言,因此有一个中间结构就足够了,这里我用了我比较熟悉的异构抽象语法树:通过一个基类型节点导出各种功能的子类型节点,同时子类型节点可以保存对基类型节点的引用,简单的来说就是Composite Pattern。

 

 

 

常用的语法结构无所谓就是定义,引用,调用函数,分支语句这些,只需按照之前写好的BNF逐一对应上就可以了,算上在草稿纸设计对象模型的时间,这部分大概用了我一个多小时。

接下来就是类型系统了,为了尽快的做出第一个原型,我挑选了四个最基本的类型:数字(Number),列表(List),函数(Function),名字(Name)。

 

数字自然保存一个整数值,直接使用CLR提供的Int64就足够了,这个很简单。

列表的设计花了我点时间,主要是在同构列表(Haskell风格)和异构列表(Python风格)之间抉择,但想到同构列表还要做类型推导这类蛋疼的工作,我选择了更简单的异构列表(而且功能似乎要更强大,只是无法提供静态类型检查)。

由于函数式语言中的函数也可以作为值传递,因此无需为它命名,只需设置它的形参和方法体即可。在这里它的方法体就是一段语法树,执行这个函数就是对语法树里进行实参代换,然后规约出结果即可,很自然的东西。

名称就是对上面这些类型实例的名字,名称保存一个对基类型的引用,因此它可以指向任何类型,这样使得函数赋值变的很简单,比如说:


 

这部分花的时间比较长,大概用了三个小时的样子,因为推翻了几个不靠谱的设计,多花了些时间。

接下来就是最外层的解释器了,在这里用的就是常用解释型语言的模式:Read and Eval:首先用Read读入代码,做一个简单的语法检查。然后再由Eval把这段代码执行(实际上就是转换成语法树并规约出结果),然后由控制台输出结果,这里不需要进行什么设计,倒是控制台的配色花了点时间,我向来很不爽GHCi或是IDLE的配色,因此我绝不会让我自己设计的解释器的配色方案跟它们一样烂:


 

做了几个简单的Test之后,把符号表弄了进去,测试了一下,变量赋值,函数递归的都能用,就是列表的递归有些问题,看了下时间,已经连续搞了快10个小时了,于是休息。第二天上午用了近两个小时才把bug揪出来,原来是函数生成List时共享了一部分内存引用,修改了就好使了。再次体会到除错的恶心。

接下来就是引入基本的操作了,这个很简单:写一个预定义的方法表,然后特定的操作符回调方法表中对应的方法。由于以前就用过这种手法,所以几乎没花什么时间,一个下午的时间,算术操作符,逻辑操作符以及基本的列表操作就搞定了,晚饭回来之后顺便写了三个最常用的高阶函数:map、reduce、filter。只要玩过函数式编程或是用过Python的人都知道这几个函数的作用,非一般的好用。



我一直很喜欢Haskell和Python的List Comprehension,于是就琢磨着把它弄进来,但考虑到RPN语法的限制,于是就参考了Python的range函数,以便快速生成一个list。



再次测试,由于都是很明显的东西,所以也出不了什么错。

事实上到这里Lucida已经成为了一个全功能的语言了:变量赋值,递归,列表,高阶函数,以及常用的内置函数,作为一个语言应该有的几样基本都具备了。

之前一直认为写个语言会是个很麻烦的事情,但写完了发现实际上也不算什么,anyway, who could not design and implement a programming language in two days?

接下来的工作


    引入浮点型,以及浮点型和整形数字的混合操作:

 

    引入匿名函数和Curry函数:

 

    引入字符和字符串类型,并设计它们与列表和数字之间的转换操作:
 

    为操作符添加一些有趣的属性:

    设置操作符映射,从而可以写出极其简短的代码:

最后就是写一个语言的手册了,这个更好写,直接仿照K&R的The C Programming Language写就行了。有兴趣的同学可以到这个链接下载阅读。

后记

 

算上这篇回顾,Lucida从无到原型,再到现在的基本完备,也就是不到一周时间的而已,所以在这里建议那些动不动就要学习新语言的同学停下来,花时间自己写一门语言出来,这比学语言深刻的多,再说写一个小语言也用不了多少时间。

刚刚看到坛子里有一个关于编译的很不错的连载博客,博主好像是叫装配脑袋吧(好奇怪的名字),他把编译那些常用的东西已经讲的很清楚了,认真的看一遍的话写一个语言是不成问题的。只可惜我看到的大多回复都毫无营养,只是毫无意义的顶来顶去。

Anyway, that's all. 有兴趣的话可以交流下编程语言的知识:lunageek@gmail.com



posted @ 2011-07-10 22:48 _Luc_ 阅读(2438) 评论(11) 编辑

2011年4月6日 #

Q:So what is the performance if you have lots of updates, very intensive data mining queries that would run a long time?

A:If you look at the workloads of yahoo i have mentioned, data mining kinds of queries would not be on PNUTS.What you would do is analyze them on Hadoop, data in hadoop can be moved into sherpa or from sherpa into hadoop effieciently, and you would do that and anlyzed data that way. In PNUTS it supports insert delete modify of single records. Time-line consistensy is important. The main factor is avalibility instead of performance.


Q:PNUTS是如何处理那些有很多更新,或者是需要运行很长时间的密集数据挖掘查询的呢?
A:我在刚才已经提到了,类似数据挖掘这样需要长时间运行的查询并不会在PNUTS上进行的。我们会在Hadoop上执行这样的分析操作,如我刚才所展示的,Hadoop里面的数据可以很容易的迁移到Sherpa上,反之亦然,因此你可以利用Hadoop提供的强大的计算能力对数据进行分析。
PNUTS主要提供单条数据上的查询,删除,以及修改操作,当然性能也很重要,但是在实际中,稳定的可用性要比性能更重要。

Q:Why Cata use two clusters, one cluster for hadoop, on cluster for indexing share?
A:I'm not famliar of Cata. So I could not answer this question.
Q:我在工作中用到Cata数据库,我有一个疑问,为什么Cata里面有两个cluster,一个Hadoop的cluster,一个用于索引共享的cluster?
A:很抱歉,我对Cata数据库了解不多,因此无法回答你这个问题。
还有其它的问题吗,希望是我能回答的问题(笑)。

Q:I want to ask a general question. What is the impact of cloud computing on databases?
A:If you look at some of the things we talked about today, hopefully answer to your question. So Cloud computing essentially means you are building a system that you can multitenant. And the tenants maybe users or developers are using the cloud. They can ask for more capacity at any time and they could get it instantly.
And this is your goal: You need to be able to add capacity to your systems and your systems could automatically distribute them all.
If you want to that, that requires many kinds of protocols i have talked about. You need high-availability, and that means many kinds of failures in very large distributed system, how could they be masked. That require some of the protocols i have talked about.
Think about this way: a conventional database system that is very large, it has a dozen notes. To date a failure of a replication. At this scale, it requires a different think.

Q:你好,Ramgu先生,我想问一下,云计算对数据库产生了很大的影响,你是如何看这种影响的呢?
A:我今天演讲的内容已经讲了很多这方面的问题。希望能对这个问题有所帮助。
从本质上来说,云计算意味着你需要搭建一个多用户的系统,这些用户可能是开发者,也可能是使用者,他们都通过云来使用你的系统。他们可以在任何时间要求更多的空间(或是性能),而你的系统需要即时相应他们的需求。
因此,你需要建立这样的系统:它允许你动态的添加各种能力(空间,性能等),而且你的系统可以自动的把你添加的这些能力有效的分配到系统的用户身上。
这样的系统需要很强的可用性,这意味着需要各种机制来处理这种大型分布式系统上面出现的异常。我在演讲中提到的系统(PNUTS)提供了很多这样的机制。


Q:One questions for the database serving in cloud. I wonder that hadoop only store the data. I mean do you store any like stored procedures on database.
A:The bottomline is at today, we don't provide any special support for this. Some developers had tried that. As you know, we could store a code block and we could execute it some way. We may do something to support that in the future.
Other things that people had asked for, is be able to date make sure arounds in the location that as same as it established. That is a feature we don't support yet.
So things like that we know we need to do, but we don't get it.


Q:您好,我想提问一个关于云中数据服务的问题。我发现Hadoop只负责存储数据,我想问您,可以在其中存储一些过程吗(比如说数据库中的存储过程)。
A:我们现在还没有提供专门的机制来支持你所提到的操作。据我所知,一些开发者可能会使用一些小技巧,比如说存储一些代码片段,然后通过某种方法执行它。在以后我们可能会采取一些机制来这些操作。

Q:You mentioned hadoop and PNUTS, do you think traditional database management systems have any chance in cloud computing environment?
A:There are many kinds of application scenarios. The scenario I had talked about today is Yahoo scenario. Take yahoo login system as an example. It is a logging database that supporting 640 million users. Support something like this large-distributed system requires kinds of things i have talked about today.
Let me give you a very very different example of cloud: There is a company called ADP(Associated Data Processing). They provide database services for other companies. For them, what they actually doing is managing and running hundreds thousands of small databases. Any of these databases may run on a single box. They want transactions, ACID, for-SQL, but they don't need anything more than simple mysql style asynchrounous application.  The design of cloud system for that would be completely different.
It would still contains some features i have talked about. In that availibility is important, in that multitenant is important, in that elasticity is important. But they need a very different design. They will try to run lots of copies of small traditional relational databases on a farm of thousand servers, and be able to move this to one farm to another but the unit is one entire database.
Many of commercial vendors are thinking about how to cloudify their stuff. If you take Microsoft Azure, it is a way of adapting SQL server to support simple database deployments on the cloud.
So the short answer is yes, it is a long story.

Q:您提到了很多关于PNUTS和Hadoop的内容,我想问一下,您认为传统的DBMS(数据库管理系统)在云计算环境中还有立足之地吗?
A:现实中存在很多的应用场景。今天我所演讲的是Yahoo的应用场景,就拿Yahoo的登录系统来举例。它有多达六亿四千万的用户,需要对这些用户的请求进行即时的处理。为了支持这个数量级的操作,需要使用我今天提到的一些技术(指PNUTS和Hadoop)。
我在这里举另外一个很不同的云应用场景为例:有一家公司叫ADP(Associated Data Processing),它们为其它一些小公司提供数据服务。ADP所做的工作就是管理长千上万个小规模数据库并维护使其正常运转,这些数据库可能在同一台机器上,也可能不是。这些数据库的用户的需求很简单:事务,ACID,SQL操作,最多也就是用MYSQL风格的异步数据访问程序。设计这样的云系统于Yahoo的登录系统截然不同。
这样的系统需要在大型服务器场中运行成千上万个传统的关系型数据库,而且需要支持在服务器间以数据库为基本单位进行数据迁移。为了支持这些操作,可用性、多用户操作、灵活性都是必须的特性。
此外,一些大型软件提供商也在试图把他们的产品"云化"(Cloudify),就拿微软的Azure为例,实际上它就是一个支持在云中进行简单数据库部署的SQL Server。
你问我传统的DBMS在云计算环境中是否还有立足之地,我的回答是肯定的。

Q:I have two questions.
First is I want to know does PNUTS support multi-key query?
And the second is, have you heard of Greenplum, how do you say about the difference between Hadoop and Greenplum?
A:When you say multi-key query, it means I give you several keys and aske you to give me all the matched records?
Q:Yes.
A:So that is the answer of the first question.(Laugh).
The second question, greenplum fundamentally is a OLAP system——Traditional relational OLAP system, but likely it has also started supporting implementations of mapreduce. Because it's popular and it got customers asking for it. implementation of mapreduce with some OLAP capabilities.
Easy way to summarize. Hadoop is a particular implementation of mapreduce. Greenplum is another implementation of mapreduce, and it also have traditional OLAP capabilities.

Q:我有两个问题。
第一个问题是,PNUTS支持多键查询吗?
第二个问题是,您听说过Greenplum吗,您是如何看待Hadoop和Greenplum这两者之间的关系的?
A:你提到的多键查询,是指输入一些键值,然后返回所匹配的记录吗?
Q:是这样的。
A:那这个问题已经解决了(笑)。
对第二个问题,从本质上来说,Greenplum是一个OLAP(Online Analytical Processing)系统——一个传统的关系OLAP系统,当然它也开始支持实现MapReduce。一方面是因为MapReduce越来越流行,另外一方面来自客户的要求。总的来说,Greenplum以一个带有OLAP能力的MapReduce实现。
简单的来说:Hadoop是一个专门的MapReduce实现,而Greenplum是一个实现了MapReduce,并带有一些传统OLAP能力的系统。

Q:Imagine you want some information about Madonna. You mentioned that the user type Madonna in the search box. Send more requests before the final results presents to the user? Different component
A:The question I understood it is someone say: tell me all about Madonna. What are the different steps that the request flow through before the user see the results?
Q:Yes.
A:Ok, the story has to begin before the user issued the question. If I really want to give you everything to know about Madonna, I have to anticipate that you are interested in celebrities. And therefore, I have to be able to get all the relevant information about celebrities from different feeds, from people who maintain feeds on video or movies, from calling the web to do the information extraction. I have to get the data, integrate them, and create the relevant tables in the web of concepts. All of this happens before you type the query.
Once you type the query, it go through the steps: that analyzing your query, enlight similar query that other users use, to make sure we understand what you are really looking for, are you looking for Madonna the actress, Madonna the musician, Madonna the mother of Christ. Ensure your major intent.Then invokes a call to the results of your previous aggregation. This maybe a system like .Which has necessary data.
By the way, in order to interpret your query Madonna. I probably aslo has the profile data about you. And every time you do something, I am updating your profile. That profile data are very likely stored in PNUTS or other system. And we look it in hadoop to interpret what you really want.
And the first pass of gathering your profile data, and create semantic aggregation for Madonna, all of that used Hadoop.

Q:您在演讲中提到了下一代的搜索(Next-Gen search),在这里我想问一下,假设您打算搜索与Madonna的信息,您在搜索框中输入Madonna,点击搜索,然后Yahoo返回搜索结果(这些结果很可能是因人而异的),在这个过程中都涉及到了那些操作呢?
A:这可要从用户输入搜索关键字之前开始说起了。
如果你需要我提供给你所有关于Madonna的信息,我就会假设你对名人感兴趣。因此,我会从各种各样的资源(视频网站,电影网站等)中,抽取和名人相关的信息。在得到这些信息后,我会把它们进行集成,然后创建对应的概念网络。这一切都是在你键入关键字搜索之前发生的。
当你输入关键字,点击搜索按钮之后,将会经历如下的步骤:分析你的查询,将你的查询与其他用户所进行的类似查询进行比较,从而来确定你真正想搜索的目标到底是演员Madonna、歌手Madonna还是圣母Madonna。在确定了你的搜索意图之后,我们会利用之前收集的信息,给出一个你所需要的答案。
顺便提一下,为了正确的解析你的查询。我们会对你的个人账户建立档案信息。我们会根据你所做的行为来更新你的档案信息。这些档案信息一般存储在PNUTS之中,然后通过Hadoop对其进行分析。

Q:OK, I want to ask the last question: Because you are very successful in research and academic field. And now moved into industrial field. I think today there are a lot of students from the IT majors at the conference. I want to ask: what is the most important capacities to prepare for their future careers. To be successful in research or in industrial field.
A:I think the most important capacity is to find good people to work with.
And, when I was a student I was fortunate to find a good teacher, and as a teacher I was fortunate to find a good student. And in industrial I was fortunate to find good colleagues.
I have to mention that Yahoo has opened the Beijing lab recently. They are doing some work like web of concepts I have mentioned. You could join us and work with us.
And the short answer is you learn from good people, you work with good people, and you will be successful.

Q:Ramgu先生您好,您无论是在学术界还是工业界都取得了巨大的成功,所以我在这里代表今天与会的听众向您提问最后一个问题:在学术界或是工业界中取得成功所需的最重要的能力是什么呢?
A:我认为最重要的能力是,找到优秀的人,与其同事,向其学习,取其之长,补己之短。
当我是一个学生的时候,我很幸运的找到了一个优秀的老师;当我成为教室后,我很幸运可以找到优秀的学生;而我进入工业界后,我很幸运的与一群最优秀的人员所共事。对了,Yahoo刚刚在北京创建了实验室,这个实验室正在做一些诸如概念网络方面的研究,如果你对这方面感兴趣,请加入我们。
总而言之,三人行,必有我师焉,择其善者而从之,其不善者而改之。(You learn from good people, you work with good people, and you will be successful)







posted @ 2011-04-06 21:50 _Luc_ 阅读(112) 评论(0) 编辑

2011年3月21日 #

与.Net大师Jeffrey Richter面对面交流——TUP对话大师系列活动回顾(多图配文字)

上周末很有幸参加了CSDN举行的TUP活动,不但获得了带有Jeffrey签名的图书一本,而且还得到了和Jeffrey面对面交流的机会。会上也见到了很多牛人,博客园上的金旭亮老师,图灵丛书的主编刘江老师以及微软的测试总管方敏先生,激动之余,做一些回顾。



首先是图灵丛书的主编刘江老师致开幕词。



然后是北理工的金旭亮老师讲解.Net 4.0的新特性。



金老师首先讲解了.Net 1.0到.Net 4.0的发展历程,然后系统的介绍了.Net 4.0新增的特性(并行处理,MEF等),讲解的很好,举的例子也不错,不过不知道为什么金老师没提到DynamicObject,可能是自己当时没仔细听吧。



金老师准备的很充分,讲解的也很全面,只可惜时间不足,后面的一些关于MEF的内容没有讲完。
感觉金老师很像我本科时的一个老师,对学生很负责任,对技术也有很执着的追求,看似古板,实际上却很幽默。在讲解C#语言的同时也融入了很多软件工程的重要思想。在如今大学校园的计算机系,能有这样的老师,实在很难得。很想阅读下金老师的那本关于.Net4.0的书,以后应该和金老师多交流下,以增长自己的见识。



接下来就是Windows核心编程以及CLR via C#系列畅销图书的作者,Windows编程、.Net编程的大牛Jeffrey Richter的登场了。



感觉这些老外还是很随意的,牛仔裤t恤衫,非常休闲的就上场了。



作为技术作家,Jeffrey首先提到他最近并没有在写新书,也暂时没有写新书的计划,that feeling is great~
对于CLR via C#是否还会出新版,Jeffrey表示之后随着.Net的版本更新,会做一些修订。而对Windows核心编程那本书,Jeffrey表示very painful,如果可以的话他是再也不想碰了,看来windows programming还真是fucking horrible。



Jeffrey表示他并没有做特别的准备,因此可以直接进入问答环节。



在这个环节,由于之前请的那位做翻译的嘉宾没来,我非常幸运的出场了(作为Jeffrey的临时Interpreter,没错,就是左边那个黑衣男),多谢刘江老师给我这个机会。



第一个问题把整个会场的人都整囧了,一个哥们居然拿出自己一个编程的问题(Winform程序,连接数据库时窗体突然消失了,请问Jeffrey老师这是为神马?),搞的我也不知道该不该翻译,Jeffrey对这样的问题也表示很无奈。


 

不过接下来一个哥们问的就很有水平了,先问了C#语言的发展趋势,然后问了下Jeffrey对"is c# better than java?"这个问题的看法,最后问了下windows操作系统未来的发展方向。

对于第一个问题,Jeffrey回顾了下C#的发展历程:C#1.0是一门Java-like、C++-like的语言;C#2.0引入了泛型的机制,使得C#更加完善;C#3.0、3.5增加了很多函数式编程的语法特性(高阶函数、lambda表达式),当然还有LINQ;随着动态语言(Ruby、Python)和多核CPU的飞速发展,C#4.0引入了动态语言的一些特性(诸如Dynamic Type),并引入了更完善的并行库。至于C#接下来的发展方向,Jeffrey认为C#会增加更多的特性以支持异步编程(Asynchronous Programming)。

对于第二个问题,Jeffrey认为这个问题不好回答。首先他承认他用java主要是在他的android phone上写一些小apps,对于java语言和C#语言而言(注意是语言,不是平台),Jeffrey认为C#更强大,更易用,他也提到这主要是由于C#晚于Java发布,因此对Java语言进行了扬长避短。同时Jeffrey认为在library这方面,.Net库比Java库设计的更合理,更人性化。而在IDE方面,Jeffrey认为Visual Studio相对于Java的IDE(Eclipse)要更强大易用。

对于第三个问题,Jeffrey提到了一些关于Windows 8的内容。微软对苹果一直很不爽,尤其是ipad这样的移动设备。他表示windows 8会更注重移动设备,优化程序性能,增加续航时间,并提供对ARM芯片的支持,以在平板设备(slate device)上同iOS相抗衡。



 

接下来有人问Jeffrey对mono的看法。Jeffrey个人认为mono是一个很好的项目,他也很希望C#这样优秀的语言可以跨平台,然而他也提到微软可不一定这么想(需要注意Jeffrey本人不是微软公司的)。



一位同学提到将来会不会有可以直接运行IL的CPU(这哥们看书看的还真细)。Jeffrey没直接回答这个问题,他只是提到微软现在开发了一些称为".Net Gadget"的小玩意,可以编写C#代码再烧制到这些设备上,通过这种方式,你甚至可以自己编写DC上的程序。


接下来进入了圆桌环节,到场的两位嘉宾(金旭亮老师和微软的测试总监方敏先生)上台,同Jeffrey一起解答之前网友的热门问题(我本来是站在一边的,后来Jeffrey让我坐他旁边了,不胜荣幸,图上的我正在疯狂的记录中,以便接下来口译)。刘江老师从中精选了一些代表性的问题。

问题1:如何在技术的浪潮中选择自己的方向?

Jeffrey认为首先要找到并跟随自己的兴趣,follow your heart。Jeffrey认为学好计算机专业基础(Computer Core Concepts)是相当重要,诸如操作系统的概念,数据结构这些课程都应该扎实掌握。在学好基础的前提下,专精一个方向,比如说图形处理或是人工智能,这样就会使自己在技术的浪潮中利于不败之地。

方敏先生则根据他的招聘经验对学生提出了一些建议。他尤其提到当前的大学学生编码能力不足,微软面试的写程序环节,很多学生写不出来,一些人写了但不是最佳的方案。为此他建议学生在校期间多编程序。

金旭亮老师也认为兴趣很重要,多编写代码,多实践,实践中补充理论,学理论时进行实践,如此互动。金老师以Brooks的新书Design of design为例,虽然是好书,但是没有点经验,是根本看不动的。

问题2:如何尽快的掌握一门新技术?

Jeffrey谈到了他是如何学习Windows Azure的:下载SDK、观看教程、阅读文档、编写sample code、与一线人员交流。他还提到,自己过去的经验对于学习掌握新技术有很大的帮助(深表赞同,自从阅读了esr的那篇文章,学习了五类编程语言之后,感触颇多),此外,要focus,要专注,要持之以恒。

刘江老师提到Jeffrey有Python的经验,Jeffrey也就发表了下对动态语言的看法。他表示自己是a fan of strongly type language,因此用C++/C#很多,他承认Python这类语言在编写一些脚本时很方便,不过他认为动态语言不适合大型的工程项目,动态语言是很灵活,但也许太灵活了。编写一些gluing code(胶水代码)动态语言很适合,但是对大型的工程而言,强类型语言更合适。


金旭亮老师在这里提到基础的重要性,以WCF为例,他提到一个学生不明白Http不知道Socket,就要学习WCF,结果自然不会好。他同时提到要多看一些技术人员的blog(good advice),尤其是那些项目主力开发人员的blog。

方敏先生进行了补充,他认为技术固然是重要的,但一定要与应用相结合,以他所负责的health care sytem为例,他们的项目组就经常到医院进行实地考察,以编写实用的医疗管理软件。

问题3:对云计算的看法以及云计算对软件界带来的影响?

对火热的云计算,Jeffrey似乎并不感冒,他用infancy这个词来描述cloud computing,认为云计算还有很长的路要走。
对于云计算的影响,Jeffrey认为如何把现有的软件迁移到云中是一个巨大的挑战(migrating is a great chanllenge),同时,如何编写可扩展性强的软件以适应云环境,也是一个难题。

方敏先生则提到,对于云计算,有三点要求:Performance(性能),Security(安全性),Extensibility(可扩展性),这三点也是Windows Azure追求的目标。

问题4:在微软的众多表现层技术中,你最推荐的是哪一种技术?

这个问题的回答比较令人意外(也许是情理之中的回答),Jeffrey首推HTML5,不但是因为微软已经把发展HTML5作为主战略,而且是因为HTML5有效的支持Cross-Platform,为了在移动终端的市场上取得更大的突破,微软需要一种跨平台的强有力的技术,这就是HTML5。Jeffrey顺便提到,如果不考虑跨平台,而且想制作绚丽的界面的话,也可以选择silverlight。



接下来就是签售活动了,各位读者纷纷向Jeffrey索要签名,或是合影。



会后不但和Jeffrey合影,而且并获得了Jeffrey的签名图书,十分兴奋,毕竟是头一次和这样的编程大神级人物走的这么近,而且还可以他面对面交流,that is really fantastic!!!


 


此外,多谢这位帅哥,帮我拍了那么多照片,呵呵。

posted @ 2011-03-21 16:04 _Luc_ 阅读(3568) 评论(55) 编辑

2010年12月10日 #

一个Quicksort究竟可以写到多么短

说实话,我从来没有能一次写对一个快速排序,总是有各种各样的错误。
快排麻烦就麻烦在,没办法去调试它,因为它是生成递归的,只能去静态调试,或者是不断的打印数组的状态以推测错误的可能性。 然而快排的基本思想却是极其简单的:接收一个数组,挑一个数,然后把比它小的那一摊数放在它的左边,把比它大的那一摊数放在它的右边,然后再对这个数左右两摊数递归的执行快排过程,直到子数组只剩一个数为止。

下面我先用最常用的C语言来写一个快速排序:

首先可以先写出一些伪代码:

void quicksort(int array[], int left, int right)
{
    //Do nothing if left <= right
    //p <- Get a number from array
    //Put elements <= p to the left side
    //Put elements >= p to the right side
    //Put p in the middle slot which index is pivot
    //Recursive quicksort the left parts and right parts
}
然后慢慢的把这些伪代码转化成C code:

Step 1:

void quicksort(int array[], int left, int right)
{
    if(left<right)
    {
        //p <- Get a number from array
        //Put elements <= p to the left side
        //Put elements >= p to the right side
        //Put p in the middle slot which index is pivot
        //Recursive quicksort the left parts and right parts
    }
}

Step 2:获得数组中的某个元素,在这里总是获取最左边的元素

void quicksort(int array[], int left, int right)
{
    if(left<right)
    {
        int p=array[left];
        //Put elements <= p to the left side
        //Put elements >= p to the right side
        //Put p in the middle slot which index is pivot
        //Recursive quicksort the left parts and right parts
    }
}

Step 3:这是比较麻烦的一步,可以遍历数组,如果碰到比标记值小的数,就把它与前面的数字交换,这样遍历一遍之后,小的数字就被移到了前面

void quicksort(int array[], int left, int right)
{
    //Do nothing if left <= right
    if(left<right)
    {
        //p <- Get a number from array
        int p=array[left];
        //Put elements < p to the left side
        //Put elements >= p to the right side
        int i=left,j;
        for(j=left+1;j<=right;j++)
        {
            if(array[j]<p)
            {
                i++;
                swap(array,i,j);
            }
        }
        //Put p in the middle slot which index is pivot
        swap(array,i,left);
        //Recursive quicksort the left parts and right parts
    }
}

Step 4:容易发现之前所做的步骤是一个划分数组的过程,为了便于理解,可以把它提升成一个函数。与此同时递归的快排左右两部分就可以了。

void partition(int array[],int left, int right)
{
    //p <- Get a number from array
    int p=array[left];
    //Put elements < p to the left side
    //Put elements >= p to the right side
    int i=left,j;
    for(j=left+1;j<=right;j++)
    {
        if(array[j]<p)
        {
            i++;
            swap(array,i,j);
        }
    }
    //Put p in the middle slot which index is pivot
    swap(array,i,left);
    return i;
}
void quicksort(int array[], int left, int right)
{
    //Do nothing if left <= right
    if(left<right)
    {
        int pivot=partition(array,left,right);
        //Recursive quicksort the left parts and right parts
        quicksort(array,left,pivot-1);
        quicksort(array,pivot+1,right);
    }
}

Step 5: 最后套一个wrapper,就完成了一个基本的qsort

void q_sort(int array[], int size)
{
    quicksort(array, 0, size-1);
}
当然上面的步骤省去了很多的测试及调试过程,之前我也提到过了,我做不到一下子写一个正确的快排,但一步一步来的话,还是可以写出来的。 接下来回到标题:quicksort能够写多短? 拿之前的C程序来说,把它弄短的途径无非是展开函数,去除大括号等山寨方法。

Step 1:展开partition函数并去除注释

void quicksort(int array[], int left, int right)
{
    if(left<right)
    {
        int p=array[left];
        int pivot=left,j;
        for(j=left+1;j<=right;j++)
        {
            if(array[j]<p)
            {
                pivot++;
                swap(array,pivot,j);
            }
        }
        swap(array,pivot,left);
        quicksort(array,left,pivot-1);
        quicksort(array,pivot+1,right);
    }
}

Step 2:去除临时变量,大括号并把自增自减操作放在一个语句里

void quicksort(int array[], int left, int right)
{
    if(left<right){
        int pivot=left,j;
        for(j=left+1;j<=right;j++)
            if(array[j]<array[left])
                swap(array,++pivot,j);
        swap(array,pivot,left);
        quicksort(array,left,pivot-1);
        quicksort(array,pivot+1,right);
    }
}

Step 3:利用C的指针算术,去掉多余的参数

void quicksort(int *array, int n)
{
    if(n>1){
        int pivot=0,j;
        for(j=1;j<n;j++)
            if(array[j]<array[0])
                swap(array,++pivot,j);
        swap(array,0,pivot);
        quicksort(array,pivot);
        quicksort(array+pivot+1,n-pivot-1);
    }
}
这样的话可以把原本20多行的快排缩减到10行,但是这样有什么意义呢,程序的可读性大为下降,而且运行效率也没有丝毫的提升。此外,指针算术很可能会导致各种越界错误。 况且C语言的话,再短也短不了多少了,接下来可以看看快排在其它的语言中的实现,鉴于Java,C#之类的语言实际上和C是一个系列的(都是基于Von-Neuman体系的Imperative Language)。我来展示如何用Declarative Language中来编写quicksort,在这里我使用Scheme(函数式语言)和Python(脚本语言)来演示。

接下来展示如何用Scheme编写的quicksort

Scheme是MIT的Guy Steele和Jay Sussman开发的一种函数式编程语言,大名鼎鼎的Sicp(Structure and Intepretation of Computer Programs)和Htdp(How to design programs)采用的就是Scheme语言,它的语法非常简单,但功能很强大,mit的6.001课程就选用scheme作为计算机专业学生的入门语言。
;; define x as value 1
(define x 1)
;; define l as a list of 1 2 3
(define x (list 1 2 3))
;; define f as a square function
(define (f x) (* x x))
;; define a function use lambda
(define f (lambda (x) (* x x)))
;; use a high-order function filter, will return (list 1 2 3)
(filter (lambda (x) (<= x 3)) (list 1 2 3 4 5))
关于Scheme语言的更多内容可以参考The little schemer或是网上的specification,在这里就不赘述了。

让我们回到快速排序这个话题,按照之前的思路,首先写一下伪码:

;; Sort a number list via quicksort algorithm
;; list of numbers -> list of numbers
(define (q-sort l)
  ;;get a number p from l
  ;;get numbers<=p from l-{p} as small part
  ;;get number>p from l-{p} as bigger part
  ;;recursively quicksort on small part and bigger part
  ;;combine small part, p, bigger part together as the sorted list
  )

Step 1: 首先获得序列中的某个数,在这里取第一个数

;; Sort a number list via quicksort algorithm
;; list of numbers -> list of numbers
(define (q-sort l)
  ;;get a number p from l
  (let ((p (first l)))
      ;;get numbers<=p from l-{p} as small part
      ;;get number>p from l-{p} as bigger part
      ;;recursively quicksort on small part and bigger part
      ;;combine small part, p, bigger part together as the sorted list
     )
  )

Step 2: 从序列中提取出比标记值小的一摊数和大的一摊数,然后对其进行递归的快排调用

;; Sort a number list via quicksort algorithm
;; list of numbers -> list of numbers
(define (q-sort l)
  (cond
    [(empty? l) empty]
    [(empty? (rest l)) (list (first l))]
    [else
    ;;get a number p from l
    ;;get numbers<=p from l-{p} as small part
    ;;get number>p from l-{p} as bigger part
     (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l)))
           (big-part (filter (lambda (x) (> x (first l))) (rest l))))
        ;;recursively quicksort on small part and bigger part
       )]
    )
  )

Step 3:为这个递归函数加上终止条件

;; Sort a number list via quicksort algorithm
;; list of numbers -> list of numbers
(define (q-sort l)
  (cond
    [(empty? l) empty]
    [(empty? (rest l)) (list (first l))]
    [else
    ;;get a number p from l
    ;;get numbers<=p from l-{p} as small part
    ;;get number>p from l-{p} as bigger part
     (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l)))
           (big-part (filter (lambda (x) (> x (first l))) (rest l))))
        ;;recursively quicksort on small part and bigger part
       (append (q-sort small-part)
               (list (first l))
               (q-sort big-part)))]
    )
  )
可以发现scheme程序的一大特点就是声明性,你只需告诉它what to do,而C程序的话则强调How to do,实际上,上面的程序根本不需要注释,它自己的代码已经足够说明自己的用途了。

最终的Scheme程序:

;; Sort a number list via quicksort algorithm
;; list of numbers -> list of numbers
(define (q-sort l)
  (cond
    [(empty? l) empty]
    [(empty? (rest l)) (list (first l))]
    [else
     (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l)))
           (big-part (filter (lambda (x) (> x (first l))) (rest l))))
       (append (q-sort small-part)(list (first l))(q-sort big-part)))]
    )
  )

最后我们看看如何用Python来构建一个快速排序:

需要注意的是,Python和前面的C、Scheme语言不一样,它是一个multi-paradigm programming language,换句话说,用python既可以procedural programming ,也可以oop甚至是fp。 首先用C的思想来写一个python版的快排:

Step 1: 列出伪代码,要实现的步骤

def q_sort(l):
    #get first number p from l
    #move elements<p to the left side
    #move elements>=p to the right side
    #recursively quicksort left and right part
    #combine them together

Step 2: 进一步细化,这里利用了Python支持在函数内部定义函数的性质

def q_sort(l):
    def quicksort(l,left,right):
        #get first number p from left end
        #move elements<p to the left side
        #move elements>=p to the right side
        #recursively quicksort left and right part
        #combine them together
    quicksort(l,0,len(l)-1)

Step 3: 再一步细化移动list元素的过程,就完成了这个函数,Python支持多重赋值,所以交换元素非常方便

def q_sort(l):
    def quicksort(l,left,right):
        if right>left:
            #get first number p from left end
            pivot,j,tmp=left,left+1,l[left]
            #move elements<p to the left side
            #move elements>=p to the right side
            while j<=right:
                if l[j]<tmp:
                    pivot=pivot+1
                    l[pivot],l[j]=l[j],l[pivot]
                j=j+1
            l[left],l[pivot]=l[pivot],l[left]
            #recursively quicksort left and right part
            quicksort(l,left,pivot-1)
            quicksort(l,pivot+1,right)
    quicksort(l,0,len(l)-1)
python有一个很好用的特性就是list comprehension,利用这个特性可以写出声明性很强的代码,比如说:
# Get all even numbers between 1 and 100
[x for x in range(1,101) if x%2==1]
# Get all line which start with 'From'
[line for line in lines if line.startwith('From')]

我们可以直接使用这个特性来构建一个更易懂的quicksort,回到之前的第一步:

def q_sort(l):
    #get first number p from l
    #move elements<p to the left side
    #move elements>=p to the right side
    #recursively quicksort left and right part
    #combine them together

Step 2: 利用python的list slice和list comprehension,很容易得到比标记值大和小的两部分,然后把它们拼接起来即可

def q_sort(l):
    #get first number p from l
    p=l[0]
    #move elements<p in l-{p} to the left side
    small_part=[x for x in l[1:] if x<p]
    #move elements>=p in l-{p} to the right side
    big_part=[x for x in l[1:] if x>=p]
    #recursively quicksort left and right part and combine them together
    return q_sort(small_part)+[p]+q_sort(big_part)
执行程序,oops,进入死循环了。

Step 3: 增加终止条件

def q_sort(l):
    if len(l)<=1:
        return l
    else:
        #get first number p from l
        p=l[0]
        #move elements<p in l-{p} to the left side
        small_part=[x for x in l[1:] if x<p]
        #move elements>=p in l-{p} to the right side
        big_part=[x for x in l[1:] if x>=p]
        #recursively quicksort left and right part and combine them together
        return q_sort(small_part)+[p]+q_sort(big_part)
这次运行结果正常了,需要注意的是,这个q_sort并不是进行原地排序,而是不断的生成新的list,当list的元素很多时会对性能造成很大的负面影响,这里只是为了演示python的特性才这么写。 如何把它变得更短呢?首先去掉注释试试

Step 4: 去除注释

def q_sort(l):
    if len(l)<=1:
        return l
    else:
        p=l[0]
        small_part=[x for x in l[1:] if x<p]
        big_part=[x for x in l[1:] if x>=p]
        return q_sort(small_part)+[p]+q_sort(big_part)
去除注释之后的q_sort函数只剩下8行了,还可以更短些吗?答案是肯定的。 注意到q_sort里面用了不少的临时变量,可以把它们去除掉

Step 5: 去除临时变量

def q_sort(l):
    if len(l)<=1:
        return l
    else:
        return q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])
只剩下5行了!

Step 6: 利用Python的3元表达式 if else 来改写上面的函数,注意此处的逻辑是相同的

def q_sort(l):
    return l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])
现在只剩下2行了,需要注意的是python也提供了lambda表达式,因此q_sort可以被进一步简化:

Final Step: 利用lambda表达式,再次简化

q_sort= lambda l: l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])
一个Quicksort究竟可以写到多么短呢?上面的代码给出了答案,1行就足够了。

总结:

我们可以看到不同的语言在处理同样的问题时所采用的方案也是截然不同的,C语言强调的是how to do,而Scheme、Python则强调的是what to do。使用C的话要注重细节,因为每一个小失误都可能会使程序down掉,这是imperative language的通性,而诸如scheme、python这样的语言,所注重的则不是实现的细节,而是解决问题的逻辑,我们只需要告诉电脑what to do,而compiler将会为你生成对应的代码,这样我们就不需要把大把的精力花费在调试程序的细节方面了。

一些人认为程序语言只是表述思想的一种方式,换句话说,他们认为语言无关紧要,重要的是所谓的思想。甚至有一些人认为掌握一门主流语言(诸如C/C++/Java/C#等)就足够了,因为语言都是相通的。 的确,C语言的熟手学Java语法,可能也就是两三天的功夫,而C++的熟手学C#的使用的话顶多就是一上午不到。但需要注意的是,知道一门语言的语法和熟练使用一门语言是很不一样的,每一种语言都有它独到的一面,而这些成分需要使用此语言相当长的时间才能够体会到。

Steve McConnel曾说过,要Program into a language而不是Program on a language[4],打个比方,假设我熟悉C,然后花了两天学会了Java的语法,但这不代表我就能写出像样的Java程序了,因为我不知道GC,不知道Spring, Hibernate等一系列框架,也不熟悉OOP。 换个说法,我们从初中开始学英语学到现在也有十年多了,但我们说的English还是被外国人称为Chinglish,why?我认为这不是我们发音不够好或是不够流利,而是我们一直都是thinking in the Chinese way,在这种情况下是不可能说出纯正的English来的。

所以我认为诸如语言无关论的观点都是错误的,一方面持有这些观点的人的眼光一般很狭窄,只是限定在命令式程序设计语言语言的圈子里(因为根本不知道其它范式的程序设计语言的存在,所以他们自然认为所有语言都是相通的了);另外一方面,这些观点的持有者认为语言并不会影响思想,但实际上并不是如此,一个Lisp程序员和一个C程序员去处理同一个问题的思路肯定相差甚远,抛开程序设计语言,即使是不同的语言,比如说汉语和英语,也会影响人的思路(著名的Sapir-Whorf假设),这也就是我们常说的东方人思维和西方人思维的产生原因之一[5]。

Eric Raymond在他的How to become a hacker一文中就曾提到过作为一个Hacker应该掌握Python, C/C++, Java, Perl这5种语言[6],因为它们各自代表了一种截然不同的编程方式:
It's best, actually, to learn all five of Python, C/C++, Java, Perl, and LISP. Besides being the most important hacking languages, they represent very different approaches to programming, and each will educate you in valuable ways.
Peter Norvig则更加直接[7]:
Learn at least a half dozen programming languages. Include one language that supports class abstractions (like Java or C++), one that supports functional abstraction (like Lisp or ML), one that supports syntactic abstraction (like Lisp), one that supports declarative specifications (like Prolog or C++ templates), one that supports coroutines (like Icon or Scheme), and one that supports parallelism (like Sisal)
所以他说才会说Teach Yourself Programming in Ten Years

References

[1] The practice of Programming. Brian W Kernighan and Rob Pike. Addison Wisley [2] How to design programs. MIT Press. [3] Learning Python 3rd edition. O'Reilly Press [4] Code complete 2nd edition. Microsoft Press [5] Linguistic Relativity. Wikipedia [6] How to become a hacker. Eric S Raymond [7] Learning programming in Ten Years. Peter Norvig

posted @ 2010-12-10 23:07 _Luc_ 阅读(6079) 评论(15) 编辑

2010年11月25日 #

此篇文章接上篇 一个编程小题目引发的思考(上)

其实很多园友已经给出答案了,不过我在这里还是要写一下自己的思路

再把题目叙述一遍

 

输入:一个小于12位的十进制正整数

 

输出:打印此数字的十进制计算器表示 例: 输入:145 输出:
         __
   ||__||__
   |   | __|
于是我又重新思考了一下这道题目,并Review了一下当前的解决方案,发现这个冗长的switch是个很大的问题,这是我想到了代码大全2里提到的表驱动编程方法(就是用一个表来代替冗长的分支控制逻辑)。 小心起见,我从一个方法开始,对其进行了重新组织:
    private void PrintTopBody(int value)
    {
        if (value != 0)
        {
            string[] table = { S1, S0, S1, S1, S0, S1, S1, S1, S1, S1 };
            PrintTopBody(value / 10);
            Console.Write(table[value % 10]);
        }
    }
测试之后发现运行结果正常,我考虑可以对另外两个方法进行这样的重构,但我发现这样写出的代码依然不好维护,虽然短了很多,但是S1,S0等莫名奇妙的全局变量仍然令人很头疼。 所以接下来就是怎么消除这些恼人的全局字符串常量了。 一时间没想到什么方法,于是又重新运行了一下这个程序,得到了下面的结果:
     __  __      __  __  __  __  __  __
   | __| __||__||__ |__    ||__||__||  |
   ||__  __|   | __||__|   ||__| __||__|
这时我突然发现,这个结果不就是一个字符串表吗?为什么不直接利用这个表呢? 于是我打开Regex,利用正则替换,将上面的字符重组为一个二维字符串表格:
    private static readonly string[,] TABLE = 
    {
        {"    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ "," __ "},
        {"   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|","|  |"},
        {"   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|","|__|"},
    };
相应的,我新创造了一个LCDPrinter1类,并按照之前的逻辑,编写了对应的方法,代码如下:
    class LCDPrinter1
    {
        private static readonly string[,] TABLE = 
        {
            {"    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ "," __ "},
            {"   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|","|  |"},
            {"   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|","|__|"},
        };
        public void PrintNum(int value)
        {
            for (int i = 0; i < 3; i++)
            {
                PrintOneLayer(value, i);
                Console.WriteLine();
            }
        }
        private void PrintOneLayer(int value, int layer)
        {
            if (value != 0)
            {
                PrintOneLayer(value / 10, layer);
                Console.Write(TABLE[layer, value % 10]);
            }
        }
    }
然后进行测试,我输入123,但惊奇的发现结果是:
 __  __
 __| __||__|
|__  __|   |
非常像一个off-by-one错误,我看了下字符串表格的定义,原来是0的位置错了,修改之后重新运行,结果正常。 下面是最终的代码:
    class LCDPrinter1
    {
        private static readonly string[,] TABLE = 
        {
            {" __ ","    "," __ "," __ ","    "," __ "," __ "," __ "," __ "," __ ",},
            {"|  |","   |"," __|"," __|","|__|","|__ ","|__ ","   |","|__|","|__|",},
            {"|__|","   |","|__ "," __|","   |"," __|","|__|","   |","|__|"," __|",},
        };
        public void PrintNum(int value)
        {
            for (int i = 0; i < 3; i++)
            {
                PrintOneLayer(value, i);
                Console.WriteLine();
            }
        }
        private void PrintOneLayer(int value, int layer)
        {
            if (value != 0)
            {
                PrintOneLayer(value / 10, layer);
                Console.Write(TABLE[layer, value % 10]);
            }
        }
    }
可以发现这个解决方案不但短小(只有25行),而且清晰易懂,逻辑一目了然,相比之前那个150行的解决方案,可谓是天壤之别。

 


反思:

  • 作为一个程序员,当我接到一个task的第一反应就是CODING(我想这也是大多数程序员的通病吧),然而这时我可能并没有对这个任务有一个清晰的认识,然后写出一摊虽然可以run但是看起来莫名其妙的代码。在完成任务之后马上进行下一个task,然后这一摊weird code就被搁置在那里。等过了一段时间之后,连我自己都看不懂了,想改也没法改,一是没有时间,二是可能有一些人用到了我的代码,修改的话会引发其各种不想看见的连带效应。
  • 所以Jon Bentley在他的Programming Pearls一书中提到:Good programmers are a little bit lazy: they sit back and wait for an insight rather than rushing forward with their first idea。而我们在编程时是怎么做的呢?真实的情况时,我们往往过早的陷入到了实现功能的误区中,而忘记了原本问题到底是什么。即使是到后来insight灵光一现,也已经是too late to modify了。所谓磨刀不误砍柴工,就是这个道理。
  • 在Geogre Polya的神作How to solve it?一书中,Polya为解决问题定义了一个系统化的方法:理解题目->规划解决方案->执行解决方案->对解决过程进行反思。Polya提到,我们很多人都只注意到了前三步,而最后一步,也是他认为非常重要的一步却被忽视了。要知道我们解决新的问题往往是基于我们已有的经验的,而这些经验并不是由重复性的工作中而来,而主要从对工作的反思中而来。
  • 此外,科学家往往有这样的思维,那就是越复杂的问题的解释往往是非常简单的。Dirac甚至说:“一个理论家宁可要一个美的方程,也不要一个丑的但结果与实验数据更一致的方程。”举个简单的的例子,我们在小时候的数学考试中,如果得到的答案是1、2或者是10,我们往往会欣然接受答案;但如果计算的答案是11/17、1.947此类的数字时,我们往往会怀疑自己是不是算错了,原因很简单,这些答案的样子太邪恶了。
  • 回到程序员的视角,如果当我们对一个问题给出一个自己都认为丑陋无比的解决方案时,这时很可能就是哪里出了问题:对问题的理解不够深入?使用了错误的数据结构?此时不应该去继续CODE,而是应该进行仔细的思考,换句话说,在一些情况下,程序员应该像Dirac那样,对优美的CODE有着近乎偏执的追求。当然了,那些manager会不会允许程序员这么做,就是另外一回事了。
posted @ 2010-11-25 18:42 _Luc_ 阅读(1654) 评论(2) 编辑

一个编程小题目引发的思考

 

首先简介下题目:

 

输入:一个不超过12位的十进制正整数

 

输出:打印此数字的十进制计算器表示

 

例:

 

输入:145

 

输出:
         __
   ||__||__
   |   | __|
看到这个题目,也没多想,反正就是把这些数字打出来而已,那就一行一行打呗 于是在纸上画了几个计算器表示形式的数字: 规律是很明显的,每个数字都由3行4列组成,每一行只有固定的几种样式,比如说8的第一行是" __ ",第二行是"|__|",第三行是"|__|" 于是就有了思路:只要一位一位的读取这个数字,然后按照上中下的顺序依次打印其计算器表示的三行就行了。 由于每个数字的每一行的样式都是固定的,很容易将这些样式抽取出来,经过观察,我找出了其所有的样式,于是就有了如下的代码:
class LCDPrinter
{
    // all the paradigms in the LCD representation
    private static readonly string S0 = "    ";
    private static readonly string S1 = " __ ";
    private static readonly string S2 = "   |";
    private static readonly string S3 = "|   ";
    private static readonly string S4 = " __|";
    private static readonly string S5 = "|__ ";
    private static readonly string S6 = "|__|";
    private static readonly string S7 = "|  |";
    public void PrintNum(int value)
    {
        //TODO: print the number layer by layer
    }
}
接下来的问题就是依次获取一个数的每一位,通过一个递归,很容易实现这个功能:
    public void PrintNum(int value)
    {
        if (value != 0)
        {
            PrintNum(value / 10, layer);
            Console.Write(value % 10);
        }
    }
测试这个方法之后,接下来的工作就是一行一行的打印数值了,按照之前的思路,我把每个数字分为上中下三层,于是可以这么写:
    public void PrintNum(int value)
    {
        PrintTopBody(value);
        Console.WriteLine();
        PrintMiddleBody(value);
        Console.WriteLine();
        PrintBottomBody(value);
        Console.WriteLine();
    }
然后逐个实现每个方法,为了确保这个思路是正确的,先不用考虑所有的数字,只考虑数字1这个情况
    private void PrintTopBody(int value)
    {
        if (value != 0)
        {
            PrintTopBody(value / 10);
            int num = value % 10;
            switch (num)
            {
                case 1:
                    Console.Write(S0);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
    private void PrintMiddleBody(int value)
    {
        if (value != 0)
        {
            PrintMiddleBody(value / 10);
            int num = value % 10;
            switch (num)
            {
                case 1:
                    Console.Write(S2);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
    private void PrintBottomBody(int value)
    {
        if (value != 0)
        {
            PrintBottomBody(value / 10);
            int num = value % 10;
            switch (num)
            {
                case 1:
                    Console.Write(S2);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
然后以1,11,1111作为输入进行测试,发现结果是正确的。 接下来就可以完善这三个方法了,经过一段时间的编码和测试,我完成了这道题目。其功能代码如下
    class LCDPriter
    {
        private static readonly string S0 = "    ";
        private static readonly string S1 = " __ ";
        private static readonly string S2 = "   |";
        private static readonly string S3 = "|   ";
        private static readonly string S4 = " __|";
        private static readonly string S5 = "|__ ";
        private static readonly string S6 = "|__|";
        private static readonly string S7 = "|  |";
        public void PrintNum(int value)
        {
            PrintTopBody(value);
            Console.WriteLine();
            PrintMiddleBody(value);
            Console.WriteLine();
            PrintBottomBody(value);
            Console.WriteLine();
        }
        private void PrintTopBody(int value)
        {
            if (value != 0)
            {
                PrintTopBody(value / 10);
                int num = value % 10;
                switch (num)
                {
                    case 0:
                        Console.Write(S1);
                        break;
                    case 1:
                        Console.Write(S0);
                        break;
                    case 2:
                        Console.Write(S1);
                        break;
                    case 3:
                        Console.Write(S1);
                        break;
                    case 4:
                        Console.Write(S0);
                        break;
                    case 5:
                        Console.Write(S1);
                        break;
                    case 6:
                        Console.Write(S1);
                        break;
                    case 7:
                        Console.Write(S1);
                        break;
                    case 8:
                        Console.Write(S1);
                        break;
                    case 9:
                        Console.Write(S1);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
        private void PrintMiddleBody(int value)
        {
            if (value != 0)
            {
                PrintMiddleBody(value / 10);
                int num = value % 10;
                switch (num)
                {
                    case 0:
                        Console.Write(S7);
                        break;
                    case 1:
                        Console.Write(S2);
                        break;
                    case 2:
                        Console.Write(S4);
                        break;
                    case 3:
                        Console.Write(S4);
                        break;
                    case 4:
                        Console.Write(S6);
                        break;
                    case 5:
                        Console.Write(S5);
                        break;
                    case 6:
                        Console.Write(S5);
                        break;
                    case 7:
                        Console.Write(S2);
                        break;
                    case 8:
                        Console.Write(S6);
                        break;
                    case 9:
                        Console.Write(S6);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
        private void PrintBottomBody(int value)
        {
            if (value != 0)
            {
                PrintBottomBody(value / 10);
                int num = value % 10;
                switch (num)
                {
                    case 0:
                        Console.Write(S6);
                        break;
                    case 1:
                        Console.Write(S2);
                        break;
                    case 2:
                        Console.Write(S5);
                        break;
                    case 3:
                        Console.Write(S4);
                        break;
                    case 4:
                        Console.Write(S2);
                        break;
                    case 5:
                        Console.Write(S4);
                        break;
                    case 6:
                        Console.Write(S6);
                        break;
                    case 7:
                        Console.Write(S2);
                        break;
                    case 8:
                        Console.Write(S6);
                        break;
                    case 9:
                        Console.Write(S4);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }
虽然这段代码可以工作,但是怎么看怎么别扭,不知所以然的S0到S7这8个全局字符串(本身就不好命名),逻辑极其类似的PrintTopBody,PrintMiddleBody,PrintBottomBody这三个方法,使得逻辑很简单的一个程序却很难看懂。

 

下篇:一个编程小题目引发的思考(下)

posted @ 2010-11-25 15:55 _Luc_ 阅读(1486) 评论(6) 编辑

2010年8月30日 #

摘要: 关于Fibonacci函数的100种写法 最近在玩Python,在粗略的看了一下LearningPython和CorePython之后,发现网上有个帖子<Python程序员的进化>写的很有意思。于是打算仿照一篇,那篇帖子用了十余种方法完成一个阶乘函数,我在这里会用九种不同的风格写出一个Fibonacci函数。 要求很简单,输入n,输出第n个Fibonacci数,n为正整数 下面是...阅读全文
posted @ 2010-08-30 19:59 _Luc_ 阅读(1344) 评论(3) 编辑

2010年8月21日 #

摘要: 最初接触电脑那会是在小学五年级,那会电脑不叫电脑,叫微机,可是个新潮东东。为了不至于假期无所事事,加上自己对机械这类的东东还是比较有兴趣的,于是报了一个微机入门班,学了一个月左右。 那个培训的大体内容就是些基本操作,Dos操作(似乎现在已经成了不少伪电工的ZB绝学),wps(史前的word,至今仍大致记得按住CTRL依次输入求伯君的拼音就可以破掉密码),cced(史前的制表软件),fox bas...阅读全文
posted @ 2010-08-21 23:33 _Luc_ 阅读(179) 评论(0) 编辑

2010年4月10日 #

摘要: CMU是全美以至全球公认的CS最猛的大学之一,没办法,作为CS的发源地,再加上三位神一样的人先后在此任教:Alan Perlis(CS它祖宗+第一届Turing奖获得者)、Allen Newell(AI缔造者+Turing奖获得者)和Herbert Simon(AI缔造者+Turing奖获得者+Nobel经济学奖获得者,当代的Leibniz,偶佩服到死的一个天神下凡级的人物,他的自传Mode...阅读全文
posted @ 2010-04-10 13:33 _Luc_ 阅读(2878) 评论(15) 编辑

2009年9月29日 #

摘要: 二,Junior Bibliography续上一篇 Prerequisites不可否认小马哥是软件学院里最有实力的老师之一,至少他的.Net课程激起了偶对CS的兴趣,将偶引入了CS的大门。但对于学习.Net是否需要阅读相关书籍这个问题,偶和小马哥的理念出现了分歧,hoho偶一直认为小马哥是典型的实用主义者,奉行"实践出真知"这个套路,他认为从初学到理解再到熟悉.Net这个过程需要大量的实践,这一点...阅读全文
posted @ 2009-09-29 22:26 _Luc_ 阅读(331) 评论(0) 编辑