###声明 目前spring只支持MRI 1.9.3, MRI 2.0.0, Rails 3.2,没有达到要求的人赶紧升级你们的ruby,rails版本吧

###问题 想必采用TDD/BDD方式进行开发的rails开发者都有着这样类似的经历:

  1. pair写了一个测试
  2. 运行测试
  3. 等待
  4. 该我来编写产品代码
  5. 运行测试
  6. 等待
  7. 代码有bug
  8. 测试失败
  9. 修复测试
  10. 运行测试
  11. 等待
  12. 测试通过,yeah!

再回过头来想想,我享受这段pair的过程吗?

  • pair很给力,很快就把一个taks实现成一个测试用例
  • 桌子上的水果也很好吃。
  • 。。。

可是,我总觉得有点不爽快,原来是那么多的等待,每运行一次测试,就需要等待十几秒甚至几十秒,每天我会运行上千次测试,这是多大的浪费?做为一个有追求的程序员,我当然不愿意把宝贵的工作时间浪费在这无谓的等待中去 :-)。

###现有方案 有追求的程序员还是大多数,google之后才发现已经有人尝试解决这个问题,如sporkzeus。他们的原理都是预先把rails环境启动起来,后面在运行测试,执行rake task时从这个启动好的进程fork一个进程,在这个进程中执行操作。然而,spork需要修改spec_helper.rb,并且需要单独启动一个server进程,zeus虽然不需要修改项目代码但仍然需要单独启动一个server进程,用起来还不是很爽快。 spring带来了更加易用的方案。

###安装 建议把spring安装到rvm的global gemset中去,这样就可以在多个project使用spring

安装命令非常简单:

gem install spring

###使用

执行测试的命令也非常简单:

spring rspec

当第一次使用spring运行测试,rake taks, db migration时,spring会自动在后台load rails 环境,因此执行速度也很慢,但是当再次执行时,spring会从先前的进程中fork出load好的rails环境,执行速度就变得飞快!

###已知问题 把 require 'rspec/autorun'从spec_helper中删掉,否则,spec会被执行两次,而且第二次会由于找不到url helper method而失败。

Failure/Error: visit posts_path
 NameError:
   undefined local variable or method `posts_path' for #<RSpec::Core::ExampleGroup::Nested_2:0x007fcf650718e0>
 # ./spec/integration/posts/posts_spec.rb:10:in `block (2 levels) in <top (required)>'
 # -e:1:in `<main>'

感谢Stefan Wienert的分享。

###总结 spring把对项目代码的影响减少到了没有,并且能够去掉加载rails环境的时间,极大地提升rails开发者的效率,是现有rails开发者必不可少的利器。enjoy coding!!!

在这篇文章里,我将介绍如何使用capistrano部署rails应用。

####准备工作:

  1. 创建一个用于部署的用户(eg. deployer)
  2. 安装本地依赖:
  3. 安装配置nginx: yum install nginx
    • config nginx ln -s /path/to/app/config/nginx.conf /etc/nginx/nginx.conf
    • restart nginx service nginx restart

####Capify 在工程根目录下执行:

capify .

####配置ruby 我使用capistrano-rbenv gem管理production环境的ruby版本,在Gemfile中增加如下一行:

`gem 'capistrano-rbenv'`

然后在config/deploy.rb中增加如下两行:

require 'capistrano-rbenv'
set :rbenv_ruby_version, '2.0.0-p0'

####配置用于deploy的信息 在config/deploy.rb中配置capistrano所需要的信息:

//...
#配置目标服务器的地址,角色
server '192.168.1.100', :web, :app, :db, :primary => true
set 'application', "my-app"
#代码库的地址
set :repository, 'deployer@github.com:my-app.git'
set :branch, 'master'
set :scm, :git
set :user_sudo, false
#用于部署的用户
set :user, 'deployer'
#部署的目标目录
set :deploy_to, "/home/#{user}/apps/#{application}"
default_run_options[:pty] = true
ssh_options[:forward_agent] = true
//...

#####部署:

cap deploy

本文简要介绍如何使用nginx和unicorn在ubuntu上搭建rails应用部署环境。

####前置条件

  • 部署用户 deployer
  • 程序已部署到$HOME/apps/blog/current

####安装nginx apt-get install nginx ####安装unicorn gem install unicorn ####配置nginx
在工程目录下创建一个nginx的配置文件 - $HOME/apps/blog/current/config/nginx.conf,把nginx的根目录指向rails应用的静态文件目录下。

root /home/deployer/apps/blog/current/public;

为upstream server(如unicorn,passenger)指定一个唯一的socket文件。

