实现函数链式计算

今天在codewars上做题时发现一道很不错的题目,写下来分享。
The goal is to implement simple calculator which uses fluent syntax:

Calc.new.one.plus.two # Should return 3
Calc.new.five.minus.six # Should return -1
Calc.new.seven.times.two # Should return 14
Calc.new.nine.divided_by.three # Should return 3

There are only four operations that are supported (plus, minus, times, divided_by) and 10 digits (zero, one, two, three, four, five, six, seven, eight, nine).
Each calculation consists of one operation only.
TestCases;

Test.assert_equals(Calc.new.four.plus.five, 9)
Test.assert_equals(Calc.new.five.plus.five,10)

分析:第一种思路是用一个类变量将三个方法存储在一个字符串里,然后调用eval方法运行字符串。用一个哈希将英文字符和阿拉伯数字及运算符对应起来,将对应的字符存储到@instance_variable里面,最后当长度为3(调用3次方法)的时候运行字符串。

class Calc
   NUM = {
      one: '1',two: '2',three: '3',four: '4',five: '5',six: '6',seven: '7',eight:  '8',
nine: 9,zero: 0,plus: '+',minus: '-',times: '*', divided_by: '/'
  }
  def method_missing name
     @str = "#{@str} #{NUM[name]}".strip
     @str.split.size == 3 ? eval(@str) : self
  end
end

第二种思路分析,一共要调用三次实例方法,第一次返回Fixnum实例对象,然后给Fixnum猴子补丁(plus,minus,times,divided_by),第二次方法调用时候返回Calc实例,同时将operation和对象本身传递进Calc对象里,然后再次调用数字(eg:four)的时候也能返回Fixnum对象。解决思路如下:

class Fixnum
  def plus; Calc.new('+', self) end
  def minus; Calc.new('/', self) end
  def times; Calc.new('*', self) end
  def divided_by; Calc.new('/', self) end
end

class Calc
  def initialize(*args)
    if args.size == 2
      @operation = args[0]
      @number    = args[1]
    end
 end

%w(zero one two three four five six seven eight nine).each_with_index do { |w,i|
  define_method(w) do
    if @operation
      @number.send(@operation, i)
    else 
     i
    end
  end
}
end

第三种思路分析,通过代码块来延迟执行计算。代码如下:

class Calc
  %w(zero one two three four five six seven eight nine).each_with_index{ |w,i|
    define_method(w) do
      @blk ? @blk.call(i) : (@number = i;self)
    end
  }
  {plus: '+', minus: '-', times: '*', divided_by: '/'}.each {|k,v|
    define_method(k) do
      @blk = lambda{ |n| @number.send(v, n) }
      self
    end
  }
end

测试代码:

require 'test/unit'

def test_calc
  assert_equal(Calc.new.one.plus.nine, 10)
  assert_equal(Calc.new.eight.minus.two, 4)
  assert_equal(Calc.new.seven.times.three, 21)
  assert_equal(Calc.new.six.divided_by.four, 1)
  assert_equal(Calc.new.five.plus.four, 9)
end

以上三种方法都离不开define_method,method_missing,代码块,可见其在ruby中的重要性。

你可能感兴趣的:(实现函数链式计算)