斯坦福编程方法学作业讲解3---Karel机器人三大定律(上)

阿西莫夫机器人三大定律?

2016年,master横空出世,在网络平台上取得对中日韩顶尖棋手60连胜0负1平的逆天战绩,1平还是因为棋手掉线。网络有很多有趣的段子: 李世石赢AlphaGo,可能是人类对机器围棋的最后一场胜利。同时关于机器超越人类的担忧,像以前科幻电影各种机器人战胜人类的事好像不再是杞人忧天。不过阿西莫夫的《我,机器人》中提到机器人三大法则:

第一定律:机器人不伤害人类个体,或者目睹人类将遭受危险而袖手不管。

第二定律:机器人必须服从人给予它的命令,当该命令与第零定律或者第一定律冲突时例外。

第三定律:机器人在不违反第零,第一,第二定律的情况下要尽可能保护自己的生存。

看到这三大定律,悬着的心才稍微安定下来。

Karel机器人三大定律?

Karel是一个弱小,还有点缺陷的机器人,他还不足以对人类产生威胁,但是它也有自己的定律。那它的定律是什么?我们先看一个任务。Karel很忙,经常接受到各种任务。

Karel从当前街角开始,不断往前走,边走边放方块,直到将这一行都填满。如下图:

拿到这个任务很高兴,写了一个新的方法createBeeperLine(),总共就6次,如下面代码:

public void createBeeperLine(){

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();

}

Karel顺利完成任务,值得庆祝一下。但是这个方法好像有些问题,如果100行,难道我们要重复写move,  putBeerper, 100次吗?好像很傻一样。有没有一种办法处理这种重复的事。

Karel的第二定律

Karel的第二定律就是:我们总是不断重复着昨天的故事。

怎么直接就第二定律了,那第一定律是什么?

第一定律,我们之前已经体验过了:我们很多时候总是按照顺序执行任务,比如大家都是先上小学,然后上中学,最后上大学,这样按顺序执行的。

那程序如何处理反复执行的事情了?程序员称这个为循环,有两种方式,一种是for 循环,for语句用于当你想按预定的次数重复执行一组命令的时候,while语句用于当你想在某些条件满足时候,重复执行一组命令的时候。

我们先看看for循环,等讲完第三条定律的时候,再看看while循环。

循环有四个要素:

1)        开始的时候,

2)        结束的时候,

3)        迭代因子(计数器递增或者递减),也就是从开始到结束,比如电梯从1楼上到10楼,每一次递进1楼,没有递进,永远停留在开始,形成死循环。

4)        循环要做的事,比如在我们刚才的任务里,反复做的事就是putBeeper(),move()。

我们看一看for循环的语法结构.

for (int  i  =  0;  i  <= count; i = i + 1; )
{
   重复执行的语句。
}

int  i;i 是一个变量,这个跟初中代数变量的意思相近,就是值可以变化,比如定义x 变量,x 可以等于不同的数。int 表示变量的类型是整形,也就是i 必须是一个整数。同样表示小数的变量,类型是double,float,必须定义一个小数型的变量格式为:double  j = 5.20;

int  i = 0:表示i是 一个整形变量,我们将0赋值给i。

我们看看for循环的结构i= 0;就是开始的时候。 i < count; count就是结束的时候, i= i+ 1 ;就是迭代因子,每次递进1步,然后{}体内就是循环的内容。

我们看看for循环的流程图

for (int  i  =  0(初始化部分);  i  <= count(条件判断); i = i + 1; (迭代因子))

{

     重复执行的语句。(循环体)

}

 

我们再回到最初的任务,怎么将下面的代码改成循环

public void createBeeperLine(){

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();
		   move();

		   putBeeper();

}

 

总共有放了6个方块,那么我们改写代码如下:

	public void createBeeperLine(){
		   
		   for (int i = 0; i < 6; i++){
		      putBeeper();
		      move();
		   }

	}

我们简单看一下for循环代码执行顺序

那么程序的执行顺序就是

这里我们对for循环基本很熟悉。

我们把刚才代码测试一下是否完成了预期的目标。

是不是我们再一次体会到了这个世界的恶意,Karel执行完上面的循环撞墙了。

当它走到角落里放完Beeper之后不能再往前走了。我们只能循环5次了,我们把判断条件改为 i  <  5,试试。好像Karel没有撞墙,但是新的问题又出现了,最后一个位置是空的