upstream app_server { server unix:/tmp/unicorn.blog.sock** fail_timeout=0; }

创建一个从$HOME/apps/blog/current/config/nginx.conf/etc/nginx/nginx.conf的soft link。

####配置unicorn 创建一个unicorn配置文件$HOME/apps/blog/current/config/unicorn.rb,配置unicorn使用同样的socket文件。

listen "/tmp/unicorn.blog.sock", :backlog => 64 把working directory指向项目部署的路径。	

APP_PATH="/home/deployer/apps/blog/"
working_directory APP_PATH + "/current"

####启动nginx /etc/init.d/nginx start

####启动unicorn cd $HOME/apps/blog/ && \ bundle exec unicorn -D -c $HOME/apps/blog/config/unicorn.rb -E production

###背景

KNN,全称K-nearest-neighbour,是机器学习中最简单的一个分类算法,它的原理是通过对样本数据的学习,对于给定的新的数据,找出与其距离最近的K个样本数据,根据这K个最近样本数据的类别,来确定这个给定数据的类别。

Coolshell上有对这个算法的讲解,我的同事邱俊涛也写了一篇关于KNN算法python实现的文章。本文讲解一个KNN算法的ruby实现。

###输入 程序输入格式如下:

x0,x1,x2,…xn|v0
y0,y1,y2,…yn|v1
z0,z1,z2,…zn|v2

每行为一个数据样本,以第一行为例,x0,x1…xn为一个向量,v0为该数据的类别。

###学习 从给定文件加载样本数据:

def train file_path
  @samples = from_file(file_path)
end

@sample的格式如下:

[
	{:vector => [x0, x1, x2, …xn], :value => v0},
	{:vector => [y0, y1, y2, …yn], :value => v1},
	…
	{:vector => [z0, z1, z2, …zn], :value => vn},
]

###分类 对于给定的数据,要判断其属于样本数据中的哪一类,需解决如下几个问题:

  1. 计算给定数据和样本数据之间的距离
  2. 找出与给定数据距离最小的K个样本数据
  3. 从这K个样本数据中找出样本多的那个分类,即为给定数据的分类。

#####1. 计算距离 给定两个向量[x0, x1,…xn][y0, y1,...yn]计算两个向量之间的距离如下:

(x0 - y0)^2 + (x1 - y1)^2 + … + (xn - yn)^2

因此,对于给定的两个向量a,b,其距离计算逻辑如下:

#a and b are two vectors
def distance_between a, b
  a.zip(b).map {|x| x[0] - x[1]}.inject(0){|sum, x| sum += x*x}
end

#####2. 找出与给定数据距离最小的K个样本数据 可以采用计算给定数据与所有样本数据的距离,然后采用最大堆来找出__top k__个样本数据。

def nearest_neighbours candidate, k
  heap = MaxHeap.new
  @samples.each do |sample|
    distance = distance_between(sample[:vector], candidate)
    heap.insert Node.new(distance, sample)
  end
  heap.take_top(k).compact.map(&:sample)
end

#####3. 从这K个样本数据中找出样本多的那个分类,即为给定数据的分类。 对得到的样本根据其类别进行分组,组内元素多的那个类别,即为该给定数据的分类

def value_with_max_vote xs
  value_with_votes = xs.group_by{|x| x[:value]}.map{|value, group| {:value => value, :votes => group.length}}
  value_with_votes.max_by{|x| x[:votes] }[:value]
end

综合上面的几个小任务,我们得到KNN分类算法的实现:

def categorize candidate, k
  neighbours = nearest_neighbours_for candidate, k
  value_with_max_vote neighbours
end

代码的完整版本可以在这里找到。

Note: 本文中的程序来自Martin Ordersky在www.coursea.org上所开设课程中的样例代码,文章主要目的是讲述我对此类问题解法及scala lazy evluation的理解。

###问题描述 给你一个容量为9升的杯子和一个容量为4升的杯子,水不限使用,要求精确得到6升水。这就是“倒水问题”。我这里会讲述一个试用scala穷举法实现的一个例子。

###建模 首先我们对这个问题进行建模。这个问题可以泛化为如下形式:

两个容量固定的杯子,可选的动作有:  
	+ 加满
	+ 倒空
	+ 从一个杯子倒入另一个杯子

初始状态为两个杯子都空,结束状态为其中一个杯子中的水量为所期望的结果。 从上面的描述中我们可以得到如下几个概念**状态**,**动作**,**路径**。

状态用于表述各个杯子中当前的水量,动作用来改变各个杯子中水量,路径用于表示到达某一状态的动作序列。

