2009年6月17日
STL的binary search是由lower_bound和uppper_bound两个函数实现的。lower_bound的描述为: lower_bound() returns the position of the first element that has a value less than or equal to value. This is the first position where an element with value value could get inserted without breaking the actual sorting of the range [beg,end). 此函数的random-access iterator版的实现为(采自《STL源码剖析》):  __lower_bound template <class ForwardIterator, class T, class Distance> ForwardIterator __lower_bound(ForwardIterator first, ForwardIterator last, const T& value, Distance*, forward_iterator_tag) { Distance len = 0; distance(first, last, len); Distance half; ForwardIterator middle;
while (len > 0) { half = len >> 1; middle = first; advance(middle, half); if (*middle < value) { first = middle; ++first; len = len - half - 1; } else len = half; } return first; }这是一个典型的binary search。为了方便分析,改写一下(以下分析均针对这段代码): 1 template <class RandomAccessIterator, class T, class Distance> 2 RandomAccessIterator __lower_bound(RandomAccessIterator first, 3 RandomAccessIterator last, const T& value, 4 Distance*, random_access_iterator_tag) { 5 Distance len = last - first; 6 Distance half; 7 RandomAccessIterator middle; 8 9 while (len > 0) { 10 half = len >> 1; 11 middle = first + half; 12 if (*middle < value) { 13 first1 = middle + 1; 14 len1 = len - half - 1; 15 16 first = first1; 17 len = len1; 18 } 19 else 20 len = half; 21 } 22 return first; 23 } 下面不严格的说明这个函数的正确性。首先确定函数的precondition。为了方便说明,假定数组[first, last)前后各有一个无穷小和无穷大的元素,最后我们将会看到这个假设不会影响结果。此函数要求输入的数组是排过序的: -∞ = *(first-1) < *first ≤ *(first+1) ≤...≤ *(last-1) < *last = +∞ 然后确定while循环的invariant: *(first-1) < value ≤ *(frist+len) 以下说明这个条件始终成立。 首先在进入while循环前的第8行处,由precondition可得invariant“-∞ = *(first-1) < value < *last = *(first+len) = +∞”成立。 在循环的过程中根据条件(*middle < value)是否成立分两种情况改变len(和first)的值,需要分别说明两种情况下的改变都可以保持invariant成立。 首先如果条件成立,那么程序执行到15行时,*(first1-1)=*middle,而此时条件*middle < value成立,因此*(first1-1)<value。而*(firtst1+len1) = *(middle + 1 + len - half - 1) = *(first + half + 1 + len - half -1) = *(first + len)因为此时老的invariant“value ≤ *(frist+len)”依旧是成立的,所以value ≤ *(first1 + len1)。所以在15行时,条件*(first1-1) < value ≤ *(frist1+len1)成立,当16、17行分别把first1、len1赋值给新的first、len后,invariant成立。 当条件 *middle < value不成立,即*middle ≥ value时,程序执行20行“len = half”。first的值不变,因此*(first1-1)<value依旧成立。第20行执行后,*(first + len) = *(first + half) = *middle(注意11行:middle = first+half),而此时的条件是*middle ≥ value,所以*(first + len) ≥ value,20行过后invariant保持成立。 由于while循环过程中invariant保持成立,所以循环过后此条件依然成立。又由于循环的条件是len > 0,循环结束后(22行)必然len = 0,所以invariant就成了:*(first-1) < value ≤ *first,这就是函数的postcondition,后面会说明此postcondition符合函数定义。 另外还需说明循环为什么能结束。第10行“half = len >> 1;”执行后,0 ≤ half < len。第14行“len1 = len - half - 1;”,所以len1 < len。因此17、20行两处更新len的值,都可以保证len要减少。最终len会变成零或负值,循环条件将不成立,此时循环结束。 最后说明一下开始做的假设*(first-1) = -∞和*last = ∞不影响证明的正确性。首先在整个循环过程中,不会dereference这两个不存在的iterator。由于first的值是改变的,为了方便说明,用first0代表最初的,即由参数传入的first的值。需要说明middle的值始终在范围[first0, last)内。第11行,middle = first + half,第一次执行时midde = first0+half ≥ first0,且循环过程中first的值是单调递增的,又half总是大于0,所以始终有middle ≥ first0。又middle = first+len/2 < first+len,循环开始时first+len = first0+last-first0 = last,且循环中随着first和len值的改变,first+len是递减的(),所以middle始终小于last。 既然middle的始终在[first0, last)内,对*(first0-1)和*last的值做出的假设就不会对计算过程有任何影响。前面说明了第23行处的postcondition为*(first-1) < value ≤ *first。当first0+1 ≤ first ≤ last-1时,很明显first处的元素值大于等于value,而first前一个值小于value,这和定义“the position of the first element that has a value less than or equal to value”相吻合。当first = first0时,只取postcondition的右半value ≤ *first,当first = last时,只取左半*(first-1) < value,均符合lower_bound的定义。此postconditition保证23行处的first值即所需结果。 以上是一个非形式化的、不严格的、对只有短短20多行的程序的正确性说明。感慨一下,要把C/C++程序写对,真不容易。
2008年11月9日
最近文档标准之争正烈。我完全不问政治(虽然在技术无区分的情况下我情感上偏袒国货),而且对相关技术也没有深入了解,但我还是想指出文档标准中一个可能被忽视的重要方面--对象寻址。现在是互联网时代,所有的东西都要考虑在网络上的使用性,文档更是如此。W3C定义的URI提供了寻址到文档的方法,但这显然是不够的,我们需要寻址到文档的一个部分,因此需要一个扩展URI的标准。HTML有可以命名的anchor,文档标准也应该有类似的机制,比如说用一个URI标出表格的一个单元,段落的一个句子。
2008年11月7日
Ruby的eval系列方法(Kernel#eval,Binding#eval,instance_eval,class_eval)支持两种方式,一是把代码片断作为字符串传递进去,二是传递block。我建议除非代码在运行时动态生成,否则尽量使用code block。因为我发现字符串中的代码对debugger是不可见的--在stack trace中不能准确显示,不能下断点,不能逐步运行,而eval的code block则完全没有问题。
2008年10月26日
2008年10月22日
干IT民工这一行的一定是人手一册PoEAA的。可惜这本书的影印版居然全国缺货,上了好多家网站都如此(OK,我知道金书网显示有货,但下单以后他们发不出来的)。这本书断货断得之彻底,是我的买书经历中从未遇到的:不仅大的购书/购物网没有,淘宝没有(很多卖家列出这本书,一问,没货,问了十来家都如此),电力出版社自己的购书网站没有,连我以前觉得什么地下刊物都能买到的kongfz也没有。电子商务的真谛一是让商品信息到达消费者,真谛二就是让消费行为迅速反馈到生产者啊。在这里真谛二完全没有实现。这本书大受欢迎,全国断货的事实并没有被出版社捕捉到,变成更多的利润,真是可惜。不过这也好,说明IT业还有很大的发展空间,这种现实对我们这种后来者再好不过了。
幸运的是在淘宝上花99块捡了本崭新的原版,我声明我绝不是托,但还有两本没卖,大家快去抢吧!
2008年10月21日
今天被一个超级trivial的格式问题搞到抓狂。程序中有这样一个match结构:
match ... with
| ... -> ...
| ... -> ...
这以后不论写一行什么编译器都抱怨格式错误。原来是#light格式的缩进规则所致,应该这样写:
match ... with
| ... -> ...
| ... -> ...
注意每个竖线前都要空一格。如果没有空格,编译器会认为match后每一行都是一个pattern,因而格式错误。如果不空格的话,把整个match用括号括起来也可以。
Channel9上一个叫Charles的说得好:One of the truly great things about JAOO is that it is not a product-focused conference: it's about programming first and foremost and enables the sharing of perspectives and ideas among the world's best and brightest programming minds.
希望中国也能有这样的会议。
Ruby没有C/C++/C#风格的enum。没有关系,上meta-programming,这是熟练Ruby程序员的第一反应,因为Ruby也没有struct,用meta-programming造出的struct一样好使。很好很强大的想法。但是如果我们想Keep It Simple呢?一个小小的parallel assignment功能就解决问题:
module WeekDay
Mon, Tue, Wed, Thu, Fri, Sat, Sun = *(1..7)
end
p WeekDay::Mon
p WeekDay::Tue
...
最后还想提醒一下,这个enum和C# enum一样是具备反射性的,只要WeekDay.constants就行了。
2008年10月18日
重口味警告:本篇适合于追求代码直观到偏执狂地步,和宁愿绞尽脑汁减少几次击键的程序员,不喜勿入!
在Ruby里我们可以写出这样的代码:
hash.if_has_key k do |val|
#do sth. with val
end
这段代码描述性太强了,以至不需要解释就能让人明白。而且对比普通写法:
if hash.has_key k then
val = hash[k]
...
end
<
p>省了不少打字的功夫。为了在F#里也有这样的效果,我想了两种方法。首先,可以模拟Ruby。实现if_has_key的关键一是开放
class--事前要将if_has_key方法加入hash的类,二是block。F#和C# 3.0一样支持extension
methods,而lambda expression可以代替block(虽然不完美),因此可以这样:
#light
type System.Collections.Generic.IDictionary with
member this.if_has_key k f =
if this.ContainsKey k then
f this.[k]
else
()
于是:
let tbl = dict [(1, "one"); (2, "two");]
tbl.if_has_key 1 (fun val->
print_any val |>ignore)
可是fun关键字实在扎眼,也只能感叹Ruby的设计至少在格式层面是很巧妙的了。第二种方法利用F#的active pattern功能:
let
(|HasKeyVal|_|)<'kt, 'vt> k
(d:System.Collections.Generic.IDictionary<'kt,
'vt>) =
if d.ContainsKey k then Some(d.[k])
else None
于是:
match tbl with
| HasKeyVal 2 v -> print_any v
| _ -> ()
缺点一是match关键字在这个地方不伦不类,二是要跟个尾巴|_->() 。好处是可以像swich包含多个case一样:
match tbl with
| HasKeyVal k1 v -> ...
| HasKeyVal k2 v -> ...
| HasKeyVal k3 v -> ...
| _-> ()
程序会依次测试tbl是否包含k1,k2,k3。这样的程序对比起用if...then...elseif...else写出来的版本节省就比较可观了。
2008年10月11日
|