最近开心上狂算24点,于是贝壳搞了一个24点计算程序,并且说明原理。
我们将24点问题一般化,变成一个搜索问题。假定从一个初始表开始,里面有一些原子。我们定义一个操作,结合。每次操作任意从中选择出两个(或者以上)原子,使用算符连接,成为一个新的原子。那么,一般来说,24点就是计算所有可能的路径,从初始表开始,持续进行结合,直到只剩下一个原子,并且对这个原子求值得24。
有人可能在算符优先级上想不开,其实不用考虑这个问题,每次求值的时候,按照求值顺序优先就可以。你想到的另外一种优先级可能,会在穷举的时候被列举出来算掉,不用担心遗漏。
同时,算子必须是两目以上算子,因为单目算子可以持续作用于同一个对象,因此原子表中的原子个数并不严格单调减少,造成无法肯定路径收敛于有限步骤上。并且,如果允许单目算子,那么我只需要求导和阶乘就可以对任何数字求24点。
((a')!+(b')!+(c')!+(d')!)!=24
因此,单目算符是没有意义的。
另外,注意算符分可交换和非可交换的。例如:a+b=b+a,但是a-b!=b-a。如果不注意这点,倒是不会漏算,但是会造成搜索空间增大,并且有重复结果。
以下是24点计算程序,python版本的。有兴趣的朋友可以用scheme重写,相信会更简洁有效。回头会用django封装一下,做成网页给大家玩玩。
#!/usr/bin/python
import sys
symbol_list = [
("%s+%s", True), ("%s-%s", False),
("%s*%s", True), ("%s/%s", False), ("%s**%s", False),
# ("min (%s,%s)", True), ("max (%s,%s)", True),
];
def diff_seq (length):
for i in range (0, length):
for j in range (i + 1, length):
yield (i, j);
def get_less_state (input_state):
for i, j in diff_seq (len (input_state)):
temp = input_state[:];
del temp[j];
del temp[i];
for s in symbol_list:
rslt = s[0] % (input_state[i], input_state[j]);
rslt = "(%s)" % rslt;
temp.append (rslt);
yield temp;
temp.remove (rslt);
if s[1]:
continue;
rslt = s[0] % (input_state[j], input_state[i]);
rslt = "(%s)" % rslt;
temp.append (rslt);
yield temp;
temp.remove (rslt);
def do_node (input_state, output):
for i in get_less_state (input_state):
if len (i) > 1:
do_node (i, output);
continue;
try:
rslt = eval (i[0]);
except:
continue;
if rslt == 24.0:
output.add (i[0].replace (".0", ""));
if __name__ == "__main__":
rslt = [];
for i in sys.argv[1:]:
rslt.append (float (i));
output = set ([]);
do_node (rslt, output);
for i in list (output):
print "%s=24" % i;
2008年10月11日星期六
分词算法的具体实践
说到分词算法,可能很多人都很陌生,然而说起百度,google,很多人却是耳熟能详。google,百度在搜索的时候,输入关键词后瞬间就可以得到结果,如果用通用数据库是无法做到的。实行这个加速的关键就是分词算法。例如"项羽是萝莉控"这句句子,我们一般搜索都是搜索项羽,或者萝莉控,萝莉。你见过有去搜"是萝"这个关键字的么?因此系统通过分词,将句子分解为"项羽/是/萝莉控",去处单字常见词"是"(如果要索引"是",可以想像有多少文章没有"是"的),我们就得到了项羽和萝莉控两个词。再通过反向关联,建立项羽,萝莉控指向文章的连接,就可以完成瞬间的搜索了(具体原理不说了,只要有一定数据库基础的人都应当能想明白原理)。并且通过关联性,某种程度上也可以提供"是萝"的搜索(带"是"的词,带"萝"的词,相关度最高)。
那么,如何来计算分词呢?方法很多,大家可以在网络上搜索下,贝壳就不赘述了。贝壳现在要说的是这次贝壳主要试验的方向,基于词典的机械分词中的最大分词算法。
机械分词算法是当今的主流,关键原因在于速度问题。虽然正确的分词很有价值,然而如果速度太慢,一样没有什么用处。机械分词一般可以保证 98%-99.5%以上的正确率,同时提供极高的分词速度。而机械分词一般来说,都是基于词典的。主要有正向分词算法,逆向分词算法,最大匹配分词算法。其中最大匹配分词算法具备最高的灵活性,只要你能评价一个切分的优秀程度,算法能把所有可能算出来让你评价。然而大家可以想像,这个是非常耗费CPU的。贝壳在这个基础上,做了一个具体的实现和细化加速。并且有准备做为一个开源项目来长期运作(只要有人有意向接手合作)。
首先我们先说贝壳这个算法的评价原则。贝壳认为,评价原则应当有以下几点。同时也必须要说明,以下原则是无法正确评价所有情况的。不过以下原则在原则正确的基础上比较便于优化。一、无法分析的词最少(这是全局最大匹配的理论核心)。二、匹配出的原子最少(这是保证分词优秀性的指标)。三、匹配出原子的出现概率和最高(这是纯粹没有办法了从概率上提高匹配正确的可能)。
当我们分析一句话的时候,我们可以想像,这句话应当是正常的,可被理解的。换句话说,句子中应当都是有意义的词。那么,在匹配后无法理解的词是什么呢?一种是匹配错误,一种是新单词,一种是单字成词和无意义助词。单字成词的例子有上面的"是",我们可以通过一个比较小的词典去除。那么,假定词典够大的情况下,无法理解和分析的词越少的组合越正确。而同样一句话,匹配出的原子越少,在搜索的时候效率越高。因此我们有规定了原子最少原则。至于最后一个,在无法分析词一致,原子个数一致的情况下,我们只能通过出现概率来猜测可能性。
然后,现在让我们分析一下分词的特点,并且做一定的优化。首先就从最著名的例子,"长春/市长/春节/致辞"开始。
>>>长春市长春节致辞
首先,匹配算法一定要先搜索到一个出现的词,有词才有匹配优化问题。没有词的话,你试试看分词"嗡嘛呢呗咪吽"。根本无法可分。因此首先我们要计算出一个出现的单词。贝壳是从正向开始计算的(主要是因为词典的加速方法是头索引的)。
>>>*长春*{市长春节致辞}
>>>*长春市*{长春节致辞}
好的,我们匹配到了两个,不过这就是全部可能么?不是,否则就变成了正向最大搜索。你可以看看"有意见分歧"。如果从头一个匹配到开始计算,无论如何都是"有意/见/分歧",而事实是"有/意见/分歧"。因此我们还有一种可能,在头一个匹配到的位置,其实并不匹配。不匹配多长呢?最大长度不会超过最短的匹配词。为什么?我们来看下面一个例子。
>>>*长春*{市长春节致辞}
>>>*长/春/(这两个字不是词,而是两个无法理解的字){市长春节致辞}
很明显,后一种分法违背了我们的第一原则,无法分析的词最少。无论后面怎么计算,其最优结果是相同的。在后续结果相同的情况下,头一次匹配到词后,所有可能的跳空(搜索可能的不匹配)最大长度严格小于最短匹配词的长度。
那么是否所有跳空都要搜索呢?也不,我们可以继续剪枝。对于情况"有意见分歧"来说,这个路径是必须搜索的。但是对于我们的例子来说,是无需搜索的。为什么呢?我们看以下计算。
>>>*长/{春市长春节致辞}(下一个匹配是什么?总不会是春市吧,所以应当是"市长")
>>>*长/春/市长*{春节致辞}
>>>*长春*{市长春节致辞}
大家可以看到,其实这个路径是无需计算的。那什么情况下需要计算呢?
一旦跳空,其跳空后寻找到的下个词的位置必须严格小于最短词的词尾位置。否则就没有搜索价值。具体可以看以下示例。
XXXXXXXNNNNNNNNNNN(X是词,N是无关紧要的)
SSSSSSSXXNNNNNNNNN(S是跳空或者跳空后形成的无法理解字,X是词,在这种情况下,无论后面怎么评价,都不影响该匹配被剔除)
OK,我们回到例子,刚刚我们说了,有"长"的匹配。但是通过刚刚的剪枝,又被剪了出去。我们下面分别计算两个情况。
>>>市长春节致辞
>>>*市/{长春节致辞}
>>>*市长*{春节致辞}
>>>长春节致辞
好,我们先不计算下去了。通过上面的计算,我们发现,在计算过程中经常需要计算同一内容的结果。我们可以想一下,同样的分词,同样的算法,出现的应当是同样的结果。就是说,分词函数是状态无关的算法。通过分解一个单词,得到一个最优结果。那么,我们对于同样的数据,何必需要计算两次呢?贝壳上文中提到过记忆函数,这次就用上了。根据贝壳的试验结果,如果记忆全部词的分解结果,会造成大量的记忆-释放,而内容基本没有用到,造成效率下降。如果只记忆长词的分解结果,往往又会因为太长,大多数句子无法达到长度而根本没用。这中间有个平衡值,具体多少贝壳就不说了。我们可以按照上文的方法计算以下两个过程,得到结果。大家可以自行验证。
>>>春节致辞
>>>*春节*致辞*
>>>长春节致辞
>>>*长/春节*致辞*
>>>*长春*节/致辞*
结合上面的过程,我们推算得到结果。
>>>*长春*{市长春节致辞}
>>>*长春*市长*春节*致辞*
>>>*长春市*{长春节致辞}
>>>*长春市*长/春节*致辞*
>>>*长春市*长春*节/致辞*
按照上面的评价原则,我们得到了正确的结果。
大家可以看看其他例子,这里着重说一下"有意见分歧"。
>>>有意见分歧
>>>*有*意见*分歧*
>>>*有意*见/分歧*
注意,有是单字成词,见可不是。如果见单字成词,做看见讲,那这句话就彻底成歧义句了。可以理解为,有意的要看到(或者让其表现出)分歧。这一般是古文语法。由此也可以看出上述原则在理解古文的时候往往会出现问题。同时还要指出的是,在匹配"长春市长春药店"的时候,会出现以下结果。
>>>长春市长春药店
>>>*长春*市长*春药店*
>>>*长春市*长春*药店*
两者的无法理解词都没有,切分数一致,最后硬是因为春药店出现概率低而被筛掉。可见系统有的时候是依赖概率和人品在工作的。
经过上面的原则和算法,贝壳实现了一个python的分词程序,1000行代码,原型系统。90W条词情况下,在AMD MK36上(2G主频)分词效率66K/s上下,具体看分词的选项(例如顺序分词就比较节约资源,分词排除重复就比较慢,启用多线程后在单CPU 机器上更慢),内存使用114M。使用C++写的核心词典后,90W条词的情况下分词速度80K/s,比python的核心词典快了20%,内存70M,节约内存40%。不过可惜,这个核心词典是公司产权,贝壳无权公布。并且贝壳做了一些工作,准备使用分词程序来生成分词词表。这个么贝壳就不准备讲了。前面讲的内容贝壳准备放到试验型站点 http://shell909090.3322.org/split_word/split_show/上面去,08年内有效。有兴趣联系我的可以发 mail给我,shell909090@gmail.com,欢迎大家试验并提出意见。
那么,如何来计算分词呢?方法很多,大家可以在网络上搜索下,贝壳就不赘述了。贝壳现在要说的是这次贝壳主要试验的方向,基于词典的机械分词中的最大分词算法。
机械分词算法是当今的主流,关键原因在于速度问题。虽然正确的分词很有价值,然而如果速度太慢,一样没有什么用处。机械分词一般可以保证 98%-99.5%以上的正确率,同时提供极高的分词速度。而机械分词一般来说,都是基于词典的。主要有正向分词算法,逆向分词算法,最大匹配分词算法。其中最大匹配分词算法具备最高的灵活性,只要你能评价一个切分的优秀程度,算法能把所有可能算出来让你评价。然而大家可以想像,这个是非常耗费CPU的。贝壳在这个基础上,做了一个具体的实现和细化加速。并且有准备做为一个开源项目来长期运作(只要有人有意向接手合作)。
首先我们先说贝壳这个算法的评价原则。贝壳认为,评价原则应当有以下几点。同时也必须要说明,以下原则是无法正确评价所有情况的。不过以下原则在原则正确的基础上比较便于优化。一、无法分析的词最少(这是全局最大匹配的理论核心)。二、匹配出的原子最少(这是保证分词优秀性的指标)。三、匹配出原子的出现概率和最高(这是纯粹没有办法了从概率上提高匹配正确的可能)。
当我们分析一句话的时候,我们可以想像,这句话应当是正常的,可被理解的。换句话说,句子中应当都是有意义的词。那么,在匹配后无法理解的词是什么呢?一种是匹配错误,一种是新单词,一种是单字成词和无意义助词。单字成词的例子有上面的"是",我们可以通过一个比较小的词典去除。那么,假定词典够大的情况下,无法理解和分析的词越少的组合越正确。而同样一句话,匹配出的原子越少,在搜索的时候效率越高。因此我们有规定了原子最少原则。至于最后一个,在无法分析词一致,原子个数一致的情况下,我们只能通过出现概率来猜测可能性。
然后,现在让我们分析一下分词的特点,并且做一定的优化。首先就从最著名的例子,"长春/市长/春节/致辞"开始。
>>>长春市长春节致辞
首先,匹配算法一定要先搜索到一个出现的词,有词才有匹配优化问题。没有词的话,你试试看分词"嗡嘛呢呗咪吽"。根本无法可分。因此首先我们要计算出一个出现的单词。贝壳是从正向开始计算的(主要是因为词典的加速方法是头索引的)。
>>>*长春*{市长春节致辞}
>>>*长春市*{长春节致辞}
好的,我们匹配到了两个,不过这就是全部可能么?不是,否则就变成了正向最大搜索。你可以看看"有意见分歧"。如果从头一个匹配到开始计算,无论如何都是"有意/见/分歧",而事实是"有/意见/分歧"。因此我们还有一种可能,在头一个匹配到的位置,其实并不匹配。不匹配多长呢?最大长度不会超过最短的匹配词。为什么?我们来看下面一个例子。
>>>*长春*{市长春节致辞}
>>>*长/春/(这两个字不是词,而是两个无法理解的字){市长春节致辞}
很明显,后一种分法违背了我们的第一原则,无法分析的词最少。无论后面怎么计算,其最优结果是相同的。在后续结果相同的情况下,头一次匹配到词后,所有可能的跳空(搜索可能的不匹配)最大长度严格小于最短匹配词的长度。
那么是否所有跳空都要搜索呢?也不,我们可以继续剪枝。对于情况"有意见分歧"来说,这个路径是必须搜索的。但是对于我们的例子来说,是无需搜索的。为什么呢?我们看以下计算。
>>>*长/{春市长春节致辞}(下一个匹配是什么?总不会是春市吧,所以应当是"市长")
>>>*长/春/市长*{春节致辞}
>>>*长春*{市长春节致辞}
大家可以看到,其实这个路径是无需计算的。那什么情况下需要计算呢?
一旦跳空,其跳空后寻找到的下个词的位置必须严格小于最短词的词尾位置。否则就没有搜索价值。具体可以看以下示例。
XXXXXXXNNNNNNNNNNN(X是词,N是无关紧要的)
SSSSSSSXXNNNNNNNNN(S是跳空或者跳空后形成的无法理解字,X是词,在这种情况下,无论后面怎么评价,都不影响该匹配被剔除)
OK,我们回到例子,刚刚我们说了,有"长"的匹配。但是通过刚刚的剪枝,又被剪了出去。我们下面分别计算两个情况。
>>>市长春节致辞
>>>*市/{长春节致辞}
>>>*市长*{春节致辞}
>>>长春节致辞
好,我们先不计算下去了。通过上面的计算,我们发现,在计算过程中经常需要计算同一内容的结果。我们可以想一下,同样的分词,同样的算法,出现的应当是同样的结果。就是说,分词函数是状态无关的算法。通过分解一个单词,得到一个最优结果。那么,我们对于同样的数据,何必需要计算两次呢?贝壳上文中提到过记忆函数,这次就用上了。根据贝壳的试验结果,如果记忆全部词的分解结果,会造成大量的记忆-释放,而内容基本没有用到,造成效率下降。如果只记忆长词的分解结果,往往又会因为太长,大多数句子无法达到长度而根本没用。这中间有个平衡值,具体多少贝壳就不说了。我们可以按照上文的方法计算以下两个过程,得到结果。大家可以自行验证。
>>>春节致辞
>>>*春节*致辞*
>>>长春节致辞
>>>*长/春节*致辞*
>>>*长春*节/致辞*
结合上面的过程,我们推算得到结果。
>>>*长春*{市长春节致辞}
>>>*长春*市长*春节*致辞*
>>>*长春市*{长春节致辞}
>>>*长春市*长/春节*致辞*
>>>*长春市*长春*节/致辞*
按照上面的评价原则,我们得到了正确的结果。
大家可以看看其他例子,这里着重说一下"有意见分歧"。
>>>有意见分歧
>>>*有*意见*分歧*
>>>*有意*见/分歧*
注意,有是单字成词,见可不是。如果见单字成词,做看见讲,那这句话就彻底成歧义句了。可以理解为,有意的要看到(或者让其表现出)分歧。这一般是古文语法。由此也可以看出上述原则在理解古文的时候往往会出现问题。同时还要指出的是,在匹配"长春市长春药店"的时候,会出现以下结果。
>>>长春市长春药店
>>>*长春*市长*春药店*
>>>*长春市*长春*药店*
两者的无法理解词都没有,切分数一致,最后硬是因为春药店出现概率低而被筛掉。可见系统有的时候是依赖概率和人品在工作的。
经过上面的原则和算法,贝壳实现了一个python的分词程序,1000行代码,原型系统。90W条词情况下,在AMD MK36上(2G主频)分词效率66K/s上下,具体看分词的选项(例如顺序分词就比较节约资源,分词排除重复就比较慢,启用多线程后在单CPU 机器上更慢),内存使用114M。使用C++写的核心词典后,90W条词的情况下分词速度80K/s,比python的核心词典快了20%,内存70M,节约内存40%。不过可惜,这个核心词典是公司产权,贝壳无权公布。并且贝壳做了一些工作,准备使用分词程序来生成分词词表。这个么贝壳就不准备讲了。前面讲的内容贝壳准备放到试验型站点 http://shell909090.3322.org/split_word/split_show/上面去,08年内有效。有兴趣联系我的可以发 mail给我,shell909090@gmail.com,欢迎大家试验并提出意见。
2008年10月6日星期一
苏博婚礼回来暨python2.6发布
这次10.1算是个大日子,因为我们可爱的苏博终于和他美丽的新娘结婚了。据说两个人相识10年拍拖7年,找的高中班主任做证婚人。实在有点为难人家,到底说高中就好上了呢?还是高中没好上?不过总而言之,他们总算结婚了。具体苏博是怎么被我们蹂躏的,以及婚礼的起因经过什么的就不写了,毕竟我不是新闻记者。这次就写一些有趣的事情和感想。
首先是去震泽的车子,因为10.1的关系,并不怎么好去。不过坐在车上晃晃悠悠两个小时,看旁边的河跟路一起走,感觉还是很不错的。江南不愧是水乡,有条河就在我们的路旁跟了10多分钟,还有条船跟我们并排跑。震泽古镇也很灵的,宝塔街古香古色,保证没有现代元素,除了大头发现的几个公共厕所外。建议大家有空可以去看看,苏博的家乡。
而后是新郎和新娘的一个让我比较震撼的问题。婚礼上,主持人问新娘的大学同学,是否在校园里面经常看到新郎。人家说,一直以为苏於良是南大学生。我吓一跳,南大啊,我一直以为王苏瑾在上海念大学。由此我得到一个结论,远距离恋爱是否会失败,和双方爱对方的程度无关,而和双方把爱付诸行动的程度有关。其实不光远距离恋爱,婚姻也是一样。认识我的人都知道我的两个总结。夫妻双方性格相近或相反,价值观一致。今天看来还要加一条,愿意将爱付诸行动。
然后是婚礼前一天,阿丁同学打过来跟我哭诉她和她男友的情况。实话说,虽然被哭诉半天,但是我还是搞不清楚她和她男友的状态,总之是非常复杂一团浆糊。因为隐私关系,我不打算说她和她男友的具体状况。不过大致就是她很喜欢他男友,喜欢到没有自我没有尊严。他男友呢,则是有点——不知道怎么说。说有问题吧,说不出来,说没有问题吧,情况确实——不怎么好。而且她本人处理事情上也不是没有问题,我觉得这个应当叫孽缘吧。不过无论如何,我的建议是——分手。
然后我就建议阿丁同学到震泽来玩一天,反正黄禹同学正好没来。然后她跑来玩了一天,回去和我说了一句雷晕人的话。我彻底无语了——
无论如何,那是她的家事。
再后面就是苏州到上海的车,同样也不怎么好弄。我问今天又没有去上海的车,最好是动车。回答说有,动车。我说来两张票(帮人代买一张),售票员说,晚上11点半的哦~~
我彻底无语。
后面一个朋友则更悲惨。他问,今天到南京的车票还有么?没了。明天的呢?也没了。后天的呢?我们只发售今明两天的~~
最后我们坐大巴回来的。
最后的最后,说一下,python2.6发布了,虽然我不打算用。比以前在构架上有了不少进步,不过很多东西暂时没有这么快迁移过去。我打算等3.0出了后直接用3.0,反正程序是一样写的。
首先是去震泽的车子,因为10.1的关系,并不怎么好去。不过坐在车上晃晃悠悠两个小时,看旁边的河跟路一起走,感觉还是很不错的。江南不愧是水乡,有条河就在我们的路旁跟了10多分钟,还有条船跟我们并排跑。震泽古镇也很灵的,宝塔街古香古色,保证没有现代元素,除了大头发现的几个公共厕所外。建议大家有空可以去看看,苏博的家乡。
而后是新郎和新娘的一个让我比较震撼的问题。婚礼上,主持人问新娘的大学同学,是否在校园里面经常看到新郎。人家说,一直以为苏於良是南大学生。我吓一跳,南大啊,我一直以为王苏瑾在上海念大学。由此我得到一个结论,远距离恋爱是否会失败,和双方爱对方的程度无关,而和双方把爱付诸行动的程度有关。其实不光远距离恋爱,婚姻也是一样。认识我的人都知道我的两个总结。夫妻双方性格相近或相反,价值观一致。今天看来还要加一条,愿意将爱付诸行动。
然后是婚礼前一天,阿丁同学打过来跟我哭诉她和她男友的情况。实话说,虽然被哭诉半天,但是我还是搞不清楚她和她男友的状态,总之是非常复杂一团浆糊。因为隐私关系,我不打算说她和她男友的具体状况。不过大致就是她很喜欢他男友,喜欢到没有自我没有尊严。他男友呢,则是有点——不知道怎么说。说有问题吧,说不出来,说没有问题吧,情况确实——不怎么好。而且她本人处理事情上也不是没有问题,我觉得这个应当叫孽缘吧。不过无论如何,我的建议是——分手。
然后我就建议阿丁同学到震泽来玩一天,反正黄禹同学正好没来。然后她跑来玩了一天,回去和我说了一句雷晕人的话。我彻底无语了——
无论如何,那是她的家事。
再后面就是苏州到上海的车,同样也不怎么好弄。我问今天又没有去上海的车,最好是动车。回答说有,动车。我说来两张票(帮人代买一张),售票员说,晚上11点半的哦~~
我彻底无语。
后面一个朋友则更悲惨。他问,今天到南京的车票还有么?没了。明天的呢?也没了。后天的呢?我们只发售今明两天的~~
最后我们坐大巴回来的。
最后的最后,说一下,python2.6发布了,虽然我不打算用。比以前在构架上有了不少进步,不过很多东西暂时没有这么快迁移过去。我打算等3.0出了后直接用3.0,反正程序是一样写的。
2008年9月6日星期六
C++下的Variant
所谓C++语言,是一种强类型语言。即是说,C++种的某个变量,在使用时类型是已经确定的。这个并不是设计者的喜好或者是偏心,而是C++中的变量都会被翻译成准确的内存地址和大小,如果类型不确定是不可能处理的。但是在事实中,我们经常要处理一种"变类型"。例如,我们可能需要解析表达式,这个时候我们可能用一个或者两个栈来解决这个问题。可栈里面塞的东西就精彩了,对象,函数,数据,都在里面。这时候,如果是python,我们可以直接用list,他是弱类型的。但是C++怎么办?
一般来说,我们会使用Variant类型来解决这个问题。这是C++面对对象机制和算子机制所派生出来的产物,能够让用户自行定义对象的行为。如果一个对象,可以表现的像这个又像那个,那不就解决问题了?因此在COM中就有一个variant。不过贝壳看过机制,是一堆东西的集合,非常的不美丽。今天贝壳又看到一个variant的实现,漂亮多了。
废话少说,上代码。
#include
using namespace std;
#include
using namespace boost;
int _tmain(int argc, _TCHAR* argv[])
{
any a;
a = 10;
printf ("%s: %d\n", a.type ().name (), any_cast(a));
a = 10.5;
printf ("%s: %f\n", a.type ().name (), any_cast(a));
a = string ("str");
printf ("%s: %s\n", a.type ().name (), any_cast(a).c_str ());
return 0;
}
当类型错误时,出现bad_cast exception。
一般来说,我们会使用Variant类型来解决这个问题。这是C++面对对象机制和算子机制所派生出来的产物,能够让用户自行定义对象的行为。如果一个对象,可以表现的像这个又像那个,那不就解决问题了?因此在COM中就有一个variant。不过贝壳看过机制,是一堆东西的集合,非常的不美丽。今天贝壳又看到一个variant的实现,漂亮多了。
废话少说,上代码。
#include
using namespace std;
#include
using namespace boost;
int _tmain(int argc, _TCHAR* argv[])
{
any a;
a = 10;
printf ("%s: %d\n", a.type ().name (), any_cast
a = 10.5;
printf ("%s: %f\n", a.type ().name (), any_cast
a = string ("str");
printf ("%s: %s\n", a.type ().name (), any_cast
return 0;
}
当类型错误时,出现bad_cast exception。
2008年8月27日星期三
python的性能问题
贝壳最近在一个朋友的网站上看到了关于SICP零钱兑换问题的python求解,使用了记忆机制,然后他给出了代码。然而他的代码计时上有点小问题,也没有用包装器(奇怪的是,有写),而且python的栈深度有限。因此贝壳做了几个修改的版本,需要测试下性能,下面就是关于性能的几个问题和过程。
本文详细论述了python语言下和C++语言下使用各种方法测试代码性能的方法,以及粗略的关于两种语言不同算法性能对比。
原始的python代码是这样的:
def change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
def cc((amount, kinds_of_coins)):
if amount == 0:
return 1
elif amount < 0 or kinds_of_coins == 0:
return 0
else:
return cc((amount, kinds_of_coins - 1)) \
+ cc((amount - first_denomination[kinds_of_coins], kinds_of_coins))
print "change_coins return %s" % cc((money, 5));
return ;
利用记忆原理包装后是这样的:
def memoiza(fun):
cache = {}
def proc ( *arg ):
if cache.has_key(arg):
return cache[arg]
else:
x = fun( *arg )
cache[arg] = x
return x
return proc
def decorator_change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
@memoiza
def cc(amount, kinds_of_coins):
if amount == 0:
return 1
elif amount < 0 or kinds_of_coins == 0:
return 0
else:
return cc(amount, kinds_of_coins - 1) \
+ cc(amount - first_denomination[kinds_of_coins], kinds_of_coins)
print "decorator_change_coins return %s" % cc(money, 5);
return ;
不记忆,利用栈模拟递归展开是这样的:
def native_change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
stack = [(money, 5)];
rslt = 0;
while len (stack) > 0:
param = stack.pop ();
if param[0] == 0:
rslt += 1;
continue;
elif param[0] < 0 or param[1] == 0:
continue;
else:
stack.append ((param[0], param[1] - 1));
stack.append ((param[0] - first_denomination[param[1]], param[1]));
continue;
print "native_change_coins return %s" % rslt;
return ;
贝壳主要需要测试上面三个代码的执行效率和瓶颈,所以贝壳用的主代码是这样的:
import time
import timeit
import profile
def test_func(f):
f (300);
if __name__ == "__main__":
t = timeit.Timer("test_func (change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
t = timeit.Timer("test_func (decorator_change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
t = timeit.Timer("test_func (native_change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
profile.run("test_func (change_coins)");
profile.run("test_func (decorator_change_coins)");
profile.run("test_func (native_change_coins)");
下面是部分结果:
change_coins return 9590
1.22809910198
decorator_change_coins return 9590
0.00217178440277
native_change_coins return 9590
2.69215193551
以上是时间测试结果,使用timeit模块来测试运行时间,重复5次,取最小值。具体原理可以看dive into python,详细请看上面的代码。从结果中我们可以看到,使用记忆技术后,性能提升了500多倍,这是符合规律的。然而使用了集合模拟栈之后,性能大幅下降。下面我们看看为什么。
change_coins return 9590
1292596 function calls (6 primitive calls) in 13.591 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.059 0.059 0.059 0.059 :0(setprofile)
1 0.000 0.000 13.533 13.533:1()
1 0.000 0.000 13.533 13.533 amount.py:102(test_func)
1292591/1 13.531 0.000 13.531 13.531 amount.py:11(cc)
1 0.001 0.001 13.533 13.533 amount.py:5(change_coins)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 13.591 13.591 profile:0(test_func (change_coins))
decorator_change_coins return 9590
2494 function calls (881 primitive calls) in 0.027 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
873 0.004 0.000 0.004 0.000 :0(has_key)
1 0.000 0.000 0.000 0.000 :0(setprofile)
1 0.000 0.000 0.027 0.027:1()
1 0.000 0.000 0.027 0.027 amount.py:102(test_func)
1 0.000 0.000 0.000 0.000 amount.py:51(memoiza)
873/1 0.013 0.000 0.026 0.026 amount.py:53(proc)
1 0.001 0.001 0.027 0.027 amount.py:62(decorator_change_coins)
742/1 0.009 0.000 0.026 0.026 amount.py:68(cc)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 0.027 0.027 profile:0(test_func (decorator_change_coins))
native_change_coins return 9590
3877778 function calls in 38.798 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1292590 5.824 0.000 5.824 0.000 :0(append)
1292592 5.960 0.000 5.960 0.000 :0(len)
1292591 6.076 0.000 6.076 0.000 :0(pop)
1 0.000 0.000 0.000 0.000 :0(setprofile)
1 0.000 0.000 38.798 38.798:1()
1 0.000 0.000 38.798 38.798 amount.py:102(test_func)
1 20.938 20.938 38.798 38.798 amount.py:80(native_change_coins)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 38.798 38.798 profile:0(test_func (native_change_coins))
以上是白盒分析结果,使用profile测试,主要分析函数的调用花费。具体可以参考http://www.sqlite.com.cn /MySqlite/11/480.Html。从上面的报表中,我们可以看出,最初的函数执行时间全消耗在了cc上。而记忆后,则是proc和cc基本对半,有的时候has_key测试也花点时间。这表示cc花费的时间大幅下降,记忆技术则花了比较多的时间。而模拟的呢?大部分时间都花在了 append,len,pop这三个函数上!这说明原始集合的效率严重制约了模拟效率。如果要提升性能的话,使用其他的集合吧。
另外贝壳又用C++写了一个,如下:
const int coin_map[] = {
1, 5, 10, 25, 50
};
const int coin_count = 5;
int cc (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
return cc (amount, kind_of_coins - 1) + cc (amount - coin_map[kind_of_coins - 1], kind_of_coins);
}
int dd (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
int rslt = 0;
for (int i = 0; i <= amount / coin_map[kind_of_coins - 1]; ++i)
rslt += dd (amount - i * coin_map[kind_of_coins - 1], kind_of_coins - 1);
return rslt;
}
class keys{
public:
int amount;
int kind_of_coins;
keys (int amount_p, int kind_of_coins_p):
amount(amount_p), kind_of_coins(kind_of_coins_p)
{}
bool operator == (const keys & k) const{
return (amount == k.amount && kind_of_coins == k.kind_of_coins);
}
bool operator < (const keys & k) const{
if (kind_of_coins == k.kind_of_coins)
return amount < k.amount;
return kind_of_coins < k.kind_of_coins;
}
};
map mCache;
int ee (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
keys k (amount, kind_of_coins);
map::iterator iter = mCache.find(k);
if (iter != mCache.end ())
return iter->second;
int rslt = 0;
for (int i = 0; i <= amount / coin_map[kind_of_coins - 1]; ++i)
rslt += dd (amount - i * coin_map[kind_of_coins - 1], kind_of_coins - 1);
mCache.insert(pair(k, rslt));
return rslt;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int loop_times = 300;
clock_t s = clock();
printf ("kind of coins: %d\n", cc (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
s = clock();
printf ("kind of coins: %d\n", dd (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
s = clock();
printf ("kind of coins: %d\n", ee (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
return 0;
}
注意到主函数中,使用的是clock来计量时间。如果C++下要做白盒性能测试就比较麻烦,需要用精确计时函数和宏。需要的可以单独和我联系。下面是部分计算结果,cc的和ee的,没有dd的。
300的计算结果
kind of coins: 9590
times:62
kind of coins: 9590
times:46
1000的计算结果
kind of coins: 801451
times:15953
kind of coins: 801451
times:11000
单位,ms。
原生的效率差异是20倍,用了缓存后性能只有略略上升?!反而是python比较快?
看来C++下的map效率也不高,要用hash_map才好。
倒是栈长度好很多,贝壳估计是131072次调用,大约是16384分。
本文详细论述了python语言下和C++语言下使用各种方法测试代码性能的方法,以及粗略的关于两种语言不同算法性能对比。
原始的python代码是这样的:
def change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
def cc((amount, kinds_of_coins)):
if amount == 0:
return 1
elif amount < 0 or kinds_of_coins == 0:
return 0
else:
return cc((amount, kinds_of_coins - 1)) \
+ cc((amount - first_denomination[kinds_of_coins], kinds_of_coins))
print "change_coins return %s" % cc((money, 5));
return ;
利用记忆原理包装后是这样的:
def memoiza(fun):
cache = {}
def proc ( *arg ):
if cache.has_key(arg):
return cache[arg]
else:
x = fun( *arg )
cache[arg] = x
return x
return proc
def decorator_change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
@memoiza
def cc(amount, kinds_of_coins):
if amount == 0:
return 1
elif amount < 0 or kinds_of_coins == 0:
return 0
else:
return cc(amount, kinds_of_coins - 1) \
+ cc(amount - first_denomination[kinds_of_coins], kinds_of_coins)
print "decorator_change_coins return %s" % cc(money, 5);
return ;
不记忆,利用栈模拟递归展开是这样的:
def native_change_coins(money):
first_denomination = {
1:1, 2:5,
3:10, 4:25,
5:50,
}
stack = [(money, 5)];
rslt = 0;
while len (stack) > 0:
param = stack.pop ();
if param[0] == 0:
rslt += 1;
continue;
elif param[0] < 0 or param[1] == 0:
continue;
else:
stack.append ((param[0], param[1] - 1));
stack.append ((param[0] - first_denomination[param[1]], param[1]));
continue;
print "native_change_coins return %s" % rslt;
return ;
贝壳主要需要测试上面三个代码的执行效率和瓶颈,所以贝壳用的主代码是这样的:
import time
import timeit
import profile
def test_func(f):
f (300);
if __name__ == "__main__":
t = timeit.Timer("test_func (change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
t = timeit.Timer("test_func (decorator_change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
t = timeit.Timer("test_func (native_change_coins)", "from __main__ import *");
print min(t.repeat (5, 1));
profile.run("test_func (change_coins)");
profile.run("test_func (decorator_change_coins)");
profile.run("test_func (native_change_coins)");
下面是部分结果:
change_coins return 9590
1.22809910198
decorator_change_coins return 9590
0.00217178440277
native_change_coins return 9590
2.69215193551
以上是时间测试结果,使用timeit模块来测试运行时间,重复5次,取最小值。具体原理可以看dive into python,详细请看上面的代码。从结果中我们可以看到,使用记忆技术后,性能提升了500多倍,这是符合规律的。然而使用了集合模拟栈之后,性能大幅下降。下面我们看看为什么。
change_coins return 9590
1292596 function calls (6 primitive calls) in 13.591 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.059 0.059 0.059 0.059 :0(setprofile)
1 0.000 0.000 13.533 13.533
1 0.000 0.000 13.533 13.533 amount.py:102(test_func)
1292591/1 13.531 0.000 13.531 13.531 amount.py:11(cc)
1 0.001 0.001 13.533 13.533 amount.py:5(change_coins)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 13.591 13.591 profile:0(test_func (change_coins))
decorator_change_coins return 9590
2494 function calls (881 primitive calls) in 0.027 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
873 0.004 0.000 0.004 0.000 :0(has_key)
1 0.000 0.000 0.000 0.000 :0(setprofile)
1 0.000 0.000 0.027 0.027
1 0.000 0.000 0.027 0.027 amount.py:102(test_func)
1 0.000 0.000 0.000 0.000 amount.py:51(memoiza)
873/1 0.013 0.000 0.026 0.026 amount.py:53(proc)
1 0.001 0.001 0.027 0.027 amount.py:62(decorator_change_coins)
742/1 0.009 0.000 0.026 0.026 amount.py:68(cc)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 0.027 0.027 profile:0(test_func (decorator_change_coins))
native_change_coins return 9590
3877778 function calls in 38.798 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1292590 5.824 0.000 5.824 0.000 :0(append)
1292592 5.960 0.000 5.960 0.000 :0(len)
1292591 6.076 0.000 6.076 0.000 :0(pop)
1 0.000 0.000 0.000 0.000 :0(setprofile)
1 0.000 0.000 38.798 38.798
1 0.000 0.000 38.798 38.798 amount.py:102(test_func)
1 20.938 20.938 38.798 38.798 amount.py:80(native_change_coins)
0 0.000 0.000 profile:0(profiler)
1 0.000 0.000 38.798 38.798 profile:0(test_func (native_change_coins))
以上是白盒分析结果,使用profile测试,主要分析函数的调用花费。具体可以参考http://www.sqlite.com.cn /MySqlite/11/480.Html。从上面的报表中,我们可以看出,最初的函数执行时间全消耗在了cc上。而记忆后,则是proc和cc基本对半,有的时候has_key测试也花点时间。这表示cc花费的时间大幅下降,记忆技术则花了比较多的时间。而模拟的呢?大部分时间都花在了 append,len,pop这三个函数上!这说明原始集合的效率严重制约了模拟效率。如果要提升性能的话,使用其他的集合吧。
另外贝壳又用C++写了一个,如下:
const int coin_map[] = {
1, 5, 10, 25, 50
};
const int coin_count = 5;
int cc (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
return cc (amount, kind_of_coins - 1) + cc (amount - coin_map[kind_of_coins - 1], kind_of_coins);
}
int dd (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
int rslt = 0;
for (int i = 0; i <= amount / coin_map[kind_of_coins - 1]; ++i)
rslt += dd (amount - i * coin_map[kind_of_coins - 1], kind_of_coins - 1);
return rslt;
}
class keys{
public:
int amount;
int kind_of_coins;
keys (int amount_p, int kind_of_coins_p):
amount(amount_p), kind_of_coins(kind_of_coins_p)
{}
bool operator == (const keys & k) const{
return (amount == k.amount && kind_of_coins == k.kind_of_coins);
}
bool operator < (const keys & k) const{
if (kind_of_coins == k.kind_of_coins)
return amount < k.amount;
return kind_of_coins < k.kind_of_coins;
}
};
map
int ee (int amount, int kind_of_coins)
{
if (amount == 0)
return 1;
if (amount < 0 || kind_of_coins <= 0)
return 0;
keys k (amount, kind_of_coins);
map
if (iter != mCache.end ())
return iter->second;
int rslt = 0;
for (int i = 0; i <= amount / coin_map[kind_of_coins - 1]; ++i)
rslt += dd (amount - i * coin_map[kind_of_coins - 1], kind_of_coins - 1);
mCache.insert(pair
return rslt;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int loop_times = 300;
clock_t s = clock();
printf ("kind of coins: %d\n", cc (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
s = clock();
printf ("kind of coins: %d\n", dd (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
s = clock();
printf ("kind of coins: %d\n", ee (loop_times, coin_count));
printf ("times:%d\n", clock () - s);
return 0;
}
注意到主函数中,使用的是clock来计量时间。如果C++下要做白盒性能测试就比较麻烦,需要用精确计时函数和宏。需要的可以单独和我联系。下面是部分计算结果,cc的和ee的,没有dd的。
300的计算结果
kind of coins: 9590
times:62
kind of coins: 9590
times:46
1000的计算结果
kind of coins: 801451
times:15953
kind of coins: 801451
times:11000
单位,ms。
原生的效率差异是20倍,用了缓存后性能只有略略上升?!反而是python比较快?
看来C++下的map效率也不高,要用hash_map才好。
倒是栈长度好很多,贝壳估计是131072次调用,大约是16384分。
2008年5月11日星期日
python的几个改进
首先需要增加的就是kill掉线程的方法,目前我们统统是调用系统函数。有没有搞错阿,需要针对系统写代码不说,还不安全。在线程关闭的过程中没有辗转开解和安全捕获。从最安全的角度上说,要关闭线程最方便的就是给其他线程抛异常。python并非不可以给其他线程抛异常,可非常麻烦不说,具体执行的时候发现,其实根本不是抛异常,而是在执行过程中检查异常。这样当程序在调用外部代码的时候死循环,想kill线程的时候根本不可行。所以安全的关闭线程的异常和直接kill掉线程的方法都要有。
其次,这东西没有什么可以快速辅助处理集合的工具类,例如STL中的set_union等等。虽说每个都不难,可是统一的实现和各自的实现毕竟是有差别的。很多时候,我们只需要抽象的计算两个集合,一个和一个的交集,就OK了。
其次,这东西没有什么可以快速辅助处理集合的工具类,例如STL中的set_union等等。虽说每个都不难,可是统一的实现和各自的实现毕竟是有差别的。很多时候,我们只需要抽象的计算两个集合,一个和一个的交集,就OK了。
2008年5月6日星期二
反射的几个类型
所谓反射,其实就是在运行时可以获得代码数据的技术,算是面对对象编程语言的专利。从这个意义上说,反射可以分为三个类型。
头一类是RTTI,其实这根本不算反射,本质上只能说多态。RTTI是一种鉴别某个对象是否为某个类的派生实例的技术,在C++中就有实现。简单的方法就是实现一个特定的虚函数,将当前对象所属的类虚函数表和所属父类的虚函数表一一返回。这样对比某个类的虚函数表,就可以知道是否为派生实例了。支持RTTI,程序才算真正支持了面对对象,而反射则是更高一层的技术。
第二类就是在C#和Java中盛行的反射技术,这种技术的核心在于可以通过名称寻找到对象。例如,我们可以寻找到一个叫做abc的对象,枚举其中的成员和方法,并且执行调用,这才是反射最大的意义。当我们遇到不同的数据输入时,我们可以调用不同的方法来处理这个数据,并且这个过程是动态配置的。而在C++中,我们无法通过编译器支持这个能力,必须手工的建立一个名称和一个对象的关联关系表,在合适的时候通过这个表,获得某个名称的函数入口指针。其实C#和Java中实现的方法和VM息息相关,他们的代码在目标文件中还保持着命名空间-类-对象的结构,Java还进一步的保留了源码(只是被翻译为了更快的P代码),而C#只保留了IL代码。这样VM在执行的时候自然可以很轻松的找到对应的函数,并且获得函数签名。而C类语言的特征是汇编时代的"符号链接"方式,编译的时候保有符号,完成链接就没了。
中间插一句,其实我们完全可以写一个只支持高阶语言的系统。这样的系统未必高效,可一定方便阿。
最后一种则是python中的系统,当用户调用一个类中的函数的时候,使用一个专门的函数来决定调用哪个。因此当对付SOAP这种东西的时候,python可以直接上。而C#,Java,C++都要通过工具生成代理方法。再用代理方法去调用公共函数库,实现调用。因为python直接将调用定向到了一个统一的函数上,所以压根不需要这步。不过这步的代价是严重的性能问题,因为每次函数调用都要去检查调用目标。python是纯脚本语言,占了这点便宜,所以才能这么干。
头一类是RTTI,其实这根本不算反射,本质上只能说多态。RTTI是一种鉴别某个对象是否为某个类的派生实例的技术,在C++中就有实现。简单的方法就是实现一个特定的虚函数,将当前对象所属的类虚函数表和所属父类的虚函数表一一返回。这样对比某个类的虚函数表,就可以知道是否为派生实例了。支持RTTI,程序才算真正支持了面对对象,而反射则是更高一层的技术。
第二类就是在C#和Java中盛行的反射技术,这种技术的核心在于可以通过名称寻找到对象。例如,我们可以寻找到一个叫做abc的对象,枚举其中的成员和方法,并且执行调用,这才是反射最大的意义。当我们遇到不同的数据输入时,我们可以调用不同的方法来处理这个数据,并且这个过程是动态配置的。而在C++中,我们无法通过编译器支持这个能力,必须手工的建立一个名称和一个对象的关联关系表,在合适的时候通过这个表,获得某个名称的函数入口指针。其实C#和Java中实现的方法和VM息息相关,他们的代码在目标文件中还保持着命名空间-类-对象的结构,Java还进一步的保留了源码(只是被翻译为了更快的P代码),而C#只保留了IL代码。这样VM在执行的时候自然可以很轻松的找到对应的函数,并且获得函数签名。而C类语言的特征是汇编时代的"符号链接"方式,编译的时候保有符号,完成链接就没了。
中间插一句,其实我们完全可以写一个只支持高阶语言的系统。这样的系统未必高效,可一定方便阿。
最后一种则是python中的系统,当用户调用一个类中的函数的时候,使用一个专门的函数来决定调用哪个。因此当对付SOAP这种东西的时候,python可以直接上。而C#,Java,C++都要通过工具生成代理方法。再用代理方法去调用公共函数库,实现调用。因为python直接将调用定向到了一个统一的函数上,所以压根不需要这步。不过这步的代价是严重的性能问题,因为每次函数调用都要去检查调用目标。python是纯脚本语言,占了这点便宜,所以才能这么干。
2008年4月7日星期一
python的非经典错误
def comp_tuple_file (tuple_file1, tuple_file2):
for i in tuple_file1:
if i in tuple_file2:
tuple_file1.remove(i);
tuple_file2.remove(i);
if __name__=="__main__":
t1=[(1,"1"),(2,"2"),(3,"3")];
t2=[(1,"1"),(3,"3"),(2,"2"),(4,"2")];
comp_tuple_file (t1, t2);
print t1;
print t2;
错在哪里?
头一次循环,i=(1,"1")被正确移除了。但是接下来,i=(3,"3")?
这个叠代器的行为很有意思哦,貌似叠代器内存储的是集合的索引。
def comp_tuple_file (tuple_file1, tuple_file2):
collection=tuple_file1[:];
for i in collection:
if i in tuple_file2:
tuple_file1.remove(i);
tuple_file2.remove(i);
if __name__=="__main__":
t1=[(1,"1"),(2,"2"),(3,"3")];
t2=[(1,"1"),(3,"3"),(2,"2"),(4,"2")];
comp_tuple_file (t1, t2);
print t1;
print t2;
这才是正确的代码。
for i in tuple_file1:
if i in tuple_file2:
tuple_file1.remove(i);
tuple_file2.remove(i);
if __name__=="__main__":
t1=[(1,"1"),(2,"2"),(3,"3")];
t2=[(1,"1"),(3,"3"),(2,"2"),(4,"2")];
comp_tuple_file (t1, t2);
print t1;
print t2;
错在哪里?
头一次循环,i=(1,"1")被正确移除了。但是接下来,i=(3,"3")?
这个叠代器的行为很有意思哦,貌似叠代器内存储的是集合的索引。
def comp_tuple_file (tuple_file1, tuple_file2):
collection=tuple_file1[:];
for i in collection:
if i in tuple_file2:
tuple_file1.remove(i);
tuple_file2.remove(i);
if __name__=="__main__":
t1=[(1,"1"),(2,"2"),(3,"3")];
t2=[(1,"1"),(3,"3"),(2,"2"),(4,"2")];
comp_tuple_file (t1, t2);
print t1;
print t2;
这才是正确的代码。
订阅:
博文 (Atom)