####状态 我们可以用一个Vector[Int]来表述杯子的状态,下标为杯子编号,元素值为当前杯子中的水量,另外,我们还需要一个Vector[Int]表示杯子的容量,下标为杯子编号,元素值为杯子的容量。于是我们得到如下代码:

	class Pouring(capacity: Vector[Int]) {
		//States
		val initialState = capacity map (x => 0)
	}	

capacity为杯子容量, intialState为问题初始状态。

####动作 由上可知,解决这个问题可以有三种动作, Empty, Fill, Pour,每个动作都会导致状态发生变化,于是我们得到如下代码:

	//Moves
	trait Move { 
		def change(state: Vector[Int]): Vector[Int] 
	}
	
	case class Empty(glass: Int) extends Move {
		def change(state: Vector[Int]): Vector[Int] = state updated (glass, 0)
	}
	
	case class Fill(glass: Int) extends Move {
	    def change(state: Vector[Int]): Vector[Int] = state updated (glass, capacity(glass))
	}
	
	case class Pour(from: Int, to: Int) extends Move {
		def change(state: Vector[Int]): Vector[Int] = {
	      val amount = state(from) min (capacity(to) - state(to))
    	  state updated (from, state(from) - amount) updated (to, state(to) + amount)
	    }
	}

注意因为要考虑杯子的容量和杯子中剩余的水量,Pour中的change方法稍微复杂了些。

####路径 接下来到了最重要的路径部分了,路径是到达某一状态的动作序列,我们需要一个List[Move]表述动作序列,一个Vector[Int]表述结束状态。 还需要一个extend方法来扩展当前的动作序列,即在当前路径上,应用一个动作(Fill, Empty, Pour),得到一个新的路径。于是我们得到如下代码:

	//Paths
	class Path(history: List[Move], val endState: Vector[Int]) {
    	def extend(move: Move) = new Path(move :: history, move change endState)
		override def toString = (history.reverse mkString " ") + " -->" + endState
	}

###算法 这里我们要采用的是穷举法:

穷举从初始状态出发所有可能的动作,以及可能达到的状态,再穷举从这些状态出发所有可能的动作以及可能达到的状态,如此反复,直到找到一个可能达到的状态满足期望,则到达这个状态所经历的所有动作组成的路径即为问题的解。

首先我们来穷举给定一组杯子可能的动作:

	val glasses = 0 until capacity.length
	val moves =
		(for (g <- glasses) yield Empty(g)) ++
		(for (g <- glasses) yield Fill(g)) ++
		(for (from <- glasses; to <- glasses if from != to) yield Pour(from, to))	

其次,在不进行任何动作时,动作列表为空,所达到的状态为初始状态,则:

	val initialPath = new Path(List(), initialState)

接下来到最关键的部分了,穷举从初始状态出发的所有可能扩展出来的路径及其所达到的状态,由于从任何状态开始穷举,都会得到一个一组路径,而不是一个,于是我们首先定义一个从给定一组路径,穷举其可能扩展出来的的路径的方法:

	 def from(paths: Set[Path], explored: Set[Vector[Int]]): Stream[Set[Path]] = {
	    if (paths.isEmpty) Stream.empty
    	else {
			val more = for {
				path <- paths
				next <- moves map path.extend
				if !(explored contains next.endState)
			} yield next
      		paths #:: from(more, explored ++ (more.map(_.endState)))
        }
  	}

paths为此次穷举的初始路径集合, explored用于记录已经穷举过的状态,以避免找出多条达到相同状态的路径,此方法通过穷举初始路径集合,在各个路径上扩展所有的动作,去掉那些达到状态已经被穷举过的路径,得到一组新的路径。

那么,从初始路径出发,其可能扩展出来的路径极其可能达到的状态如下:

	val pathSets = from(Set(initialPath), Set(initialState))

对于给定的目标水量,遍历上述穷举结果路径找出endState包含目标水量的路径,如下:

	
	def solutions(target: Int): Stream[Path] = {
		for {
			pathSet <- pathSets
			path <- pathSet
			if path.endState contains target
    	} yield path
  	}

###总结 我们知道,穷举是一个无穷无尽的过程,上面的程序是如何运行的呢?其实是scala中强大的lazy load发挥了作用。我们再来看from方法的返回值类型,是Stream[Set[Path]], Stream 用于表述元素序列,它的一个重要特点是,只有需要使用到其中的某个元素时,程序才会去计算这个元素。于是,在程序运行时,scala并不会一下子把所有可能的路径都计算出来。对于solutions方法,也是一样。因此,scala只有在从solutions的计算结果中获取满足条件的路径时,pathSet才会穷举可能的路径,并且在找到满足条件的路径后,计算会立即结束,不会再列举其余的可能。