按 ‘ 编程 ’ 标签归档

程序员入伙书——运算符和表达式

上次我们说到了简单数据,现在就要说说怎么烹饪这些数据。因为我们的示例语言是Python,所以这里的内容只能保证对Python完全正确,其它的语言会有相似内容,但不一定完全相同。不用担心,各种编程语言的要素其实是差不多的,学会一门语言的思路,其它的也能很快融会贯通。

适用于数字的运算符

+       -       *       **      /       //      %
< <      >>      &       |       ^       ~
<>       < =      >=      ==      !=

其中,第一行运算符是一般的数学运算,第二行是计算机特色的位运算(不用担心看不懂,后头会讲到),第三行是数值比较。

先说第一行:+和-都很直观,加减是也。*是乘法,因为计算机键盘上没有×号,所以拿星号表示。你会问为什么不用x来代替?我回答:因为x是一个英文字母,会被Python当作一个变量名。

**是乘方的意思,例如2**3等于8,2**-1等于0.5,2**0.5等于1.4142135623730951(即根号2)。你可能会如梦方醒地说,既然可以这么方便地求解一个数的平方根,为啥当初要写那么复杂的一个算法?俺一边躲闪着观众的飞石,一边说:一、那段程序的主要目的,是演示二分查找的算法。二、其实在计算机的芯片级指令里也并没有乘方功能,**运算符的简洁外表的背后,隐藏着泰勒级数展开的算法,不比我那段程序轻松。

/是除法。小时候如果在试卷上这么写,是会被扣分的。老师只允许用÷号,或者以分数形式表示。键盘上没有÷号,就只能用/了。

//是除法取整数商。注意它和/是不同的:3/2的结果是1.5,而3//2会把那个小数部分.5扣除,只留下1。同理,1//2等于0,而不是0.5。拿这个运算符算整数是安全的,而算实数要小心,例如你可以试试0.7//0.1,和《两个世界》一章里的计算结果是一样一样的:6,具体原因在那一章已经讲过了。

%是除法求余数,例如7%4等于3,3%2等于1,8%4等于0。这个运算符很实用,常用来判断一个数字能否被另一个数整除(余数为0),或者把随机数字限定在一个范围内(不需要十分懂下面这个例子):

>>> import random
>>> random.random()
0.08662194959873759
>>> int(random.random() * 1E10) % 10
9
>>> int(random.random() * 1E10) % 10
5
>>> int(random.random() * 1E10) % 10
0
>>> int(random.random() * 1E10) % 10
4
>>> int(random.random() * 1E10) % 10
6
>>> int(random.random() * 1E10) % 10
2
>>> int(random.random() * 1E10) % 10
1
>>> int(random.random() * 1E10) % 10
8
>>> 

 

暂且跳过第二行的位运算,这些概念目前有点难,大部分情况下也用不着,先介绍第三行的比较运算吧。

<>       < =      >=      ==      !=

这六个运算符十分好理解,从左往右,分别是小于、大于、小于等于、大于等于、等于、不等于的意思。怎么样?很简单吧?计算机键盘上没有≤、≥、≠这些字符,所以用< =、>=、!=来代替,就和刚才说过的乘除法似的,比较好懂。对于==,就得多解释一下:为什么不用单个的=呢?因为单个的=被用来表示“赋值”的含义了。看下面和Python Shell的交流过程,来加深理解:
猛击阅读全文

程序员入伙书——简单数据

一边写一边问读者对这个系列教材的感觉。组里的小姑娘说,读下来,照着做,目前理解上没问题。不过她也说,她的室友是学新闻的,对编程不感兴趣,读这些文章时,反映“读不太懂”。她又说,直到现在还上开胃菜,进度有点慢,希望尽快吃到正餐。

好,正餐来了。

