Modest opinions  
by a humble autodidact
日历
<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
统计
  • 随笔 - 62
  • 文章 - 0
  • 评论 - 127
  • 引用 - 1

导航

与我联系

搜索

 

常用链接

留言簿

我参与的团队

我的标签

随笔档案

收藏夹

最新评论

  • 1. re: 硬币的两面
  • 貌似不是发现programming pearls里面quicksort有个bug而是programming pearls里面提到世界上绝大多数binary serach都写得有问题.当然也可能是我搞错...
  • --hanliinter

阅读排行榜

评论排行榜

 

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
这是一个典型的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++程序写对,真不容易。


 

 





 


 

posted @ 2009-06-17 19:30 yushih 阅读(29) | 评论 (0)编辑

2008年11月9日

最近文档标准之争正烈。我完全不问政治(虽然在技术无区分的情况下我情感上偏袒国货),而且对相关技术也没有深入了解,但我还是想指出文档标准中一个可能被忽视的重要方面--对象寻址。现在是互联网时代,所有的东西都要考虑在网络上的使用性,文档更是如此。W3C定义的URI提供了寻址到文档的方法,但这显然是不够的,我们需要寻址到文档的一个部分,因此需要一个扩展URI的标准。HTML有可以命名的anchor,文档标准也应该有类似的机制,比如说用一个URI标出表格的一个单元,段落的一个句子。
posted @ 2008-11-09 20:43 yushih 阅读(45) | 评论 (0)编辑

2008年11月7日