解决这个问题很容易,等循环结束之后,我们在最后的位置执行putBeeper的命令,就完美解决问题。

代码如下:

	public void createBeeperLine(){
		
			for (int i = 0; i < 5; i++){
				putBeeper();
				move();
			}

			putBeeper();
	}

结果如下:

现在我们已经非常熟悉了for循环。Karel现在又接收到了新任务,将整个屏幕都填满方块。

我们执行完放满一行的任务之后,Karel现在处在屏幕第1街第6道,也就是第1街的最右边,现在Karel如何执行填满第2行的任务:首先上到第2街,然后调整好方向,然后接着执行createBeeperLine()命令。

我们把相应的代码写在Run方法里出来:

turnLeft();
move();
turnLeft();
createBeeperLine();

好像完成了我们的任务,Karel虽然有点残疾,但是他也有普通人没有的功能,就是倒立行走,跟西毒欧阳锋一样,倒立行走。

我们完成了第二行的任务。我们看看第三行怎么做?

是不是调整好方向就跟其他行没什么区别,如何调整方向?

向右转,往上走一步,然后再向右转,是不是跟第一行一样了?

我们把相应的代码写出来:

turnRight();

move();

turnRight();

createBeeperLine();

是不是第三行也放好了。我们继续这样放好第四行,第五行…直到最后一行。是不是顺利完成任务了。

 

大家回过头看看,是不是觉得这个方法一样不科学,他们是不是在重复着做某些内容。

第一行,第三行,第五行….奇数行是不是方向相同?

第二行,第四行,第六行…..偶数行是不是方向相同?

但是奇数行与偶数行不一致,有没有办法用一个循环解决?

有的人马上想到以放两行为一个循环,这样是不是比较完美地解决问题。我们先试试,先完成放两行的命令:

	public void createBeeperTwoLine(){
		createBeeperLine();
		turnLeft();
		move();
		turnLeft();
		createBeeperLine();
	}

放完两行之后,如果我们要继续再放两行是不是先要走上去,那么执行的命令的是先向右转,然后前进一步,然后再向传,是不是可以重复进行第二次放两行的命令:

我们现在可以向以前一样重复执行放两行的命令了,那么我们现在可以完整的写下代码:

	public void run(){
		for ( int k = 0 ; k < 3 ; k++){
			createBeeperTwoLine();
			turnRight();
			move();
			turnRight();
		}
	}

Karel的第三定律

这样是不是能够完成我们的任务,我们把代码检验一下,满怀期待,再一次感受到世界的恶意,是不是我们放完了,机器人并没有像我们预计的那样停下来,继续往上走,碰壁了。

我们如何解决这个问题,如果我们知道已经到了最后一行的时候,是不是可以告诉机器人不往前走了,我们现在来看Karel的第三大定律:人生总是充满着很多选择:比如向左走,还是向右走?对于我们的karel机器人当前遇到的问题是:是不是到了最后一行?是的话,不向上走了,如果没到,就继续向上走了?我们在程序有什么语法对应这种需要选择的情况。我们看看经典的if语句

if(条件检测){

   只有当条件满足时才会执行的语句

}else{

   只有当条件不满足时才会执行的语句

}

女生都是这种语句使用高手,谈过恋爱的男生都知道,女生一旦想要达成某项目标的时候,比如想让你给她买个包的时候,她都会用上这个语句:

if (你是不是不爱我了){

    如果是,就分手。

}else{

    如果不是,就买包。

}

基本这招屡试不爽。当然,有时候只有if,也就是意味着else的情况什么也不做。我们后面会经常见到。

 

比如我们怎么修改刚才的代码,可以避免机器人完成任务之后继续往上走撞墙,是不是如果是最后一行,就不往上走了,如果不是,那么机器人继续往向上走。对应的else的代码就是什么也不做了,完整代码如下:

	public void run(){
		for ( int k = 0 ; k < 3 ; k++){
			 createBeeperTwoLine();
			 if (k != 2){
				 turnRight();
				 move();
				 turnRight();
			 }
		}
	}

是不是完美地解决了这个撞墙的问题。

 

好,我们现在想想一次循环完成放两行的代码有什么缺陷?如果总行数是偶数的话,每次执行两行这样没有任何问题,但是如果总行数是奇数,比如7行,这个代码是不是不行。我们如何解决?是不是我们得先判断一下是不是奇数行,还是偶数行,又是一次判断。

 

