Programming

蚂蚁问题

题目:“有一根27厘米的细木杆,在第3厘米、7厘米、11厘米、17厘米、23厘米这五个位置上各有一只蚂蚁。木杆很细,不能同时通过一只蚂蚁。开始时,蚂蚁的头朝左还是朝右是任意的,它们只会朝前走或调头,但不会后退。当任意两只蚂蚁碰头时,两只蚂蚁会同时调头朝反方向走。假设蚂蚁们每秒钟可以走一厘米的距离。编写程序,求所有蚂蚁都离开木杆的最小时间和最大时间。”

我用Java写的解答 :http://wangcong.org/src/ant.java

构造递归并不容易

递归绝对没有我们想像中那么容易构造,虽然这一点很早以前就有所体会,但今天在用Java写一个全排列的程序时才有了更深刻的认识,决定记录下来。

题目是这样的:

“用1、2、2、3、4、5这六个数字,用Java写一个程序,打印出所有不同的排列,如:512234、412345等,要求:”4”不能在第三位,”3”与”5”不能相连。 ”

思路非常简单,就先对给定的数字进行全排列,然后剔出不符合要求的就行了。所以这个题的关键是求全排列。记得以前写全排列的时候都是用一个非递归的蛮力算法,今天想试试以前见过的一个递归算法。递归的原理其实很简单:依次从给定的数中抽取一个,把它放到最前面,然后把剩下的再进行全排列即可。但是,真正写起代码来却不容易,说来惭愧,光是构造递归我就花了近一个小时。

我认为,构造递归的关键在于对以下三个问题的处理:

1. 传递什么样的参数才能使得递归顺利进行?

2. 递归过程用不用返回值,用的话返回什么样的值?

3. 递归的终点是什么?

经过几次尝试以后,我决定这样构造上面那个递归,传递的参数是进行全排的字符串,返回该字符串的全排列的一个Vector。 具体源代码见:http://wangcong.org/src/permute.java

Debugging Day

今天下午审查我们这边的Java代码时捕捉到N个bug,收获颇多。在这里小总结一下。

1. 最难找的bug

这个bug困惑我们很久了,我们为了它 已经有好几天食不甘味了。bug的症状是这样的,在通过ls读取数据连接的数据时,总是停在最后那里。我们一直以为是read的原因,而且网上也有讨论说是靠read()的返回值并不可靠。于是我们都在研究read()……但今天孔建军突然报告说是close()出问题了,而且把FTPOutStream.close()去了问题就没有了!最后分析发现,应该是Java中的stdout不能关闭。

修补很简单,如果是标准输出跳过关闭就行了。


+if (!LocalFile.equals(“”)) //We can NOT close the stdout.

  • FTPOutStream.close();
    2. 最无奈的bug

我们ls/get某个文件总是出错,一开始还以为是编码问题,后来我才发现竟然是空格问题!

修补就是把多余的空格去了!

3. 最头疼的bug

FTPConnection.Status的值几乎总是不对,关键是处理异常后没有恢复原来的状态。因为它和异常处理缠绕在一起,所以不太好修复。

4. 处理最巧妙的bug

到最后,周晓炜发现一个问题,我们竟然无法区分close和quit,因为有控制连接时我们都把它翻译成了QUIT!在处理QUIT时无法对最初的输入进行判断。

我想到的修复办法比较巧妙,那就是在quit对应的QUIT后面再加一个QUIT。发送第一个QUIT命令后,可以通过检查后一个命令是否还是QUIT来判断是quit还是close。这样问题就解决了。

Java!!!

对Java比较无语。

先是调试器JDB,和GDB根本就没法比。JDB不能通过向上向下键来取历史命令,JDB不能简单地通过回车来执行上一条命令,最关键的是, 在调试交互输入的程序时,JDB根本就不让你插手。KAO啊,无语~

再说Input/Reader 里面的read*方法,貌似通过其返回值来判断是否读完很不可靠,总是读“不完”。这还不算完,我不用返回值来判断了还不行!新闻组上说最好在另一个thread中读,无语了。为了读个文件还得再弄一个thread……

无语中……

另一道编程题目

题目是这样的,已知set的定义文法如下:


Set ::= “{“ Elementlist “}”
Elementlist ::= <empty> | List
List ::= Element | Element “,” List
Element ::= Atom | Set
Atom ::= “{“ | “}” | “,”

要求,输入一个 字符串,判断它是否为一个set。如果我没理解错的话,原应该错了,因为按照这个定义”4”不应该是set。这道题感觉比上一道还简单,我的代码如下(注意,我这程序是用后面跟的参数做为输入,你应该清楚shell是怎样处理参数的,请确定这一点后再来谈我这程序。):

