栏杆的lisp_Lisp - 行者无疆 始于足下 - 行走,思考,在路上

按照我所了解的当代中国本科计算机教育,大多人工科学生学习的第一门编程语言应该是C,接下来如果还有需要的话,就是C++或者Java了。不幸的是,鄙人也是这样过来的,幸运的是,鄙人天资愚鲁,学了半年C++后知难而退,转战了实战主义的Shell Script和Python,这一年来闹中取静,为了探寻MapReduce算法模型的本原,啃了几本Lisp的书,回过头来,总算对C++的诸多繁杂特性有了一些全局性的认识,这种认识来自于跨语言的佐证和思考,而非来自于C++语言本身。事实上C++中的很多概念在C++中是无法学到通透的,比如:

C++11的lambda:你可以不知道Alonzo Church, 也可以不会Lambda calculusHigher-order function

STL:STL几乎就是C++经典库和设计的代名词,通过迭代器将算法和组件分离可惜你不知道的是,迭代器并不是一个高层次的抽象机制,迭代器的本质是一种迭代遍历操作,至于通过什么手段来迭代遍历,这些本不应该是使用者所应该关心的细节问题。所以对于下面的这段c++伪代码:

for (vector::iterator itr = v.begin(); itr != v.end(); ++itr)

{

do_something(*itr)

}

其高阶抽象代码应该是:

for_each(v, do_something)

稍微了解一点Lisp的读者都能想到如下的等价伪代码

