python中的for循环问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014633283/article/details/44621107

一、问题的引入——分赌本问题

前几天看到一个“分赌本”问题,问题描述如下:

(1)赌徒甲、乙两赌徒赌技相同,每局无平局,他们约定,谁先赢得十局则得到全部赌本。
(2)当前甲赢了5局,乙赢了2局,因故终止赌博,请问他们按照怎样的比例分赌本?

教课老师希望通过仿真模拟的方式,计算出两个赌徒若继续比赛各自胜出的概率,然后根据各自的获胜概率分赌本(很显然,该老师是一位计算机专业老师。。)

老师在课上给出的Python代码主体如下:

<span style="font-size:14px;">#老师给的Python主体代码</span>
<span style="font-size:14px;">#-*- coding:utf-8 -*-
import sys
import random

'''
n: the total number of games to win
n1: the number of games player1 won
n2: the number of games player2 won
'''
def Bookie1(n, n1, n2):
	for i in range(n-min(n1,n2)):#the number of games needed to end
		D = random.randint(1,2)
		if D==1:
			n1+=1
		elif D==2:
			n2+=1
		if n1==n:#player1 wins
			return 1
		if n2==n:#player2 wins
			return 2

def simulate1():
	n = 10000
	win1 = 0
	win2 = 0
	for i in range(n):#simulate 10000 games
		#simulate game whose total number is 10, and player1 won 5, player2 won 2
		result = Bookie1(10,5,2)
		if result==1:
			win1+=1
		else:
			win2+=1
	print 'player1 wins: ' + str(float(win1)/float(n))
	print 'player2 wins: ' + str(float(win2)/float(n))

if __name__ == '__main__':
	simulate1()
</span>

乍一看,该代码是不存在问题的。尤其是第11行“for i in range(n-min(n1,n2))”,作者希望动态地改变for循环的range序列,我的第一反应是觉得老师的这种思路很巧妙。但模拟结果(如图1)却总是不如人意,player2的获胜概率总是大于player1的。这与我们的直觉相反,因为player1已获胜5次,要高于player2的2次,理应player1获胜的概率更大。。


图1

在一段相当长的检查之后(代码检查经验不足,心塞。。),知道问题就是出在那行思路巧妙的代码上。因为貌似Bookie1函数在很多次运行情况下返回值为空(既没有返回1,也没有返回2),而在simulate()函数中进行调用时,else分支默认在无返回值时win2+=1。。所以,才会导致player2的获胜概率要高于player1的。其实若我们在else分支时,增加上判断条件elif result==2,是很容易就发现该错误的。因为此时两个赌徒的模拟获胜概率和不为1,也就是说出现了某些局的比赛没有获胜者的情况,从而可以推断出是Bookie1函数中返回值为空。

该老师的学生修改代码如下,经验证,代码以99.99999%的可能性是正确的。主要是修改了第11行代码为for i in range(2*n-n1-n2-1),数学上很容易验证,在2*n-n1-n2-1次比赛下必然可以分出胜负,从而函数返回1或2。其运行结果如图2所示:

#修改过后的代码
#-*- coding:utf-8 -*-
import sys
import random

'''
n: the total number of games to win
n1: the number of games player1 won
n2: the number of games player2 won
'''
def Bookie1(n, n1, n2):
	for i in range(2*n-n1-n2-1):#the number of games needed to end
		D = random.randint(1,2)
		if D==1:
			n1+=1
		elif D==2:
			n2+=1
		if n1==n:#player1 wins
			return 1
		if n2==n:#player2 wins
			return 2

def simulate1():
	n = 10000
	win1 = 0
	win2 = 0
	for i in range(n):#simulate 10000 games
		#simulate game whose total number is 10, and player1 won 5, player2 won 2
		result = Bookie1(10,5,2)
		if result==1:
			win1+=1
		elif result==2:
			win2+=1
	print 'player1 wins: ' + str(float(win1)/float(n))
	print 'player2 wins: ' + str(float(win2)/float(n))

if __name__ == '__main__':
	simulate1()

图2

图2

二、胡思乱想——新的问题出现
问题是解决了。但我总是忘不掉那一行代码“for i in range(n-min(n1,n2))”,忘不掉该老师巧妙的思路(尽管该老师的某位学生对老师的巧妙思路予以否认),忘不掉该老师。。。。

我的想法就是,能否在for循环配合range的情况下,动态地改变for循环的序列值。这有两点好处,一是可以及时地跳出该循环,二是可以不间断地开始下一循环。说的详细一点:

第一点好处的情景是:若当前for循环的range序列值有10000项,如果可以动态地改变range序列值的话,也许在满足某个条件下,可以提前跳出for循环。上面的程序其实已经提供了一个方法,就是通过“判断语句”加上“return”来实现。但我考虑的是是否可以有新的方式。

第二点好处的情景是:可能当前for循环结束后,仍没有我们需要的结果出现,我们需要再一次进行for循环。该想法其实可以通过多层for循环实现,但这里我也希望可以采用别的方式。