http://wangcong.org/src/set.cpp

一道编程题目

题目大体上是这样的:

输入两个字符串,要求 输出如何从前一个变到后一个,允许的操作只有如下三种:

1. 替换,Ca2表示把第2个字符替换为a
2. 删除,Dc3表示删除第3个字符c
3. 插入,Id4表示在第4个字符处插入字符d

如果有多种可能,只输出一种即可。比如,输入abcde和bcgfe,一种可能的输出为:
Da1
Cg3
If4

这不难,请仔细想想再看我写的代码:
http://wangcong.org/src/str_ops.cpp

学习C++的一些心得

最近在看《Essential C++》 ,发现自己的C++学得很不及格!下面是学习的一些心得:

1. 重载<<或>>操作符时千万注意,它不能是成员函数,如果不不幸把它声明成了类foo的成员函数,那你得这样用它了:
foo<<cout<<’n’;
没有人阻止你这么做,但这几乎肯定不会是你想要的!

2. 函数对象是一个好东西,它有点像是C中的函数指针。它其实就是重载了()的类,没什么奇特的。

3. static类型的数据成员表示单一的实例,它被所有类的对象共享。static类型的函数和Java中一样,也是可以不通过实例就直接调用。

4. 虚函数要么在声明它的类中定义,要么就被定义成纯虚函数。带有纯虚函数的类是不能有实例的。

5. 基类的析构函数必须是虚函数。

6. 使用dynamic_cast而不是static_cast。

7. 模板中不是只能带类型,而且还能带变量,它能按照你指定的值去创建“带有”这些值的类,它的作用有点儿类似于“默认”。

一道挺有意思的编程题

函数原形已经给出:int p(int i, int N);
功能:调用该函数,打印如下格式的输出,例p(1, 7);
1
2
3
4
5
6
7
6
5
4
3
2
1
即每行一个数字。(注意:N只打印一次)
要求:

1. 函数中唯一能够调用的函数就是printf
2.
不准使用如下的关键字:typedef, enum, do, while, for, switch, case, break, continue, goto, if
3.
不能使用逗号表达式和?:表达式。

4. 函数中只能有一条语句。

你会怎么做呢?请仔细想想,这道题不太容易想,但思考的过程非常有意思。我写的程序如下:

source code

发布SPyFTP

今天终于把以前用Python写的程序发布到Google Code上了,起了个自我感觉不错的名字SPyFTP,它是Smart Python FTP的缩写。打算把它的功能一点一点完善,以后还准备添加图形界面,而且也准备用Python写一下服务器端。项目链接如下:

http://code.google.com/p/spyftp/

希望各位朋友能帮忙测试,编写文档,或者进行改进。

引用Google Code上的一句话作为结束:

Release early, release often.

从C/C++到Java(三)

1. Java中对象的创建都是通过new操作符,而在C++中显然没有必要,你可以还通过定义一个实例来创建一个对象。

2. static在Java和C,C++中的意思差别挺大:
(1) 在Java中用它指明一个类成员是先于类而存在的,即可以不定义一个类的实例就可以直接使用。
(2) 在C++中有些复杂,第一个意思和Java类似,即可以不定义实例就使用类的成员,第二个意思是static成员可以直接继承到子类的作用域中,第三个意思是类定义中的static数据成员并不在类中定义,而且该成员并不属于该类,该类的实例共享同一个static成员。
(3)在C中,static的意义往往让人困惑。其实C中的static不外乎有两种作用,一种是限制存储域,一种是限制作用域。它用于函数时表示该函数是定义在该文件中的,外面的不能调用,当它用于局部变量时,它限制局部变量的存储域,让局部变量每次存取都维持上一次的值,当它作用于全局变量时,它限制该全局变变量只在其定义的文件中有效。

3. Java和C++都可以在运行时决定类型,C++是用typeid,而Java是用instanceof。

4. Java中的abstract和C++的virtual还不太相同。abstract类是不能有实例的,只能用来继承,abstract方法没有实体,它必须在所有子类中被覆盖(override)。上面的abstract方法在C++中被称做pure virtual,virtual方法在C++中的意思比Java中的abstract方法要复杂得多,virtual方法可以在各个类中定义,最后使用时调用哪个是动态决定的。再多说一句,C++中的基类的析构函数都必须是virtual的,而构造函数必须不能是virtual类型。

5. Java中没有运算符重载,虽然C++的运算符重载可以产生一些很好的代码,但是掌握它的使用也是要花一些代价的。

6. Java中没有模板,不过Object可以代为完成这个功能。因为Object是所有类的父类,所以它可以用来作为通用类,换句话说,在某种程度上完全可以当做模板。