(mapcar #'do_something v)

每次你写"v.begin(), v.end()"这样的代码时,你就不知不觉地降低了自己的抽象层次,使自己脱离问题域而转向去纠结于实现域,不要小看这种力量, 软件工程的一切欢乐和痛苦,只是聚沙成塔的两个极端而已。

事实上STL本身包含很多函数式编程的思想,比如说 functor这种组件,其实质是将在c++中作为second-class的函数通过类封装的手段提升至first-class,如此一来,函数的核心操作摇身一变成为functor的时候,就可以像函数式语言里面的Higher-order function一样,可以用类成员变量来模拟实现闭包,可以被当做普通参数传递返回(这样就不用费力去写令很多新手语法不过关的函数指针了),甚至可以通过std::bind1st/std::bind2nd这种奇技淫巧实现一个蹩脚的线性代数级别的函数映射与变换。

STL里面大量的算法都是基于迭代器的抽象而进行序列的批量化操作,同种算法多种容器的核心技术是 基于C++模板实现的静多态 ,"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures",从这个角度上来讲,STL算法和Lisp中针对sequence类型数据的各种函数(mapcar/remove/remove_if/member等)有异曲同工之妙。

最后来八一八STL之父Alexander Stepanov,其实人家是莫斯科大学数学系毕业的高材生,所以STL背后有着很深的数学思想,"Elements of Programming"或许是解开这个谜题的钥匙。另外,Alexander是反对OOP的。

泛型与模板:这大概是Modern C++中最重口味的话题了,也是很多C++初学者的噩梦。我认为C++模板足够强大,但同时也足够扭曲且非人道,布满了大大小小的地雷和陷阱。探究起来,C++模板之所以有那么多坑,其历史原因在于C++模板是一种被发现而非被发明的技术C++模板的本质在于用编程的手段显式地控制编译器的代码生成。没错,聪明的你已经想到,Lisp的macro做的也是同样的事情。但是不同于Lisp的macro,由于C++模板的先天不足和C++静态类型系统的限制,C++在语言层面上对模板编程的支持非常有限。荣耀先生有一篇非常精炼的PPT《C++模板元编程技术与应用》, 基本上概括了C++模板编程的核心机制和语言实现,我摘录了一些如下:

模板元编程使用静态C++语言成分,编程风格类似于函数式编程,其中不可以使用变量、赋值语句和迭代结构等。

在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型。被操纵的实体也称为元数据(Metadata)。所有元数据均可作为模板参数。

由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。

编译期赋值通过整型常量初始化和typedef语句实现。例如:

enum { Result = Fib::Result + Fib::Result};

static const int Result = Fib::Result + Fib::Result;

成员类型则通过typedef引入,例如:

typedef T1 Result;

条件结构采用模板特化或条件操作符实现。如果需要从两个或更多种类型中选其一,可以使用模板特化,如前述的IfThenElse

静态C++代码使用递归而不是循环语句。递归的终结采用模板特化实现。如果没有充当终结条件的特化版,编译器将一直实例化下去,一直到达编译器的极限

而正是由于底层支撑性语言机制的匮乏,使得C++模板编程非常的冗长、丑陋,甚至有些扭曲乃至非人道有时候你想要舞蹈的时候,要低头看看,你的脚上是否带着不必要的镣铐。 C++的静态类型系统对于泛型编程而言,就是这样的镣铐。

引用、指针、const、static等:除了以上比较“重口味”的C++语言特性,C++里还有各种各样的语言小尾巴,而且这个尾巴一般都拉的特别长。当然,尾巴长的好处之一就是可以养活很多语言专家,什么effective啊、exceptional啊、faq啊啥的,在所有的编程语言中,C++这点绝对是独树一帜。其实每个语言特性的背后都有值得深究的知识, 没有任何事情是想当然的。 const够简单了吧?可是你知道const pointer和pointer to const的区别吗?你知道什么时候用const引用传参什么时候返回const引用什么时候返回值吗?你知道const成员函数吗?你知道为什么会有初始化成员列表的存在吗?再来说说引用这个概念,其本质上就是一种受限指针加上编译器层面上的语法糖修饰,按理说不太难,但是什么时候传引用返回引用确是值得深究的好问题,搞清楚了这点,你就会搞明白C++中的copy constructor/copy assignment operator,Java中的Object.clone(),Python中的"is"、和Lisp中的eq/eql/equal。传引用/指针还是传值涉及到深刻的程序语言原理,并不是你想象的那么简单而已。

以上谈了这么多,读者可能会问,既然C++如此繁杂,还要不要学习C++?学,当然要学,否则你怎么批判呢?怎么学?批判地学。要去学习语言机制的根源和本质而不要迷失在语言特性的森林里

最后,还是回到面试题上,还是放上鄙人的C++代码,也好和Lisp/Python版的程序做一个小对比:

#include

#include

#include

#include

#include

#include

#include

#include

#include

using namespace std;

struct vertex

{

int index; /// the vertex index, also the vertex name

vertex* prev; /// the prev vertex node computed by bfs and bfs_shortest

int dist; /// the distance to the start computed by bfs

/// and bfs_shortest

vector adj; /// the adjacency list for this vertex

vertex(int idx)

: index(idx) {

reset();

}

void reset() {

prev = NULL;

dist = numeric_limits::max();

}

};

class graph

{

public:

graph() { }

~graph();

void add_edge(int start, int end);

void bfs(int start);

void bfs_shortest(int start);

list get_path(int end) const;

void print_graph() const;

protected:

vertex* get_vertex(int idx);

void reset_all();

list get_path(const vertex &end) const;

private:

/// disable copy

graph(const graph &rhs);

graph& operator=(const graph &rhs);

typedef map > vmap;

vmap vm;

};

graph::~graph() {

for (vmap::iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

delete (*itr).second;

}

}

/**

* return a new vertex if not exists, else return the old vertex, using std::map

* for vertex management

*

* @param idx vertex index

*

* @return a (new) vertex of index idx

*/

vertex* graph::get_vertex(int idx) {

/// cout << "idx: " << idx << "\tvm.size(): " << vm.size() << endl;

vmap::iterator itr = vm.find(idx);

if (itr == vm.end())

{

vm[idx] = new vertex(idx);

return vm[idx];

}

return itr->second;

}

/**

* clear all vertex state flags

*

*/

void graph::reset_all() {

for (vmap::iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

(*itr).second->reset();

}

}

/**

* add an edge(start --> end) to the graph

*

* @param start

* @param end

*/

void graph::add_edge(int start, int end) {

vertex *s = get_vertex(start);

vertex *e = get_vertex(end);

s->adj.push_back(e);

}

/**

* print the graph vertex by vertex(with adj list)

*

*/

void graph::print_graph() const {

for (vmap::const_iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

cout << itr->first << ": ";

for (vector::const_iterator vitr = itr->second->adj.begin();

vitr != itr->second->adj.end();

++vitr)

{

cout << (*vitr)->index << " ";

}

cout << endl;

}

}

/**

* traversal the graph breadth-first

*

* @param start the starting point of the bfs traversal

*/

void graph::bfs(int start) {

if (vm.find(start) == vm.end())

{

cerr << "graph::bfs(): invalid point index " << start << endl;

return;

}

vertex *s = vm[start];

queue q;

q.push(s);

s->dist = -1;

while (!q.empty()) {

vertex *v = q.front();

cout << v->index << " ";

q.pop();

for (int i = 0; i < v->adj.size(); ++i)

{

if (v->adj[i]->dist != -1)

{

q.push(v->adj[i]);

v->adj[i]->dist = -1;

}

}

}

}

/**

* the unweighted shortest path algorithm, using a std::queue instead of

* priority_queue(which is used in dijkstra's algorithm)

*

* @param start

*/

void graph::bfs_shortest(int start) {

if (vm.find(start) == vm.end())

{

cerr << "graph::bfs_shortest(): invalid point index " << start << endl;

return;

}

vertex *s = vm[start];

queue q;

q.push(s);

s->dist = 0;

while (!q.empty()) {

vertex *v = q.front();

q.pop();

for (int i = 0; i < v->adj.size(); ++i)

{

vertex *w = v->adj[i];

if (w->dist == numeric_limits::max())

{

w->dist = v->dist + 1;

w->prev = v;

q.push(w);

}

}

}

}

/**

* get the path from start to end

*

* @param end

*

* @return a list of vertex which denotes the shortest path

*/

list graph::get_path(int end) const {

vmap::const_iterator itr = vm.find(end);

if (itr == vm.end())

{

cerr << "graph::get_path(): invalid point index " << end << endl;

return list();

}

const vertex &w = *(*itr).second;

if (w.dist == numeric_limits::max())

{

cout << "vertex " << w.index << " is not reachable";

return list();

}

else {

return get_path(w);

}

}

/**

* the internal helper function for the public get_path function

*

* @param end

*

* @return a list of vertex index

*/

list graph::get_path(const vertex &end) const {

list l;

const vertex *v = &end;

while (v != NULL) {

l.push_front(v->index);

v = v->prev;

}

return l;

}

class chessboard {

private:

struct point {

int x;

int y;

point(int px, int pb)

: x(px), y(pb) { }

};

public:

chessboard(int s);

void solve_knight(int x, int y);

protected:

bool is_valid(const point &p);

point next_point(const point &p, int i);

private:

graph board;

int size;

};

/**

* constructor, build a underlying graph from a chessboard of size s

*

* @param s

*/

chessboard::chessboard(int s)

: size(s) {

for (int i = 0; i < size; ++i)

{

for (int j = 0; j < size; ++j)

{

int start = i * size + j;

point p(i, j);

for (int k = 0; k < 8; ++k)

{

/// the next possible knight position

point np = next_point(p, k);

if (is_valid(np))

{

int end = np.x * size + np.y;

/// add edges in both directions

board.add_edge(start, end);

board.add_edge(end, start);

}

}

}

}

}

/**

* find and print a path from (x, y) to (size, size)

*

* @param x

* @param y

*/

void chessboard::solve_knight(int x, int y) {

int start = (x-1) * size + (y-1);

int end = size * size - 1;

board.bfs_shortest(start);

list l = board.get_path(end);

int count = 0;

for (list::const_iterator itr = l.begin(); itr != l.end(); ++itr)

{

cout << "(" << *itr/size + 1 << ", " << *itr%size + 1<< ")";

if (count++ != l.size() - 1)

{

cout << " -> ";

}

}

cout << endl;

}

/**

* whether or not the point is valid in the chessboard

*

* @param p

*

* @return true for valid

*/

bool chessboard::is_valid(const point &p) {

if (p.x < 0 || p.x >= size - 1 || p.y < 0 || p.y >= size - 1)

{

return false;

}

return true;

}

/**

* the next possible position, every has 8 next possible position, though not

* all 8 position is valid

*

* @param p the original knight position

* @param i

*

* @return

*/

chessboard::point chessboard::next_point(const point &p, int i) {

int knight[8][2] = {

{2, 1}, {2, -1},

{-2, 1}, {-2, -1},

{1, 2}, {1, -2},

{-1, 2}, {-1, -2}

};

return point(p.x + knight[i][0], p.y + knight[i][1]);

}

int main(int argc, char *argv[])

{

if (argc != 4)

{

cerr << "Wrong arguments! Usage: knight.bin N x y" << endl;

return -1;

}

int N = atoi(argv[1]);

int x = atoi(argv[2]);

int y = atoi(argv[3]);

chessboard chess(N);

chess.solve_knight(x, y);

return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值