我们有没有更好的解决方案,我们还是回到最初的地方去?

我们将任务分解成两件事:先铺满一行,如果是最后一行就结束任务,如果不是Karel就到上一行,然后接着铺满一行,如此反复。我们现在考虑如何完成这个任务了。我们可以看到在不同的行,Karel想往上走一步,行为是不一样:比如Karel完成铺满一行的时候,如果Karel现在面朝东方的,它要上去,是需要完成以下几个命令:

1)        向左转。

2)        往前走一步。

3)        向左转,调整方向。

如果Karel现在面朝西方,则与这个命令相反。

如果我们有一个命令能判断Karel现在是面朝东方,还是西方,就能完美解决这个任务。这个世界不仅仅让你感觉到恶意,但是更多的时候,是更多的善意,正如你所愿,系统真的提供了完整的命令:

facingEast() :Karel是不是面朝东方的;

facingWest():Karel是不是面朝西方的;

当然也有北方和南方:

facingNorth():Karel是不是面朝北方的;

facingSouth():Karel是不是面朝南方的;

 

我们判断是不是Karel现在面朝东方还是西方,便是很容易了。

if(Karel是不是面朝东方的){

    是的话,做什么?
}
else{

    不是的话,做什么?
}

 

具体的代码如下:

public void run( ){

		for ( int k = 0 ; k <6 ; k++){
		      createBeeperLine();
		      moveToNextLine ();
		}

	}



	public void moveToNextLine (){

		if (facingEast()){
			turnLeft ();
			move();
		    turnLeft ();
		}else{
			turnRight();
		    move();
            turnRight();
        }
	}

 

是不是很清楚了?是不是又感觉大功告成了。我们测试一下,是不是再次发现世界的恶意,大家发现问题在哪?是不是又没有停下来?我们如何去解决这个问题。留给大家自己去做一做。

 

我们在这里是不是经常遇到大小差一的错误(off by one)就是指某个变量的最大值和最小值可能会和正常值差1,或者循环多执行一次/少执行一次。一般在临界情况时发生。我们在边界条件要格外注意,在我们这里经常遇到多走一步撞墙,少做一次,方块放不全的问题。比如小学时候经典问题如果你要建造一个100米长的栅栏,其栅栏柱间隔为10米,那么你需要多少根栅栏柱呢?11根或9根都是正确答案,而10根却是不正确的答案。一只青蛙掉在井底.井深10米,青蛙白天爬三米,晚上向下掉2米,问青蛙几天爬上来?这样经典问题,我们常常在最后边界条件出了问题。

关于Off by one 有个既浪漫又悲伤的笑话:

有一个男孩他爱上了一个女孩,女孩知道后接见了他,

男孩见到女孩之后更爱她了,就对女孩说出了心里话。

女孩就说你在我的窗下等上100天我就会爱上你!

男孩就高兴的答应了,从那天开始,男孩每天都在窗下等着就着样一天又一天,

一个星期又一个星期的过去了,男孩不论刮风还是下雨都痴痴的等在窗下,

但是窗户从来都没打开过,但是女孩吗她当然会信守诺言的!

可是到了女孩数到99天男孩再一次看着窗户,

傍晚他回身了,微笑着走了没有回头,从此在没人看到过他。

为什么?

因为女孩是程序员,她从零开始计数的。

 

我们这里是不是循环多了一次,最后结束到了第6行时候,铺完最后一行的时候是不是应该停下来,我们如何去修改这个问题?大家想一想就有办法了。

我们基本上能完成铺满整个屏幕的问题,但是现在又有了新的问题,我们现在做的都是已经知道具体的行数或者列数的。我们现在要适用于所有的地图该如何做?当我们不知道地图大小的时候,机器人是否有办法顺利完成任务。这个任务留在下一次课再探讨。

 

今天我们总结一下机器人三大定律:

1)        顺序

2)        循环

3)        选择

在程序的世界,只有这三种情况,暂时没见到第四种情况。就像牛顿运动三大定律一样,所有宏观物体运动,大到星球,小到分子,电子运动,无比符合三大定律。

所有的代码要么是顺序,要么循环,要么选择。没有第四种选择。

 

欢迎收看我们的下一节课:机器人三大定律(下),我们继续使用这三种情况完成更复杂的任务,比如适应所有的地图。

 

 

 

 

转载于:https://my.oschina.net/u/2398237/blog/866553

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值