我正在浏览关于Rubymonk的这个恼人的教程,它要求我做以下事情:
为我写三种方法 - 计算,加法和减法。测试应该全部通过。如果遇到麻烦,请看一下提示!还有一点额外的提示:记住你可以使用something.is_a?(Hash)或another_thing.is_a?(String)来检查对象的类型。
我甚至无法理解他们要求我做什么,所以我只是决定看到解决方案,然后按照自己的方式理解任务。
以下是解决方案:
def add(*numbers)
numbers.inject(0) { |sum, number| sum + number }
end
def subtract(*numbers)
current_result = numbers.shift
numbers.inject(current_result) { |current_result, number| current_result - number }
end
def calculate(*arguments)
# if the last argument is a Hash, extract it
# otherwise create an empty Hash
options = arguments[-1].is_a?(Hash) ? arguments.pop : {}
options[:add] = true if options.empty?
return add(*arguments) if options[:add]
return subtract(*arguments) if options[:subtract]
end
我不了解很多东西,但令我困惑的一件事是shift方法:current_result = numbers.shift。为什么会这样?我的意思是,我理解它的作用,但它在这段特殊代码中的作用是什么?
顺便说一句,如果有人为我打破这段代码而烦恼,我会无休止地永远感恩。任务位于以下页面的底部: https://rubymonk.com/learning/books/1-ruby-primer/chapters/19-ruby-methods/lessons/69-new-lesson#solution3899
答案 0 :(得分:4)
添加(*号)强>
让我们首先调用:
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
)分配给备忘录({{1然后继续如上所述从接收者的第二个元素(sum
)开始。正如所料,这产生了相同的答案:
2
那么为什么要包含零参数呢?通常我们会希望def add(*numbers)
numbers.inject { |sum, number| sum + number }
end
add(1,2,3) #=> 6
(即add()
返回零。我会留给您调查这两种add(*[]))
形式的内容。您可以得出什么结论?
正如@Stefan在答案中指出的那样,你可以简单地说:
inject
这就是你通常看到它的方式。
但是,如果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
带有&#34; truthy&#34;值(除了:add
或false
之外的任何内容,我们要添加;如果哈希的键nil
带有&#34; truthy&#34;值(除了{{之外的任何值) 1}}或: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
我对你的承诺感到不知所措,感谢他们,并且感激不尽,但是如果你感谢我的努力几分钟,那就足够了。
答案 1 :(得分:3)
current_result = numbers.shift
。为什么会这样?我的意思是,我理解它的作用,但它在这段特殊代码中的作用是什么?
该行从numbers
数组中删除第一个元素,并将其分配给current_result
。然后,为剩余的current_result - number
数组中的每个number
计算numbers
。
记录示例:
numbers = [20, 3, 2]
current_result = numbers.shift
current_result #=> 20
numbers #=> [3, 2]
numbers.inject(current_result) do |current_result, number|
(current_result - number).tap do |result|
puts "#{current_result} - #{number} = #{result}"
end
end
输出:
20 - 3 = 17
17 - 2 = 15
15
将是方法的返回值。
但是,不需要删除第一个元素; inject
默认执行此操作:
如果您没有为备忘录明确指定初始值,那么集合的第一个元素将用作备忘录的初始值
因此,subtract
方法可以简化:
def subtract(*numbers)
numbers.inject { |current_result, number| current_result - number }
end
subtract(20, 3, 2) #=> 15
您还可以提供方法符号:
def subtract(*numbers)
numbers.inject(:-)
end
同样适用于add
:
def add(*numbers)
numbers.inject(:+)
end
答案 2 :(得分:2)
subtract
方法
让我们说你的输入是[2,3,4,5]。
在你看来,你可以认为你需要一个能够做到这一点的程序
result = 2 # This is first element of array
result = result - 3 # This is second element of array
result = result - 4 # This is third element of array
result = result - 5 # This is fourth element of array
最后,你最终得到了
result = -10
shift
允许您从数组中获取第一个元素,这样您就可以对剩余元素使用inject
,其中第一个元素移位的数组作为accumulator
值。
如果您了解inject
方法works的方式,上述说明就有意义。
同样可以采用add
方法。
def add(*numbers)
current_result = numbers.shift
numbers.inject(current_result) { |current_result, number| current_result + number }
end
但是,add
代码的作者在其实施中使用了更为正统的inject
版本,因为将0
添加到另一个数字不会产生任何影响。
如果作者使用他们的add
方法并将+
替换为-
,那么他们最终将使用
def subtract(*numbers)
numbers.inject(0) { |result, number| result - number }
end
但是,subtract([2,3,4,5])
会提供-14
(0-2-3-4-5 = -14
)而不是-10
(2-3-4-5 = -10
)的输出。
要修复上述代码,必须删除0
和add
中的默认参数subtract
,并编写如下内容
def subtract(*numbers)
numbers.inject { |result, number| result - number }
end
def add(*numbers)
numbers.inject { |result, number| result + number }
end
我们现在可以获得-10
subtract([2,3,4,5])
的正确结果
上述代码有效,因为记录在案here(并在其他答案中也有表达)
inject的参数实际上是可选的。如果没有默认值 作为参数传入,块第一次执行,第一次 参数将被设置为枚举的第一个元素和 第二个参数将被设置为可枚举的第二个元素。
总之,Rubymonk的作者可以避免混淆,或者可能是他们试图通过使用shift
calculate
方法
易于理解的calculate
版本可能如下所示:
def calculate(*arguments, options)
return add(*arguments) if options[:add]
return subtract(*arguments) if options[:subtract]
end
p calculate(1,2,3,4,{add: true}) # Adds the numbers, outputs 10
p calculate(1,2,3,4,{subtract: true}) # Subtracts the numbers, outputs -8
Ruby会将函数调用的最后一个参数赋给options
,将其余元素作为数组分配给arguments
。如果我们仅使用一个参数调用calculate
,则该值为options
。
p calculate({add: true}) # Outputs 0
p calculate(1) # Will return error as 1 is not a Hash and can't be assigned to 'options'
代码的作者想要使options
成为可选参数,以便下面的代码也能正常工作。
p calculate(1,2,3,4) # Assume add, and output 10
这需要更改更简单的calculate
版本,以便我们可以使用options
的最后一个元素,而不是明确声明calculate
作为arguments
的参数。如果数组为options
,则数组为Hash
。因此,我们将最终实施Rubymonk上显示的calculate
。