三、一些验证——关于Python的for循环执行机制

我们来看这几个验证代码:

#验证代码1
a = 5
for i in range(a):
	print "i: ",i
	print "a: ",a
验证代码1的执行结果如图3:

图3

#验证代码2

a = 5
for i in range(a):
	a+=1
	print "i: ",i
	print "a: ",a
验证代码2的执行结果如图4:


图4

对比验证代码1和2以及相应的结果,我们发现,即使range()中传入的是变量值a,即使a的值在循环体中发生了改变(如验证代码2所示),但在for循环执行过程中只在最初时生成一次序列值,即0~4。。。也就是说,Python的执行机制中,序列值只在循环开始之前生成一次,不提供动态改变的功能。


四、垂死挣扎——一些替代的方法

那是一份执念,想保留最初的第11行代码。但Python中for循环的执行机制,限定了序列值不能动态地改变。但我扔向垂死挣扎一番,并找到了一些替代的方法,如下面的替代代码1:

</pre><pre name="code" class="python"><pre name="code" class="python">#替代代码1
#-*- coding:utf-8 -*-
import sys
import random

'''
n: the total number of games to win
n1: the number of games player1 won
n2: the number of games player2 won
'''
def Bookie1(n, n1, n2):
	for i in range(n-min(n1,n2)):#the number of games needed to end
		D = random.randint(1,2)
		if D==1:
			n1+=1
		elif D==2:
			n2+=1
		if n1==n:#player1 wins
			return 1
		if n2==n:#player2 wins
			return 2
	else:
		return Bookie1(n, n1, n2)#由于不能动态改变i的序列值,但若仍没有决出胜负
					#我们可以递归调用Bookie1函数,从而变相地改变序列值
					#这里没有使用嵌套的for循环,而实现了“连续开始下一循环”
					#的目的。此外,这里也很难通过嵌套的for循环准确地控制
					#外层循环的次数。。这里用到了for...else语法,其中“else”
					#也可以注释掉,那么每执行一次循环体中的内容,都会重新
					#生成range()序列,相比前者更加动态,但效率较低

def simulate1():
	n = 10000
	win1 = 0
	win2 = 0
	for i in range(n):#simulate 10000 games
		#simulate game whose total number is 10, and player1 won 5, player2 won 2
		result = Bookie1(10,5,2)
		if result==1:
			win1+=1
		elif result==2:
			win2+=1
	print 'player1 wins: ' + str(float(win1)/float(n))
	print 'player2 wins: ' + str(float(win2)/float(n))

if __name__ == '__main__':
	simulate1()
 我们在第22行~第29行,调用了Bookie1函数本身。这里分为两种情况,使用else和不使用else: 
 

1.使用else:该情况下,当一次完整的for循环执行结束(即range序列中的值都执行完毕)后无法分出胜负时,执行else下的语句,即递归调用函数本身。此时n1和n2的值已发生改变,从而可以生成新的range序列,实现了“二、胡思乱想——新的问题出现”中的第二点好处:多次生成range()序列而不借助于for的嵌套。正如代码注释中所说,该方法相比于for循环的嵌套,可以实现对外部循环次数以及内部range序列生成的精确控制。

2.不使用else:该种情况下,每当循环体执行一次(对比上面的完整for循环执行一次,完整for循环执行一次包含循环体的多次执行),便立即递归调用函数本身,从而在新的for循环中生成新的range序列,这可以更加实时地动态改变range序列值。但由于频繁地产生新的range序列值,会产生相当的开销。但该方法在range序列值初始值较大,且产生的新序列值可能很大程度上变小时,效率会比较高。。这也就实现了二、胡思乱想——新的问题出现”中的第一点好处。


五、小结
1.说了这么多,可能显得有点多此一举了。确实,只要将for循环中生成的range序列值次数增大,就可以保证赌徒在每次比赛中一定可以决出胜负,正如“修改过后的代码”中所示。但这篇博客目的不在于解决该问题,而在于对该问题引申出的一些思考:能否动态地改变for循环中的range系列值,以使代码更加智能,并且可能地提升效率。文中提出了一种编程模式(或者只能说是一种经验吧),可以实现以上需求,如下图所示:


2.由于笔者语言表达能力有限,对于编程需求以及相应解决方法的介绍并不那么易懂。如果不想“动态改变range序列值”,那么有几点编程的建议如下:

1)不要在range()中使用循环体中会变化的变量值,那会如文中开头所写的那样,很容易出现问题却检查不出来。可以在for循环开始之前定义一个新的变量并赋值,将该变量传给range()函数。就以文中开始的例子来说,实现代码如下,其中count在循环体中是不发生变化的:

#建议1的代码

……
count=2*n-n1-n2-1
for i in range(count)
……

2)不要轻易相信紧接着if的else语句。尤其当代码出现问题时,试着制定else语句中的确切条件,即替换成elif,也许就可以帮我们找到代码的问题所在




猜你喜欢

转载自blog.csdn.net/u014633283/article/details/44621107