Ruby(猴子修补arrays)
返回获取更多关于Bloc课程的帮助。 决定带给你们一些我正在使用Monkey Patching the Array Class的问题。 这个任务有8个规格可以满足,我现在卡在一个左边,我不知道该怎么办。
我只会给你RSpecs和我遇到麻烦的部分的书面要求,因为其他一切似乎都在传递。 此外,我将包括他们开始给我的入门脚手架,因此代码中没有混淆或无用的添加。
以下是Array Class Monkey Patch的书面要求:
-
编写一个在
Array
类的实例上调用的新new_map
方法。 它应该使用它所调用的数组作为隐式(self
)参数,但在其他方面表现相同。 ( 未完成 ) -
写一个new_select! 行为与select类似的方法,但会改变调用它的数组。 它可以使用Ruby的内置集合选择方法。 ( 完整 )
以下是有关Array类需要满足的RSpec:
注意:“返回具有更新值的数组”是唯一未传递的规范。
describe Array do describe '#new_map' do it "returns an array with updated values" do array = [1,2,3,4] expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} ) expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] ) end it "does not call #map" do array = [1,2,3,4] array.stub(:map) { '' } expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} ) end it "does not change the original array" do array = [1,2,3,4] expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} ) expect( array ).to eq([1,2,3,4]) end end describe '#new_select!' do it "selects according to the block instructions" do expect( [1,2,3,4].new_select!{ |e| e > 2 } ).to eq( [3,4] ) expect( [1,2,3,4].new_select!{ |e| e < 2 } ).to eq( [1] ) end it "mutates the original collection" do array = [1,2,3,4] array.new_select!(&:even?) expect(array).to eq([2,4]) end end end
这是他们开始使用的脚手架:
class Array def new_map end def new_select!(&block) end end
最后,这是我的代码:
class Array def new_map new_array = [] self.each do |num| new_array << num.to_s end new_array end def new_select!(&block) self.select!(&block) end end
Ruby数组类:
map { |item| block } → new_ary
block
就像一个方法,您可以在方法调用后指定一个块,例如:
[1, 2, 3].map() {|x| x*2} #<---block ^ | method call(usually written without the trailing parentheses)
该块隐式发送到方法,在方法内部,您可以使用yield
调用块。
yield
- >调用方法调用后指定的块。 在ruby中, yield
等价于yield()
,这在概念上等同于调用块: block()
。
yield(x)
- >调用在方法调用之后指定的块,向它发送参数x,这在概念上等效于调用块: block(x)
。
那么,以下是如何实现new_map():
class Array def new_map result = [] each do |item| result << yield(item) end result end end arr = [1, 2, 3].new_map {|x| x*2} p arr --output:-- [2, 4, 6]
这个注释有点高级,但你实际上不必编写self.each()
来调用self.each()
中的each()方法。 所有方法都由某个对象调用,即点左侧的对象,称为接收器 。 例如,当你写:
self.each {....}
self是方法调用each()的接收者。
如果您没有指定receiver
并且只写:
each {....}
...然后对于接收器,ruby使用当时分配给self
变量的任何对象。 在上面的new_map()中,ruby会将调用new_map()方法的Array分配给self,因此each()将遍历该Array中的项目。
你必须对自变量有点小心,因为ruby会在不告诉你的情况下不断改变自变量的值。 因此,您必须知道 ruby在代码中的任何特定点分配给自变量的内容 - 这都来自经验。 虽然,如果您想知道ruby在代码中的某个特定点为自己分配了什么对象,您可以简单地写:
puts self
如果经验丰富的rubyists看到你在new_map()里面写了self.each {...}
那么他们就会self.each {...}
他们的舌头,但在我看来, 代码清晰度胜过了代码的诡计 ,因为初学者在那里写自己更有意义,所以去吧前进并做到这一点。 当您获得更多经验并希望炫耀时,您可以在不需要时消除显式接收器。 这与具有明确回报的情况类似:
def some_method ... return result end
和隐含的回报:
def some_method ... result end
请注意,您可以像这样编写new_map():
class Array def new_map(&my_block) #capture the block in a variable result = [] each do |item| result << my_block.call(item) #call the block end result end end arr = [1, 2, 3].new_map {|x| x*2} p arr --output:-- [2, 4, 6]
将其与使用yield()的示例进行比较。 当你使用yield()时,就好像ruby为你创建一个名为yield
的参数变量来捕获块。 但是,使用yield会使用不同的语法来调用块,即()
,或者如果它们不是块的参数,则可以消除括号 - 就像调用方法时一样。 另一方面,当您创建自己的参数变量来捕获块时,例如def new_map(&my_block)
,您必须使用不同的语法来调用块:
-
my_block.call(arg1, ...)
要么:
-
myblock[arg1, ...]
请注意,#2就像调用方法的语法一样 - 除了用[]
代替()
。
再一次,经验丰富的rubyists将使用yield来调用块而不是在参数变量中捕获块。 但是,在某些情况下,您需要在参数变量中捕获块,例如,如果要将块传递给另一个方法。
看看这里的规格:
it "returns an array with updated values" do array = [1,2,3,4] expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} ) expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] ) end
看起来他们只是想让你重新编写Array.map
以便它可以与任何给定的块一起使用。 在您的实现中,您告诉方法以一种非常特定的方式工作,即在所有数组元素上调用.to_s
。 但是你不希望它总是对数组元素进行字符串化。 在调用方法时,您希望它对每个元素执行任何块提供。 试试这个:
class Array def new_map new_array = [] each do |num| new_array << yield(num) end new_array end end
请注意,在我的示例中,方法定义未指定要对self
每个元素执行的任何特定操作。 它只是循环遍历数组的每个元素,产生元素( num
)到调用.new_map
时传递的任何块,并将结果铲到new_array
变量中。
通过.new_map
和给定array = [1,2,3,4]
,您可以调用array.new_map(&:to_s)
( 该块是对数组的每个元素执行的任意操作的位置指定)并获取["1","2","3","4"]
或者你可以调用array.new_map { |e| e + 2 }
array.new_map { |e| e + 2 }
并获得[3,4,5,6]
。
我想谈谈new_select!
。
选择vs选择!
你有:
class Array def new_select!(&block) self.select!(&block) end end
您可以改为写:
class Array def new_select! self.select! { |e| yield(e) } end end
这使用方法Array#select! 。 你说可以使用Array #select ,但是没有提到select!
。 如果你不能使用select!
,你必须做这样的事情:
class Array def new_select!(&block) replace(select(&block)) end end
我们来试试吧:
a = [1,2,3,4,5] a.new_select! { |n| n.odd? } #=> [1, 3, 5] a #=> [1, 3, 5]
显式vs隐式接收器
请注意,我在没有任何显式接收器的情况下编写了这个方法,用于Array#replace和Array#select
。 当没有明确的接收者时,Ruby认为它是self
,即a
。 因此,Ruby评估replace(select(&block))
,好像它是用显式接收器编写的:
self.replace(self.select(&block))
由你决定是否要包括self.
。 有些Rubiests有,有些则没有。 你会注意到self.
不包含在Ruby中实现的Ruby内置方法中。 另一件事: self.
在某些情况下需要避免歧义。 例如,如果taco=
是实例变量@taco
的setter,则必须编写self.taco = 7
来告诉Ruby你指的是setter方法。 如果你写taco = 7
,Ruby会假设你想要创建一个局部变量taco
并将其值设置为7。
两种forms的选择!
你的方法是new_select!
直接替代select!
? 也就是说,两种方法在function上是否相同? 如果你看一下Array#select!
docs的文档Array#select!
,你会看到它有两种forms,一种你模仿的forms,另一种你没有实现的forms。
如果select!
没有给出一个块,它返回一个枚举器。 那你为什么要那样做? 假设你想写:
a = [1,2,3,4,5] a.select!.with_index { |n,i| i < 2 } #=> [1, 2]
有select!
被封锁了? 不,所以它必须返回一个枚举器,它成为方法Enumerator #with_index的接收者。
我们来试试吧:
enum0 = a.select! #=> #
现在:
enum1 = enum0.with_index #=> #:with_index>
您可以将enum1
视为“复合枚举器”。 仔细查看定义enum1
时Ruby返回的对象的描述(上面)。
我们可以通过将枚举器转换为数组来查看枚举器的元素:
enum1.to_a # => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
这五个元素中的每一个都传递给块,并由Enumerator#each (它们调用Array#each )分配给块变量。
足够的,但重点是当没有给出块时让方法返回枚举器是允许我们链接方法的原因。
您没有确保new_select!
的测试new_select!
当没有给出块时返回一个枚举器,所以也许这不是预期的,但为什么不给它一个去?
class Array def new_select!(&block) if block_given? replace(select(&block)) else to_enum(:new_select!) end end end
试试吧:
a = [1,2,3,4,5] a.new_select! { |n| n.odd? } #=> [1, 3, 5] a #=> [1, 3, 5] a = [1,2,3,4,5] enum2 = a.new_select! #=> # enum2.each { |n| n.odd? } #=> [1, 3, 5] a #=> [1, 3, 5] a = [1,2,3,4,5] enum2 = a.new_select! #=> # enum2.with_index.each { |n,i| i>2 } #=> [4, 5] a #=> [4, 5]