在《程序在干什么?》一章,我说过程序最基本的功能是计算——如果没有计算,我们也不需要计算机了。计算机的运算和人类的数学不同。人类的数学可以设置未知数,然后用推导方法把这个未知数的实际值逼出来。计算机的每一步运算,参与者都是已知的,只是有些参与运算的值永远不变,有些一会儿装三个苹果,一会儿装一百个大象。永远不变的被称为“常量”,可能改变的被称为“变量”。

以下我要说到的规则,仅保证对Python语言有效,其他语言虽然会很相似,但可能有自己的特色法则。

常量

有基本的两种常量:数字和字符串。快捷地描述它们的区别:数字是数学,字符串是语文。

数字很直接,0、-3.5、2012、378.2452,这些数学上看着顺眼的,都是Python语言里合法的数字。另外有些不太顺眼的数字,例如.3和5.,Python也宽宏大量地认为它们是合法的数字,.3意思就是0.3,而5.意思就是5.0。有些巨大的数字或者极小的数字,用科学计数法表达更为便捷的,如1.5×109或者3.0×10-32,Python不会让你辛辛苦苦埋头去数0,而是可以分别表示为1.5E9和3.0E-32,这里的E可以小写。指数前也可以加正负号。
猛击阅读全文

程序员入伙书——程序在干什么?

程序员是写程序的,程序是干什么的?

我的独门总结:形形色色的程序,大到组成整个操作系统或者办公软件,小到打印一个九九乘法表,它们都有三个共同的功能块:

  • 计算
  • 流程控制
  • 输入输出

计算,不但是数学运算,也包括对语言文字信息的分析,判断异同、比较大小,都属于“计算”的范畴。如果没有计算,如果所有的数据、信息都不需要处理,我们要计算机做什么?当打字机使么?

流程控制,包括条件判断、循环、跳转。通过使用流程控制,我们可以写很短的程序,让计算机重重复复地执行成千上万的步骤,并且花样繁多。计算机虽然不会思考,但它们做明确具体的事情时还是比人速度快、而且重复性好。你让计算机做两个数相除,做一亿次的结果都是一样的,且它也不喊累。可让人来做的话,首先就是慢,也可能做错,做十次会不耐烦,做五百次会精神崩溃。小时候被老师罚抄作业十遍的同学,请自行默默回忆童年时的阴影。

输入:键盘、鼠标指令,从网络上接收服务请求,或者从硬盘读个图片……把外部设备上的电信号吸纳进计算机的过程,都算是输入。输出:在屏幕上显示一张图片、一些文字,往U盘上拷东西,播放音乐或视频……把计算机心里想明白的事情说出来的过程,这些都是输出。如果程序没有输入,那么它每次的运算都是从已知的、固定的条件开始,结果会单调得让人厌烦。学编程的时候,看到一群老年大专生用BASIC语言打印熊猫,以为自己在编程,心里的怜悯油然而生。如果程序没有输出,计算机有电没电的区别就不太大了,当然,冬天屋里冷的时候,多开个电器,确实可以更暖和一些。

可以说,所有的程序做的事情不外乎这三个概念。你打开网络浏览器,IE也好,FireFox也罢,你输入一个网址,按回车键,它就向网络端口上输出一个请求,向那个网址要网页,要来的网页从网络端口输入进来,浏览器反复判断每个传进来的数据流,看它们是图片还是一般文字,如果是图片,还需要做些解码工作计算摆放它们的位置,显示在窗口上。在这个回合里,计算、流程控制、输入输出都是齐全的。除了这三样,确实也没有其它的操作了。

说了这么多,我来演示三个例子吧。

第一个例子,是算从1加到100的总和,传说高斯用很聪明的办法搞定了这道题,我们来看看很笨很死脑筋、却擅长做高速计算的计算机是怎么做的。打开Python的窗口(说啥呢?),输入(虽然可以拷贝粘贴,但我更希望你亲自敲一遍):

total = 0
for i in range(1, 101):
	total = total + i
	# 请在这里多敲一两次回车(Enter)键,以使Python显示>>>提示符
print(total)

