添加(*数字)
让我们从调用开始:
def add(*numbers)
numbers.inject(0) { |sum, number| sum + number }
end
像这样:
add(1,2,3) #=> 6
或者像这样:
add(*[1,2,3]) #=> 6
两者是等价的。后者向您展示了运算符“splat”的作用。
这会导致:
numbers #=> [1,2,3]
所以 Ruby 将Enumerable#inject(又名reduce)发送到numbers:
[1,2,3].inject(0) { |sum, number| sum + number }
inject 首先将“备忘录”sum 初始化为inject 的参数(如果像这里一样,它有一个),然后将“接收者”[1,2,3] 的第一个元素传递到块中,然后将其分配给块变量number:
sum #=> 0
number #=> 1
Ruby 然后计算:
sum + number #=> 0 + 1 => 1
成为备忘录的新值。接下来,inject 将2 传递到块中并计算:
sum #=> 1
number #=> 2
sum + number #=> 3
所以(备忘录)sum 现在是 3。
最后,
sum #=> 3
number #=> 3
sum + number #=> 6
由于接收者的所有元素都已传递到块中,inject 返回备忘录的值:
sum #=> 6
如果您检查 inject 的文档,您会发现如果该方法没有参数,Ruby 会将接收者的第一个元素(此处为 1)分配给备忘录(sum),然后继续以上从接收器的第二个元素(2)开始。正如预期的那样,这会产生相同的答案:
def add(*numbers)
numbers.inject { |sum, number| sum + number }
end
add(1,2,3) #=> 6
那么为什么要包含参数零呢?通常我们会希望add()(即add(*[])) 返回零。我将留给您调查inject 的两种形式中的每一种会发生什么情况。您能得出什么结论?
正如@Stefan 在他的回答中指出的那样,您可以简单地这样做:
def add(*numbers)
numbers.inject :+
end
这是你通常看到的写法。
但是,如果 numbers 可能是一个空数组,您需要为备忘录提供一个零初始值:
def add(*numbers)
numbers.inject 0, :+
end
add(*[]) #=> 0
减(*数字)
def subtract(*numbers)
current_result = numbers.shift
numbers.inject(current_result) { |current_result, number|
current_result - number }
end
这与add 的方法类似,只是稍有不同。我们需要备忘录的第一个值(这里是current_result)作为接收者的第一个元素。我们有两种方法可以做到这一点。
第一种方式是这样的:
def subtract(*numbers)
numbers[1..-1].inject(numbers.first) { |current_result, number|
current_result - number }
end
numbers = [6,2,3]
subtract(*numbers) #=> 1
让
first_number = numbers.first #=> 6
all_but_first = numbers[1..-1] #=> [2,3]
然后:
numbers[1..-1].inject(numbers.first) { ... }
是:
all_but_first.inject(first_number) { ... }
#=> [2,3].inject(6) { ... }
相反,作者选择写:
first_number = numbers.shift #=> 6
numbers #=> [2,3]
numbers.inject(first_number) { ... }
#=> [2,3].inject(6) { ... }
这可能有点漂亮,但选择权在你。
第二种方式是不带参数使用inject:
def subtract(*numbers)
numbers.inject { |current_result, number| current_result - number }
end
numbers = [6,2,3]
subtract(*numbers) #=> 1
您可以通过查看inject 的文档来了解其工作原理。
另外,类似:add,可以写成:
def subtract(*numbers)
numbers.inject :-
end
最后,subtract 要求numbers 至少有一个元素,所以我们可以这样写:
def subtract(*numbers)
raise ArgumentError, "argument cannot be an empty array" if numbers.empty?
numbers.inject :-
end
计算(*参数)
我们看到calculate 期望以下列方式之一被调用:
calculate(6,2,3,{ :add=>true }) #=> 11
calculate(6,2,3,{ :add=>7 }) #=> 11
calculate(6,2,3,{ :subtract=>true }) #=> 1
calculate(6,2,3,{ :subtract=>7 }) #=> 1
calculate(6,2,3) #=> 11
如果散列的键 :add 具有“真实”值(除 false 或 nil 之外的任何值,我们将添加;如果散列具有键 :subtract 具有“真实”值(除false 或nil 之外的任何内容,我们都要减去。如果最后一个元素不是散列(calculate(6,2,3)),则假定为add。
注意:
calculate(6,2,3,{ :add=>false }) #=> nil
calculate(6,2,3,{ :subtract=>nil }) #=> nil
让我们这样写方法:
def calculate(*arguments)
options =
if arguments.last.is_a?(Hash) # or if arguments.last.class==Hash
arguments.pop
else
{}
end
if (options.empty? || options[:add])
add *arguments
elsif options[:subtract]
subtract *arguments
else
nil
end
end
calculate(6,2,3,{ :add=>true }) #=> 11
calculate(6,2,3,{ :add=>7 }) #=> 11
calculate(6,2,3,{ :subtract=>true }) #=> 1
calculate(6,2,3,{ :subtract=>7 }) #=> 1
calculate(6,2,3) #=> 11
calculate(6,2,3,{ :add=>false }) #=> nil
calculate(6,2,3,{ :subtract=>nil }) #=> nil
注意return 关键字不是必需的(在原始代码中也不需要)。使用散列来表示要执行的操作类型似乎很奇怪。调用该方法会更有意义:
calculate(6,2,3,:add) #=> 11
calculate(6,2,3) #=> 11
calculate(6,2,3,:subtract) #=> 1
我们可以这样实现:
def calculate(*arguments)
operation =
case arguments.last
when :add
arguments.pop
:add
when :subtract
arguments.pop
:subtract
else
:add
end
case operation
when :add
add *arguments
else
subtract *arguments
end
end
更好:
def calculate(*arguments, op=:add)
case op
when :subtract
subtract *arguments
else
add *arguments
end
end
calculate(6,2,3,:add) #=> 11
calculate(6,2,3) #=> 11
calculate(6,2,3,:subtract) #=> 1
我对你提出的“永远感激不尽”感到不知所措,但如果你在几分钟内感谢我的努力,那就足够了。