ABOUT

avatar

I'm a web developer based in Chengdu, China.

I like simple things and stuff.

I share code on Github.

Powered by Jekyll and hosted on Github.

Redis commands 11 May 2013

A language that doesn’t change the way you think about programming is not worth learning.—Alan Perlis

我认为对数据库来说也是如此。Redis 和其他的 NoSQL 数据库不一样,使用好它至少得换一种思路去存取数据,所以也是值得去花时间学习的。

一个月前我开始了 redis-commands 这个项目, 想通过自己去实现 Redis 的命令 这种方式来学习并理解它。这个过程收获很大,遇到了很多新的概念和抽象,每次都就像是在树林里捡到一朵小蘑菇一样令人开心。

虽然目前没有完全实现,不过可以在 node 环境下通过 npm 安装使用:

$ npm install redis-commands

使用的方式

var Redis = require('redis-commands');
var R = new Redis();

R.hmset('user:yuanchuan', 
  'location', 'chengdu',
  'age', '18'
)

console.log(
  R.hvals('user:yuanchuan')
)

R.expire('user:yuanchuan', 60);

如果你也感兴趣,Please Fork!

在shell脚本中使用JSON作为配置文件 02 Feb 2013

编写shell脚本的时候,通常会把一些可配置的参数和处理逻辑的部分离出来,比如把参数以变量的形式放在程序的顶部,这样能使程序结构清晰一点。

当有多个shell脚本并且都会用到相同的配置参数的时,如果不愿意重复,不想将来修改一个参数而编辑多个脚本文件的话,自然地就会想到把那些可配置的参数单独放在一个文件里。

假如把配置文件的格式定义成为普遍的 “key = value” 的形式,如下所示:

database   =  dbname
collection =  test
target     =  /path/to/target         

于是现在多了写程序来解析配置文件的步骤。

使用JSON作为配置文件

你可以完全自己写一个解析程序:遍历配置文件的每一行,分离key和value,找到匹配的key, 返回与之对应的值…… 但是有个可以偷懒的办法:使用JSON来作为配置文件并用nodejs来读取它。一是格式特别的相似,二是通过nodejs读取JSON文件避免了自己去编写解析程序的步骤。

{
  "database":   "dbname",
  "collection": "test",
  "target":     "/path/to/target"
}

唯一需要的只是一段nodejs脚本来作为衔接:

#!/usr/bin/env node

var path = require('path');
console.log(
  require(path.join(
      process.cwd(), process.argv[2]
  ))[process.argv[3]] || ''
)

使用方法

为了使用便捷,把上面的nodejs脚本保存在/usr/local/bin目录下面,并把它命名为 rconfig (read config),当然,不要忘了修改属性:

sudo chmod +x /usr/local/bin/rconfig

这样,一个简单的配置文件解析工具就完成了:

rconfig config.json database     # dbname
rconfig config.json collection   # test

shell脚本里就可以这样写:

database=$(rconfig config.json datebase)
collection=$(rconfig config.json collection)

结论

这个解析配置文件的方法远非完善,没有容错处理,也不是很高效。但是作为hack它很快地解决了一个问题,同时可以看到在Linux下能嵌入多种工具的灵活性,也是一个不错的尝试。Cheers!

Ring数据结构 24 Mar 2012

A ring is much like a sequence: It holds N values associated with the integer indices zero through N-1 when N is positive. An empty ring holds no values. Values are pointers. Like the values in a sequence, values in a ring may be accessed by indexing.Unlike a sequence, however, values can be added to a ring anywhere, and any value in a ring can be removed. In addition, the values can be renumbered: ” rotating” a ring left decrements the index of each value by one modulo the length. —-《C interfaces and implementations》

Ring 和常用的桟、队列一样,是一种抽象数据类型,中文可以译成。和桟和队列不同的是,一般的教课书上没有它的介绍,也很少有人谈论到它。 实际上很常见,像在出名的淘宝网,首页中间切换图片的幻灯片,其实就是一个。当图片切换到最后一张又会从头开始切换,这样一直循环。 的特征就是这样,首尾相接,甚至可以没有首和尾。

看上去很直观,也很简单,一旦把和有相似的特征的对象抽象成,那么就会使代码更加清晰简洁,和环一样直观了。

简单的实现

文章中的例子都是基于下面 JavaScript 的简单实现,注意到它只提供了两个接口:

var Ring = (function() {
  function Ring(list) {
    this._list = [].slice.call(list, 0);
    this._length = this._list.length;
    this._curr = -1;
  };
  Ring.prototype = {
    rollTo: function(idx) {
      this._curr = idx % this._length;
      if (this._curr < 0) this._curr += this._length;
      return this._list[this._curr];
    }, 
    next: function() {
      return this.rollTo(this._curr + 1); 
    }
  };
  return function(list) {
    return new Ring(list);
  };
}());

格子

有一个非常有趣的题目,大概是这样描述的:

用程序实现九个格子和一个按钮。当点击一次按钮,第一个格子就显示 1;点击第二次,第二个格子就显示 2 … 点击第十次,因为一共只有九个格子,所以就回到第一个格子,第一个格子就显示 10;这样一直循环。