打字时请注意,Python对每行的缩进要求很严格,因为它认为这是表达程序结构的一部分,所以一定要按照这个例子里来敲。敲完最后一行“print(total)”,如果你看到了5050,就知道自己做对了。
猛击阅读全文

程序员入伙书——两个世界

作为程序员,我们常往来于两个世界之间:计算机世界和现实世界。为了快速地让你感受到这两个世界的不同,我举一个例子。请在Python(啥?)的“>>> ”提示符后输入这样一行:

int(0.7 / 0.1)

现在先不要按Enter(回车)键,停下来,听我解释一下:这句话意思是,计算0.7除以0.1所得的商,取其整数部分。这个简单的除法,你觉得结果会是什么?

7,对吧?现在,敲回车,看看计算机认为结果是什么。

>>> int(0.7 / 0.1)
6
>>>

😮 😮 😮

为了弄清楚这个6是怎么出来的,我们先把“取整数部分”去掉,只留下0.7 / 0.1——如果你曾经听说过“调试”这个词,我们现在就是在调试:

>>> 0.7 / 0.1
6.999999999999999
>>>

现在我们至少可以理解,为什么“取整数部分”之后的结果等于6了,因为6.999999999999999的整数部分确实等于6(虽然四舍五入等于7,但“取整数部分”的操作是比较憨傻的)。可是可是,为什么0.7 / 0.1不正好等于7呢?这就是计算机世界和现实世界的差别之一。完美的数学世界里,任意两个实数之间必然存在无穷个实数,而计算机不这么认为。因为计算机的位数有限,它能表达的实数是有限的,实数之间总是存在一个个“空隙”,在计算机的世界里,“两个相邻的实数”是合理的说法。不精确但是意思到了的类比是,在只能显示五个数字的计算器液晶屏上,刚刚比1.0000大的下一个数就是1.0001,而介于其间的其他数,如1.00005、1.0000342376,则无法被这个液晶屏精确表达。

说出来你可能不信,0.1就是一个计算机无法精确表达的数字。这个数字对于我们这些习惯了十进制的人来说,是如此的简洁。可是机器用二进制思考,在二进制的世界里,十进制的0.1是一个无限循环小数:0.000101000101…,无法被精确表示。

再考虑这段例子:

>>> 1e10
10000000000.0
>>> 1e10000
inf
>>> 

1e10的意思是1乘以10的10次方,计算机心领神会地把这个数字表达出来了。1e10000的意思是1乘以10的10000次方,计算机口吐白沫地说:这个数……太大了……这就是传说中的“无穷大”吧?
猛击阅读全文

程序员入伙书——下载Python并念诵程序员真言

想通过纯读书来学习任何技术,可能都行不通。我发现,即使你不编写程序,而只是把别人写好的程序亲自在键盘上敲一遍,都会有很强的感觉。默读一首诗,试图背诵它,不如念出声给自己听来得快。所以我恳请读者通过练习来掌握编程,练习需要用至少一种编程语言,我在这里推荐Python,后头的示例里,也会一直用Python来做例子。

你可以尽情忽略本篇里的“编程语言”、“Python”这些还不应该被提及的术语,它们目前一点也不重要。

对于早就是程序员的围观群众来说,我选择Python作为示范语言的理由有:

  • Python无需(看得见的)编译即可直接运行。
  • Python完全免费,拿来教学不涉及法律问题。
  • 使用Python的话,即使是初学者的程序,看起来也和牛人们写的一样悦目。(请呵呵大笑)