Ruby的eval系列方法(Kernel#eval,Binding#eval,instance_eval,class_eval)支持两种方式,一是把代码片断作为字符串传递进去,二是传递block。我建议除非代码在运行时动态生成,否则尽量使用code block。因为我发现字符串中的代码对debugger是不可见的--在stack trace中不能准确显示,不能下断点,不能逐步运行,而eval的code block则完全没有问题。
 

posted @ 2008-11-07 11:06 yushih 阅读(110) | 评论 (0)编辑

2008年10月26日

2008年10月22日

干IT民工这一行的一定是人手一册PoEAA的。可惜这本书的影印版居然全国缺货,上了好多家网站都如此(OK,我知道金书网显示有货,但下单以后他们发不出来的)。这本书断货断得之彻底,是我的买书经历中从未遇到的:不仅大的购书/购物网没有,淘宝没有(很多卖家列出这本书,一问,没货,问了十来家都如此),电力出版社自己的购书网站没有,连我以前觉得什么地下刊物都能买到的kongfz也没有。电子商务的真谛一是让商品信息到达消费者,真谛二就是让消费行为迅速反馈到生产者啊。在这里真谛二完全没有实现。这本书大受欢迎,全国断货的事实并没有被出版社捕捉到,变成更多的利润,真是可惜。不过这也好,说明IT业还有很大的发展空间,这种现实对我们这种后来者再好不过了。

幸运的是在淘宝上花99块捡了本崭新的原版,我声明我绝不是托,但还有两本没卖,大家快去抢吧!

posted @ 2008-10-22 10:45 yushih 阅读(46) | 评论 (3)编辑

2008年10月21日

今天被一个超级trivial的格式问题搞到抓狂。程序中有这样一个match结构:

match ... with
| ... -> ...
| ... -> ...

这以后不论写一行什么编译器都抱怨格式错误。原来是#light格式的缩进规则所致,应该这样写:

match ... with
 | ... -> ...
 | ... -> ...

注意每个竖线前都要空一格。如果没有空格,编译器会认为match后每一行都是一个pattern,因而格式错误。如果不空格的话,把整个match用括号括起来也可以。

posted @ 2008-10-21 17:23 yushih 阅读(32) | 评论 (1)编辑
 

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.

希望中国也能有这样的会议。

posted @ 2008-10-21 11:25 yushih 阅读(31) | 评论 (0)编辑
 

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就行了。
posted @ 2008-10-21 00:21 yushih 阅读(35) | 评论 (0)编辑

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写出来的版本节省就比较可观了。

posted @ 2008-10-18 23:14 yushih 阅读(33) | 评论 (0)编辑

2008年10月11日

利用C++ template进行编译时计算是大家都知道的。这里介绍一种新的C++ template编译时计算。这种方法,据我有限的了解,是头一次在江湖上出现(如果大家知道已经有人实践过这种方法,请告诉我,不过不用到Boost里去找,我已经找过了,没有)。我不练习C++好多年,这个主意是神托梦给我的(当天白天我在搞.net,应该不是“日有所思,夜有所梦”),所以如果大家觉得这种方法很没意思,怪神,不要怪我。

这种方法利用C++ template和C++的类型检查,实现了编译时的SLR(1) parsing。具体的说,有一个用BNF写的语法,把语法中的每个terminal作为一个函数名,我们可以把SLR(1)(或者LL(1),LR(1)都一样,但SLR(1)是最常用的)parsing table编码成几个template定义,使C++在编译时可以检查一串连续的,用“.”分隔的函数调用是否符合这个语法。举个例子,假设有这样一个语法:

A -> 'L' A 'R' |
      'x'

那么我们希望以下的C++代码片段能通过编译:

f()
{
...
begin().x().end();
begin().L().x().R().end();
begin().L().L().x().R().R().end();
}

因为串x,LxR,LLxRR都是符合A的语法的;而以下的代码在编译时出错:

g()
{
...
begin().L().R().end();
begin().L().L().x().R().end();
begin().R().x().L().end();
begin().L().x().R().x().end();
}

因为串LR,LLxR,RxL,LxRx都不符合语法A。

虽然C++ templates的编译时计算虽然是图灵完备的,但这毕竟不是它最初的目的,把它当成一般性的编程语言来进行计算很别扭。好在SLR(1)解析的难点在于构建parsing table,一旦parsing table构建好,只需很简单的操作就可以进行解析了。这里并不涉及parsing table的构建,用C++ template进行语法解析的工作就相对容易了。在进行SLR(1)解析时,需要维持一个解析堆栈,堆栈上的每个元素为解析表中的状态之一。我们怎么用C++的类型系统表示这个堆栈的状态呢?首先我们为每个状态定义一个template class,假设有三个状态0,1,2,那么定义:

template<typename T>
class S0 // representing state 0
{/* ignore members for now*/}

template<typename T>
class S1 // representing state 1
{/* ignore members for now*/}

template<typename T>
class S2 // representing state 2
{/* ignore members for now*/}

我们可以用其中的一个类表示堆栈顶端的元素,用这个类的template parameter表示它后面的元素,这个template paramter的template parameter表示再后面一个元素...以此类推。例如堆栈内容(从底至顶)201可以表示为类型:

S1<S0<S2<void> > >

这里我们将void赋予最底元素的template parameter,意思是它后面就没有东西了。有了表示解析堆栈状态的方法,我们就能勾画出语法解析的基本思路了:每一个函数调用返回一个对象,其类型表示SLR(1)解析器在接受到相应输入后堆栈的状态;这个对象再接受下一个输入(方法调用),返回对象的类型表示新的堆栈状态。按照这个思路开始实施,以此语法为例:

E' ->  E 'end'
E -> E 'plus' 'n' | 'n
'

它的SLR(1)解析表是这样的:

State Input Goto

n plus end E
0 s2

1
1
s3 accept
2
r(E->n) r(E->n)
3 s4


4
r(E->E plus n) r(E->E plus n)

表中有两种不同的动作:s后跟数字表示shift,然后往堆栈里压入这个数字表示的状态;r表示reduce,需要从堆栈中pop出一些元素,然后压入Goto一栏里的状态。从表中看出,如果状态0在栈顶,那么下一个输入只能是n而不能是plus或end,且输入n后应该压入状态2,因此这样编码状态0:

template<typename PrevInStack>
struct S0
{
    typedef PrevInStack PrevState;
    S2<S0<PrevInStack> > n() { return S2<S0<PrevInStack> >(); }

    typedef S1<S0<PrevInStack> > GotoEState;
};

当状态0处于堆栈顶端时,堆栈的内容可以由S0<PrevInStack>表示,其中PrevInStack表示除栈顶外的堆栈状态,此时往堆栈压入状态2,堆栈的状态就会变成S2<S0<PrevInStack> >,这就是根据解析表得出的方法n的返回类型。至于GotoEState的作用,马上会出现。

状态1包含accept动作,为此需要定义一个dummy类E:

struct E{};

accept只需返回这个dummy类:

template<typename PrevInStack>
struct S1
{
    typedef PrevInStack PrevState;

    typedef S3<S1<PrevInStack> > PlusState;
    PlusState plus() { return PlusState(); }

    typedef E EndState;
    EndState end() { return EndState(); }
};

最后我们以状态4为例看reduction动作怎么处理。当解析堆栈的顶部元素是状态4,下一个输入是plus时,需要将堆栈中的三个元素弹出,因为E->E plus n右边有三个符号E,plus和n,然后压入此时堆栈顶部状态对应的Goto状态。Reduction完成后还需输入plus,做shift。在这里我们必须把reduction和shift操作合成一个步骤:

template<typename PrevInStack>
struct S4
{
    typedef PrevInStack PrevState;

    typename PrevInStack::PrevState::PrevState::GotoEState::PlusState plus()
    { return typename PrevInStack::PrevState::PrevState::GotoEState::PlusState(); }

    typename PrevInStack::PrevState::PrevState::GotoEState::EndState end()
    { return typename PrevInStack::PrevState::PrevState::GotoEState::EndState(); }
};

其中PrevInStack::PrevState::PrevState即将三个元素弹出堆栈,然后转到GotoEState,这就是reduction的结果,接着shift,最终的结果就是PrevInStack::PrevState::PrevState::GotoEState::PlusState。至此我们就可以编码整个SLR(1)解析表了。最后,因为SLR解析的初始状态是0,所以这样定义begin函数:

S0<void> begin() { return S0<void>(); }

完整的代码如下:

Code

现在可以开始用了,辛苦typing(双关)一阵,吃几颗糖先:

#define n n()
#define plus plus()
#define end end()
#define begin begin()

于是我们可以写这样的语句:

E e1 = begin.n.plus.n.end;
E e2 = begin.n.plus.n.plus.n.end;

而这样的语句,因为不符E的语法,所以编译不了:

E e3 =  begin.plus.n.end;
E e4 =  begin.n.plus.n.plus.end;

如果在Visual Studio里编辑文件,一个有趣的功能将会出现,Visual Studio的IntelliSense在每一处提示下一个输入可能是什么,只要按照Visual Studio的提示来,写出来的串一定是符合我们定义的语法的:




最后也是最关键的问题,这种技术有用吗?我觉得有,这种技术可以用来构建嵌入式DSL。在现有的基础上可以增加生成parse tree或abstract syntax tree的功能,请看下回分解!

谢谢观看!

p.s.写了一通C++,我突然想起C++/CLI来了,有用C++/CLI干事的兄弟吗?

posted @ 2008-10-11 12:44 yushih 阅读(972) | 评论 (11)编辑
 
Copyright © yushih Powered by: 博客园 模板提供:沪江博客