Ruby 大家看看这个代码能改进不

fsword · 2012年10月19日 · 最后由 fsword 回复于 2012年10月22日 · 3317 次阅读

公司内部编程游戏,我给了一个 ruby 一行版,但是自己也觉得看起来很 sb,求不土鳖的做法

Dir['*'].each{|dir| print "#{dir}: "; Dir.chdir(dir){ puts Dir['*.txt'].map{|x| File.read(x) =~ /max:(.+) min:(.+)/ ;; [$1.to_i - $2.to_i, x]}.max.to_s}}

多行版是这样的

Dir['*'].each do |dir| 
  print "#{dir}: "
  Dir.chdir(dir) do 
    puts Dir['*.txt']
                     .map{ |x|
                       File.read(x) =~ /max:(.+) min:(.+)/
                       [$1.to_i - $2.to_i, x]
                     }.max.to_s
  end
end

题目

统计出全国所有城市在 1996 年 01 月 01 日 -2012 年 9 月 31 日。平均昼夜温差 (最高温度减最低温度) 最大的一个月

条件

一个目录给出了各城市的每日昼夜温差,形如这样:

HANGZHOU
   |
   |---1996-01-01.txt 
   |.........
   ----2012-09-31.txt
QIANNAN
SIPING
WUXI
YINGKOU

1996-01-01.txt 这样的文件内容只有一行

max:-1 min:-10

先换行吧

#1 楼 @Rei 呵呵,是为了突出所谓一行

平均昼夜温差(最高温度减最低温度)最大的一个月 你好像算的是最大的一天吧,而不是一个月。

文件查找那一块不知道可以改成 Dir['**/*.txt'] 不?

Dir['**/*.txt'].map{|file| File.read(file) =~ /max:(.+) min:(.+)/;[$1.to_i - $2.to_i, file]}.max_by{|x| x[0]}.to_s

#5 楼 @cxh116 这个游戏不管这个 #6 楼 @LarryLv 你说的对,我好像理解错了

#8 楼 @hooopo 不行的,这个题目是每个城市单独计算,不能混在一起

刚发现 max 函数可以直接比较数组,所以不用带 block 了,纯 c 实现,性能会好很多

#7 楼 @cxh116 他需要知道是哪个城市?

#10 楼 @fsword 那就先 group by 一下啊

还有比较数组用 max 一定没有 max by 性能好

恩,我理解错误题目了,大家先出一个正确的版本吧,没有正确性,其它都是没用的

#14 楼 @fsword 不能这么说。。和 sort&sort_by 一样,影响因素比较多。像你这种简单的比较还是 max 快..

max: https://gist.github.com/3916880 sort: https://gist.github.com/3916850

一行版本

p Dir['**/*.txt'].map { |file| File.read(file) =~ /max:(.+) min:(.+)/; [file.split('-')[0..-2].join('-'), $1.to_i - $2.to_i] }.group_by {|x| x[0]}.map { |k, v| [k, v.map(&:last).inject(:+).to_f/v.size] }.group_by {|x| x[0].split('/')[0]}.map {|k, v| [k, v.max_by(&:last).last] }

多行版本

p Dir['**/*.txt'].map { |file|
  File.read(file) =~ /max:(.+) min:(.+)/
  [file.split('-')[0..-2].join('-'), $1.to_i - $2.to_i]
}.group_by {|x| x[0]}.map { |k, v|
  [k, v.map(&:last).inject(:+).to_f/v.size]
}.group_by {|x| x[0].split('/')[0]}.map {|k, v|
  [k, v.max_by(&:last).last]
}

修正版:

Dir['*'].each do |dir|
  puts "%15s: %.4f %s" % [dir, Dir["#{dir}/*.txt"]
    .map{|x| File.read(x) =~ /max:(.+) min:(.+)/ ; [$1.to_i - $2.to_i, x.split('/')[1]] }
    .group_by{|a| a[1][0..6]}
    .map{|k,v| [v.map{|a| a[0]}.reduce(:+).to_f/v.size, k]}
    .max].flatten
end

OO 版,未测试

class City
   attr_reader :name

   def initialize(dir)
      @files = Dir["#{dir}/*.txt"]
      @name = dir.chomp('//')
      @temperatures = files.map do |file|
         [max, min] = Temperature.new(file)
      end
   end 

   def maximum_deference
      @temperatures.max_by { |m,n| m-n}
   end
end

class Temperature
   def initialize(file)
      parser.parse(File.read(file))
   end

   def parser
      @parser ||= new DateParser
   end
end

class DateParser
   def parse(data)
      if  m =  /max:(.+) min:(.+)/.match(data)
         [m[1].to_i, m[2].to_i]
      else 
         [0,0]
      end
   end
end