在Windows的机器上,下载并安装Python的步骤(其他机型需要去http://www.python.org自行发现):

  1. 点击 http://python.org/ftp/python/3.3.0/python-3.3.0.msi下载一个叫做python-3.3.0.msi的文件。把它存放在你的桌面上,或者一个你回头找得着的文件夹下。
  2. 下载完毕后,运行这个文件,接受一切默认选项,一路点Next就可以了。
  3. 安装完毕后,当你点“开始”或者“Start”按钮,再移动鼠标到“所有程序”或“All Programs”时,你会发现一个叫做“Python 3.3”的新建程序组,再移动鼠标到这里展开它,选择“IDLE (Python GUI)”,你会看到这个窗口:

当读者老爷们看到这个窗口后,我不想对大家说:“恭喜你!工具已经准备好了,现在请关掉窗口并期待明天的正式讲课。”正相反,我想请大家做一件十分重要、十分重要的事情。这个事情,就是念诵程序员真言,写下你人生中的第一个程序。让闪烁的光标伴随着你因激动而发抖的双手,在窗口提示的“>>> ”后面输入以下文字:

print('hello, world')

现在,停一下,请检查:

  • hello和world必须全都是小写字母。
  • world后面不要跟句点。
  • 逗号后面一定要跟个空格。

都确认之后,敲Enter键,你会看到窗口里有这些内容:

>>> print('hello, world')
hello, world
>>>

其中第一行print啥啥的是你敲进去的,第二行“hello, world”是程序的输出,而“>>> ”是Python期待你继续指挥它的提示符。

关于“hello, world”,传说,每个程序员必须尽早让自己的一个程序朗诵这句话,并且,除了朗诵这句话之外,这个程序什么别的事也不要做。念诵这句话必须做到每个字母、每个标点、每个空格都是精准正确的,否则,不但不能成为一个好的程序员,未来的编程工作中还会出现种种意想不到的失误(简称bug)。

现在,我可以说:恭喜你!欢迎入伙!你编写了至少一个程序,现在已经是程序员了!

程序员入伙书——序

公司里有个小女孩,在我组里工作一年半了。虽然在我组里,其实是挂靠一下。她的工作是数据录入,多是手工工作,体力上很辛苦,工资也委实不高。但她很敬业很努力,也时常做超越自己的事情。

我这个组里的其他人都是程序员,她坐在里头,耳濡目染地有了一些思路。每每有事,她还是很想用自动化的方法解决的。有这么一个机会,一个同事用Excel写了个数据发送程序,里面放了些用于统计及查错的公式,这还没涉及编程呢,已经引起她浓厚的兴趣。后来她还真的自己捣鼓出来一个检查数据是否已经完备的VBA,别管她请了多少人帮忙吧,总算是她自己推进的,更重要的是她做成了。我注解一下,在大公司里,做一件不同于常规业务职责的事,是很不容易的。

曾经有封信,从好吃懒做的印度同事发来的,信里问的是个技术问题,当时北京已经下了班,他们就把信发给了巴黎技术组的当班经理,不料这小姑娘当时在北京的办公室还没下班,就手把这个“技术问题”解决了。印度大窘,后来常常受到我们的挤兑:你们就不能多学点?你瞧瞧我们的小姑娘,也不是科班出身,才毕业一年多点,都不用技术员们帮忙就搞定了!

我觉得呢,很多事情,例如写程序,有些人说学不会,那是有两个原因:一、没兴趣;二、没法让他感兴趣。如果第一个原因是第二个原因引起的,或许还可以从表达手段上努力一下,拉他们入伙。像我刚才说到的这位小姑娘呢,她是感兴趣却还没有完全找到感觉,平时说话也总让人觉得糊里糊涂的。但我觉得,这个“糊里糊涂”只是非码农的经典思路而已,只要能上道,就可以改变。以我的表达水平,把她雕琢成程序员还是有希望的。人到我这里时一张白纸,如果被我画上画,而且还画得不错,拿出去能升值,是我最大的成就。

所以我准备写个编程教材系列,把只会使用“电脑”(对不起,我只称“计算机”)打字上网玩游戏的非科班人士变成程序员。不要指望读了这个系列能成为计算机科学家,因为我还不是。我也不想把这个教材称为傻瓜书,因为大家不傻。我本着平等的原则,称之为“程序员入伙书”吧。

二十四点

这是一个算二十四点的程序。从命令行输入四个数字,结果以后缀表达式输出。如:

$ calc24 8 8 3 3
8383/-/

即8/(3-8/3)。

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
#include	<math.h>

void	calc24(int, int, int, int);
double	calc(char *);

void	main(int argc, char *argv[])
{
	int	n[4];
	int	i, j, k, m;

	if (argc != 5) {
		printf("Usage: %s a b c d\n", argv[0]);
		return;
	}
	for (i = 0; i < 4; i++)
		if ((n[i] = atoi(argv[i+1])) < 1 || n[i] > 9) {
			printf("%s is not a valid input\n", argv[i+1]);
			return;
		}
	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++) {
			if (j == i)
				continue;
			for (k = 0; k < 4; k++) {
				if (k == i || k == j)
					continue;
				for (m = 0; m < 4; m++) {
					if (m == i || m == j || m == k)
						continue;
					calc24(n[i], n[j], n[k], n[m]);
				}
			}
		}
}