很显然,那九个格子就构成了一个。第一个格子和第九个格子是相连的,每点击一次按钮,就在下一个相邻的格子里显示总共点击的次数。 不过,一年前我并没有这样的概念,从这个 gist 可以看出那时是通过模运算,和在格子上标识出id找到下一个格子的。

使用重新实现之后更容易看懂了,: (打开 demo )

var numClicked = 0
  , cells = Ring(
      document.getElementsByTagName('td')
    );

document.getElementById('button').onclick = function() {
  cells.next().innerHTML = (++numClicked);
}

帧动画

在游戏中,通常会使用连续变换背景图的偏移位置,来模拟一个人物的奔跑动画。奔跑动画分解成许多静态的动作,每一个动作即一帧。整个背景图也是相邻的,实现的时候把每一帧的偏移坐标放在一个数组里面,从数组依次取出坐标值,然后改变背景偏移位置。当取到了数组最后一个值的时候,又从头开始取值。

同样很明显,背景图的每一个动作构成一个。在有很多这种动画的时候,抽象成能避免重复,使得代码可读性更好。(打开 demo )

var role = (function() {
  var elem = document.getElementById('role')
    , offsets = Ring(
        range(14).map(function(i) {
          return (-106) * i + 'px 0';
        })
      );
  
  return {
    run: function() {
      elem.style['backgroundPosition'] = offsets.next();  
    }
  };
}());

幻灯片切换

UCDChina书友会网站上有个切换图片的幻灯片,仔细观察会发现往回切换图片的时候有一点点问题。 但是它的实方式很复杂(源代码链接在这里),想去修复那个问题却很难把其中的逻辑理清楚,这也是因为缺乏很好的抽象。

这种图片切换和淘宝首页的图片切换是一样的,也可以使用来进行抽象。(打开 demo )

var slider = (function() {

  var slideRing = Ring(
    range(links.length).map(function(i) {
      return {
        link: links[i], offset: (-1)*w*i + 'px' 
      };
    })
  );

  function switchSlide(slide) {
    var curr = document.getElementById('on');  
    curr && (curr.id = '');
    slide.link.id = 'on';

    //using css3 animation  
    slides.style.left = slide.offset;
  };

  return {
    next: function() {
      switchSlide( slideRing.next() );
      return this;
    },
    rollTo: function(i) {
      switchSlide( slideRing.rollTo(i) );
      return this;
    }
  };

}());

可以和原来的程序做一下比较,使用来实现确实简单多了 :D

结论

我用 JavaScript 实现的 Ring 还不完整,仅仅是为了表达一个概念,根据不同的情景会进行调整。希望通过这几个例子能说明好的抽象给程序带来的好处。

有趣的fromCharCode函数 03 Nov 2011

JavaScript 中的字符串都有 charCodeAt 这个函数,如’abcd’.charCodeAt(0),它返回字符串’abcd’位于第一个字符即’a’的编码,等于97。还有一个和它功能相反的函数 fromCharCode,通过String调用,如 String.fromCharCode(97),它返回编码为97的字符,即字符’a’.

Douglas Crockford 的《JavaScript: The Good Parts》一书中也有关于这两个函数的示例。String.fromCharCode 可以接受多个参数,拿书中第93页的原例来说明一下:

var a = String.fromCharCode(67, 97, 116) 
// a is 'Cat'

String.fromCharCode 这个函数非常有趣,传给它的那些数字还可以是8进制和16进制,如果我把开始返回字符’a’的97换成1000,会返回是个什么样的字符呢?是不是很想知道? 通过这个函数就可以查看以前没见过的字符了!

输入一个字符编码试试:

生成很多字符

要想一下子生成很多字符,你可能首先想到了使用循环,比如从0一直到1000,依次用 String.fromCharCode(n) 返回对应的字符,然后打印出来。但是这样做的话调用了函数1000次,效率降低了。前面说过 String.fromCharCode 能接受多个参数,所以要是能够这样做就好了:

String.fromCharCode(1, 2, 3, 4, 5, 6..., 1000);

幸运的是,JavaScript的apply可以变换函数的调用方式,能通过一个数组把所有参数传递给函数。就像下面这样:

String.fromCharCode.apply(null,  [1,2,3,4,5,6,..., 1000]);

其中第一个参数是函数的”执行环境(context)”(我先不在这里解释什么是”执行环境”。要解释”执行环境”就要解释神秘的 this 关键字,希望我能在以后的文章中再把它们讲解清楚)。那么,现在就可以单独生成一个数组。之后只须调用一次函数就行了。

//首先生成编码数组
var code = [1, 2, 3, 4, 5, 6, 7, 8, 9, ... , 1000];

//再把编码数组传递给函数,作为参数调用
String.fromCharCode.apply(null,  code );

下面的几行代码会打印出编码从1到1000的字符:

(function(){
  var code = [], from = 1, to = 1000;
  while (from <= to) {
    code.push(from++);
  }
  console.log(
    String.fromCharCode.apply(null, code)
  );
})();

在google-chrome控制台下运行的结果:

编码从1到1000的字符

想查看更多没见过的字符吗?我特意写了一个 Demo 页面,希望你能用它发现特别的东西!