# main
cities = Dir["**/"].map do |dir|
   City.new(dir)
end
cities.echo do |city| 
   puts "#{city.name} maximum temperature defference #{city.maximum_difference)}"
end

Perl 控,再来个 Java 控的版本吧。 -__,-

#20 楼 @fredwu #19 楼 @hysios 看了 OO 版,我绝对不会再想写 java 版了 :-P

简单文本解析用 scanf 最方便了

require 'scanf'
'max:-1 min:-10'.scanf 'max:%d min:%d' #=> [-1, -10]

OO 是工具不是目的,不要为 OO 而 OO。另外写个 class 也不是 OO,只是写了个 class 而已...

Dir.glob("*/**/*.txt").group_by{|f| f =~ /(.+)\//;$1}.map{|k,v| { k => v.max_by{open(k).read =~ /max:(.+) min:(.+)/;-[$1.to_i - $2.to_i].abs}}}

#22 楼 @luikore 完全赞同啊。。 不过你这么写又成了“Ruby 黑魔多”的证据,然后你就成了外国人, 最后把你挂到Rubydramas上面..

#22 楼 @luikore 恩,这个不错,多一行 require,不过可以简化最长的那句

require 'scanf'
Dir['*'].each do |dir| 
  puts "%15s: %.4f %s" % [dir, Dir["#{dir}/*.txt"]
    .map{|x| [File.read(x).scanf('max:%d min:%d/').reduce(:-), x] }
    .group_by{|a| a[1][-14..-8]}
    .map{|k,v| [v.map{|a| a[0]}.reduce(:+).to_f/v.size, k]}
    .max].flatten
end

性能暂且不论,可读性有提高,可惜还是做不到 one-liner

#25 楼 @fsword 把代码再包装成 gem,就可以真正一行了 -__-

我也来个 OO 版,甚至有点为了过分追求 OO 以及软件的重构而 OO.

不过代码还算清晰,基本上都是不可再分的小方法,也没有太多的参数传递,在几个月后再看代码,比单行脚本易懂一些。

(好吧,我承认,使用单行的方式来解决这么复杂的应用,我还真写不出来呢)

class City
  def initialize(city_path)
    @city_path = city_path
  end

  def average_arrays
    month_days_hash.map {|month, days| [name, month, Parser.parse_temperature(days)/days.length] }
  end

  def month_days_hash
    date_files.group_by do |date_file|
      File.basename(date_file, ".txt").slice(/(\d{4}-\d{2})-\d{2}/, 1)
    end
  end

  def date_files
    Dir["#{@city_path}????-??-??.txt"]
  end

  def name
    File.basename(@city_path)
  end

  class Parser

    def self.parse_temperature(arrays)
      arrays.inject(0) do |sum, file|
        string = File.read(file)
        sum += parse(string)
      end
    end

    def self.parse(string)
      string =~ /max:(.+) min:(.+)/
      ($1.to_f - $2.to_f).abs
    end
  end

end

city_directorys = Dir['city/*/']

report = city_directorys.map do |city_directory|
city = City.new(city_directory)
# 一个二维数组, 每个元素是 ["城市名", "年-月", 月平均温差(浮点数)] 形式
city.average_arrays.max_by {|e| e[2] }
end

report.each {|city|  printf "%-10s在 %5s 拥有最高温差: %0.1f", *city }

下面是补上的测试,使用 Minitest.

require 'minitest/autorun'
require 'awesome_print'
require 'minitest/pride'

require_relative "test3"

describe City do
  before do
    @city = City.new("city/hangzhou/")
  end

  it "must return city name" do
    @city.name.must_equal "hangzhou"
  end

  it "must return a month_days_hash" do
    def @city.date_files
      ["city/hangzhou/1996-01-03.txt",
       "city/hangzhou/1996-01-02.txt",
       "city/hangzhou/1996-02-02.txt",
       "city/hangzhou/1996-02-03.txt",
       "city/hangzhou/1999-12-25.txt",
       "city/hangzhou/1996-01-01.txt"]
    end

    @city.month_days_hash.must_equal({"1996-01"=>["city/hangzhou/1996-01-03.txt", "city/hangzhou/1996-01-02.txt", "city/hangzhou/1996-01-01.txt"], "1996-02"=>["city/hangzhou/1996-02-02.txt", "city/hangzhou/1996-02-03.txt"], "1999-12"=>["city/hangzhou/1999-12-25.txt"]})
  end

  specify { City::Parser.parse("max:20 min: -5").must_equal 25 }

end

@fsword , 你有没有那些温度的文件呢?

有的话,打包给我发一份。[email protected], 我也好实际测试下效果如何。

#28 楼 @zw963 这些数据是可以伪造的,我用的数据也是伪造的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号