void	calc24(int a, int b, int c, int d)
{
	char	expr[10];
	char	*oper = "+-*/";
	int	i, j, k, m;

	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++) {
			for (k = 0; k < 4; k++) {
				sprintf(expr, "%d%d%d%d%c%c%c", a, b, c, d, oper[i], oper[j], oper[k]);
				if (fabs(calc(expr) - 24) < 1.0E-3)
					puts(expr);
				sprintf(expr, "%d%d%d%c%d%c%c", a, b, c, oper[i], d, oper[j], oper[k]);
				if (fabs(calc(expr) - 24) < 1.0E-3)
					puts(expr);
				sprintf(expr, "%d%d%d%c%c%d%c", a, b, c, oper[i], oper[j], d, oper[k]);
				if (fabs(calc(expr) - 24) < 1.0E-3)
					puts(expr);
				sprintf(expr, "%d%d%c%d%d%c%c", a, b, oper[i], c, d, oper[j], oper[k]);
				if (fabs(calc(expr) - 24) < 1.0E-3)
					puts(expr);
				sprintf(expr, "%d%d%c%d%c%d%c", a, b, oper[i], c, oper[j], d, oper[k]);
				if (fabs(calc(expr) - 24) < 1.0E-3)
					puts(expr);
			}
		}
}

double	calc(char *expr)
{
	double	a, b, stack[4];
	int	top, i, n;

	n = strlen(expr);
	top = 0;
	for (i = 0; i < n; i++) {
		if (isdigit(expr[i])) {
			stack[top++] = expr[i] - '0';
			continue;
		}
		if (top < 2)
			return	9999;
		b = stack[--top];
		a = stack[--top];
		switch(expr[i]) {
		case '+':
			stack[top] = a + b;
			break;
		case '-':
			stack[top] = a - b;
			break;
		case '*':
			stack[top] = a * b;
			break;
		case '/':
			if (b == 0.0)
				return	9999;
			stack[top] = a / b;
			break;
		}
		top++;
	}
	if (top != 1)
		return	-9999;
	return	stack[0];
}

98%的人一小时也做不出来的题目

据说这是98%的人一小时也做不出来的题目。在网上看见,一时技痒,编了个程序来解,总算落到那2%里面了。哈哈。

请回答下面10个问题:

1、第一个答案是b的问题是哪一个?
(a)2;(b) 3;(c)4;(d)5;(e)6
2、唯一的连续两个具有相同答案的问题是:
(a)2,3;(b)3,4;(c)4,5;(d)5,6;(e)6,7
3、本问题答案和哪一个问题的答案相同?
(a)1;(b)2;(c)4;(d)7;(e)6
4、答案是a的问题的个数是:
(a)0;(b)1;(c)2;(d)3;(e)4
5、本问题答案和哪一个问题的答案相同?
(a)10;(b)9;(c)8;(d)7;(e)6
6、答案是a的问题的个数和答案是什么的问题的个数相同?
(a)b;(b)c;(c)d;(d)e;(e)以上都不是
7、按照字母顺序,本问题的答案和下一个问题的答案相差几个字母?
(a)4;(b)3;(c)2;(d)1;(e)0。(注:a和b相差一个字母)
8、答案是元音字母的问题的个数是:
(a)2;(b)3;(c)4;(d)5;(e)6。(注:a和e是元音字母)
9、答案是辅音字母的问题的个数是:
(a)质数;(b)阶乘数;(c)平方数;(d)立方数;(e)5的倍数
10、本问题的答案是:
(a)a;(b)b;(c)c;(d)d;(e)e

