10 October 2014

缘起

好歹也是个Ruby工程师,说出去怕被人笑话。和Ruby相比,我其实更熟悉shell,因为后者的学习时间更长,也常写一些自用的函数和别名。

工作时,遇到的有些任务可以自动化处理,简单也许两三条命令搞定,复杂的可能需要一个脚本。以下是自己的写的一些脚本的例子。内容持续添加。

其一:将汉字转换为拼音,并提取其首字母,形成首字母大写的简写

# convert.rb - 这是一个用完就扔的转换文本的脚本
# 输入文件: 每行两列(中文名,代码),以tab分隔,
# 输出文件:每行三列(中文名,代码,首字母拼音),以tab分隔
# 辅助函数:汉字转拼音的gem包
# 小记:第一次编写Ruby脚本,感觉还不错
require "hz2py"

input = "/home/xiajian/works/test/new_stock_less.txt"
output = "/home/xiajian/works/test/stock.txt"

f = File.open(input ,"r+")
o = File.new(output,"w+")

def hz2py(name)
  tmp=""
  Hz2py.do(name).split(" ").each { |x| tmp << x[0] }
  tmp.upcase
end

p "convert start ........"
f.each_line do |line| 
  st = line.chomp.split("\t")
  o.puts("#{st[0]}\t#{st[1]}\t"+hz2py(st[0]))
end
# 关闭文件,之前一直没有关闭文件,担心有问题,所以添加了文件关闭
f.close
o.close
p "convert over .........

其二 测试获取传递参数的脚本

# 这是一个用来测试Ruby读写特定文件测试脚本
# 文件的格式,一行一列,以tab作为分隔
if ARGV.size == 0
  file="/home/xiajian/works/test/new_stock.txt"
else
  file="/home/xiajian/works/test/#{ARGV[0]}"
end

f= File.open(file ,"r+")
p "Print testing......"
f.each_line do |line| 
  tmp= line.chomp.split("\t")
  tmp.each do |x|
    p x
  end
end
f.close
p "testing is over....."

# Test input: ruby readfile.rb xijain
# Output: 
# This is test for get something,xijain
# The second way: ["xijain"]  - #{ARGV}
# The second way: xijain - #{ARGV[0]}
#
# 第一种获取命令行参数的方式
# puts "This is test for get something," + $*[0]

# 第二种获得命令行参数的方式
# puts "The second way: #{ARGV[0]}"
# puts "Total arguement number:#{ARGV.size}"

其三 将包含@xxx+[:| ]的字符串转换为链接

i = "@user: ,@xijia @tete @jieeefe, @dfaewf: @121L: @jifjei"
i.gsub(/@.+?[:| ]/).each { |x| "<a href="+">#{x}</a>" }

其四 将某个文件下全部的.erb文件名替换为.haml文件

Dir["**/*.erb"].each do |file|
  file.split("erb").each do |x|
    n = x + "haml"
    p 
    system "mv #{file} #{n}"
  end
end
# Ruby中执行shell命令的方式: exec "" , `` , system ""等等
# 文件的命名如下areas/show.html.erb,偶然发现,split以字符串分割挺方便的。

经验: 在irb中测试,然后在将语句汇总成脚本。这样比编写脚本,然后运行测试快多了。当然,shell也可以这么处理,事实证明,成功的概率大多了。

其五 计算留存率的问题

# 原本想使用前置++和后置++的,由于在代码块中闭包的问题,没能成功。
# Rails中数字和时间处理方法确实灵活
c, d, h = 0,0,0
User.where("created_at >= ? ", 6.days.ago).each do  |x|
   c+=1
   d+=1 if (x.last_sign_in_at - x.created_at) >= 1.days
   h+=1 if (x.last_sign_in_at - x.created_at) >= 1.hours
end
p "间隔一天的三日留存率为: #{(d.to_f/c*100).round(2)}%"
p "间隔一小时的三日留存率为: #{(h.to_f/c*100).round(2)}%"

留存率的扩展版本:

def exist_rate(date, num)
  c = User.where(:created_at => (date .. date.tomorrow) ).count
  return [0.0 , 0 , 0] if c == 0
  s = User.where(:created_at => (date..date.tomorrow) ).where(:last_sign_in_at => (date+1.days .. date+num.days ) ).count
  [(s.to_f/c*100).round(2), c , s ]
end
      
o = File.new("/home/lodestone/users.txt","w+")
o.puts("日期\t3日留存率\t7日留存率\t15日留存率\t30日留存率\t")
(Date.parse("2014-7-1")..Date.today).to_a.map(&:at_beginning_of_day).each do |date|
  rate = []
  [3,7,15,30].each do |x|
    rate << exist_rate(date, x+1 )
  end
  o.puts "#{date.to_date}\t#{rate[0][0]}(#{rate[0][2]}/#{rate[0][1]})\t#{rate[1][0]}(#{rate[1][2]}/#{rate[1][1]})\t#{rate[2][0]}(#{rate[2][2]}/#{rate[2][1]})\t#{rate[3][0]}(#{rate[3][2]}/#{rate[3][1]})"
end
o.close

清空数据的方法:

UserStatistics.each do |x|
  x.rate = []  # 之前尝试`=||`操作,忽略了其根本的语义。
  x.save!
end

对于历史遗留的数据,采用的rake任务导入,并使用新打开的类UserStatistics的中的方法。

desc "设置留存率的历史计算"
 task  :statistics => :environment do
   (Date.parse("2014-7-1")..Date.today).each do |day|
     UserStatistics.calc_rate(day)
 end
 p "done"
end

下面是一个打开的model类:

class UserStatistics
  # 三日留存率, date表示日期,num为留存率参数
  def exist_rate(date, num)
    c = User.where(:created_at => (date .. date.tomorrow) ).count
    return 0.0 if c == 0
    s = User.where(:created_at => (date..date.tomorrow) ).where(:last_sign_in_at => (date+1.days .. date+num.days) ).count
    (s.to_f/c*100).round(2)
  end
  
  # 留存率的部分,分别计算三日,7日,15日以及30日留存率,三天即需要向前推4天。
  def self.calc_rate(date)
    [3,7,15,30].each_with_index do |x,idx|
      start = date - (x+1).days
      us = UserStatistics.where(:date => date).first
      if !us.blank? && us.rate[idx].blank?
        us.rate[idx] = us.exist_rate(start,x+1)
        us.save!
      end
    end
  end
end

备注:这里第一次学到了如何给类对象编写方法,self.xxx表明这是一个类方法而不是实例方法。

后记

等我以后NB了,这些就是我在NB道路上的足迹。哈哈!!




傲娇的使用Disqus