唯一的答案是CDEBEEDCBA,算法如下:

#include	<stdio.h>

int	a[10];

void	select(int n, int answer);

void	main()
{
	int	i;

	for (i = 'a'; i <= 'e'; i++)
		select(0, i);
}

void	select(int n, int answer)
{
	int	i;
	int	same, pos;
	int	asum, bsum, csum, dsum, esum;
	int	diff;

	a[n] = answer;
	if (n < 9) {
		for (i = 'a'; i <= 'e'; i++)
			select(n + 1, i);
		return;
	}
	/* 1 */
	for (i = 0; i < 6; i++)
		if (a[i] == 'b')
			break;
	if (i < 1 || i > 5 || a[0] != 'a' + i - 1)
		return;
	/* 2 */
	same = 0;
	pos = 0;
	for (i = 0; i < 9; i++)
		if (a[i] == a[i+1]) {
			if (same)
				return;
			same = 1;
			pos = i;
		}
	if (pos < 1 || pos > 5 || a[1] != 'a' + pos - 1)
		return;
	/* 3 */
	if (a[2] == 'a' && a[0] != a[2])
		return;
	if (a[2] == 'b' && a[1] != a[2])
		return;
	if (a[2] == 'c' && a[3] != a[2])
		return;
	if (a[2] == 'd' && a[6] != a[2])
		return;
	if (a[2] == 'e' && a[5] != a[2])
		return;
	/* 4 */
	asum = 0;
	bsum = 0;
	csum = 0;
	dsum = 0;
	esum = 0;
	for (i = 0; i < 10; i++) {
		switch (a[i]) {
		case 'a':
			asum++;
			break;
		case 'b':
			bsum++;
			break;
		case 'c':
			csum++;
			break;
		case 'd':
			dsum++;
			break;
		case 'e':
			esum++;
			break;
		}
	}
	if (asum != a[3] - 'a')
		return;
	/* 5 */
	if (a[9+'a'-a[4]] != a[4])
		return;
	/* 6 */
	if (asum == bsum && a[5] != 'a')
		return;
	if (asum == csum && a[5] != 'b')
		return;
	if (asum == dsum && a[5] != 'c')
		return;
	if (asum == esum && a[5] != 'd')
		return;
	if ((asum == bsum || asum == csum || asum == dsum) && a[5] == 'e')
		return;
	if ((bsum == csum || csum == dsum || dsum == esum ||
	    esum == bsum || esum == csum || bsum == dsum) && a[5] != 'e')
		return;
	/* 7 */
	diff = a[6] - a[7] > 0 ? a[6] - a[7] : a[7] - a[6];
	if (a[6] != 4 - diff + 'a')
		return;
	/* 8 */
	if (asum + esum < 2 || asum + esum > 6 || a[7] != 'a' + asum + esum - 2)
		return;
	/* 9 */
	switch (bsum + csum + dsum) {
	case 3:
	case 7:
		if (a[8] != 'a')
			return;
		break;
	case 4:
	case 9:
		if (a[8] != 'c')
			return;
		break;
	case 6:
		if (a[8] != 'b')
			return;
		break;
	case 8:
		if (a[8] != 'd')
			return;
		break;
	case 10:
		if (a[8] != 'e')
			return;
		break;
	default:
		return;
	}
	/* 10 */
	/* print solution */
	for (i = 0; i < 10; i++)
		putchar(a[i]);
	putchar('\n');
}