Ruby(猴子补阵)
Ruby (Monkey Patching Array)
回来为我在 Bloc 的课程作业提供更多帮助。决定让你们参与解决我在使用 Monkey 修补数组 Class 时遇到的问题。这项作业有 8 个规范 需要满足,但我现在只剩下一个了,我不知道该怎么做。
我只会向您提供我遇到问题的部分的 RSpec 和书面要求,因为其他一切似乎都已通过。此外,我将包括他们给我的入门脚手架,这样就不会对代码造成混淆或无用的添加。
这里是 Array Class Monkey Patch 的书面要求:
编写一个新的 new_map
方法,在 Array
class 的实例上调用。它应该使用它调用的数组作为隐式 (self
) 参数,但其他方面的行为相同。 (不完整)
写一个new_select!行为类似于 select 的方法,但会改变调用它的数组。它可以使用 Ruby 的内置集合 select 方法。 (完成)
以下是数组 class 需要满足的 RSpecs:
Note: "returns an array with updated values" Is the only Spec not passing.
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
查看此处的规范:
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 }
并获取 [3,4,5,6]
.
Ruby Array Class:
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()
调用new_map()里面的each()方法。所有方法都由某个对象调用,即点左侧的对象,称为 receiver。例如,当你写:
self.each {....}
self 是方法调用 each() 的接收者。
如果您不指定 receiver
而只写:
each {....}
...然后对于接收器 ruby 使用当时分配给 self
变量的任何对象。在上面的 new_map() 中,ruby 会将调用 new_map() 方法的 Array 分配给 self,因此 each() 将遍历该 Array 中的项目。
您必须对 self 变量小心一点,因为 ruby 会在不通知您的情况下不断更改 self 变量的值。因此,您必须 知道 ruby 在您的代码中的任何特定点分配给 self 变量的内容——这是经验带来的。不过,如果你想知道在你的代码中的某个特定点,什么对象 ruby 分配给了 self,你可以简单地写:
puts self
如果有经验的ruby玩家看到你在new_map()里面写self.each {...}
,他们会惊叫起来,但在我看来代码清晰度胜过代码技巧,因为初学者在那里写自己更有意义,继续做吧。当您获得更多经验并想炫耀时,您可以在不需要时消除显式接收器。这与显式 returns:
的情况类似
def some_method
...
return result
end
和隐含的returns:
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 来调用块,而不是在参数变量中捕获块。但是,在某些情况下,您需要在参数变量中捕获块,例如如果您想将块传递给另一种方法。
关于new_select!
我想说几句。
select 对比 select!
你有:
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!. You said 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]
显式与隐式接收器
请注意,我编写此代码时没有为方法 Array#replace 和 Array#select
提供任何显式接收器。当没有显式接收者时,Ruby 假定它是 self
,即 a
。因此,Ruby 评估 replace(select(&block))
就好像它是用显式接收器编写的:
self.replace(self.select(&block))
是否包含 self.
由您决定。有些红宝石有,有些则没有。您会注意到 self.
未包含在 Ruby 中实现的内置方法 Ruby 中。另一件事:在某些情况下需要 self.
以避免歧义。例如,如果 taco=
是实例变量 @taco
的 setter,则必须编写 self.taco = 7
来告诉 Ruby 您指的是 setter ] 方法。如果您写 taco = 7
,Ruby 将假定您要创建一个局部变量 taco
并将其值设置为 7。
两种形式select!
您的方法 new_select!
是 select!
的直接替代方法吗?也就是说,这两种方法在功能上是否等效?如果您查看 Array#select!
的文档,您会看到它有两种形式,一种是您模仿的,另一种是您尚未实现的。
如果 select!
没有给出一个块,它 return 是一个枚举器。现在你为什么要那样做?假设你想写:
a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
#=> [1, 2]
select!
有没有被阻止?不,所以它必须 return 一个枚举器,它成为方法 Enumerator#with_index.
的接收者
让我们试试看:
enum0 = a.select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:select!>
现在:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index>
您可以将 enum1
视为 "compound enumerator"。仔细看定义enum1
.
时对象Rubyreturns的描述(上)
我们可以通过将枚举器转换为数组来查看枚举器的元素:
enum1.to_a
# => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
这五个元素中的每一个都被传递给块并由Enumerator#each (which calls Array#each分配给块变量。
够了,但关键是在没有给出块时拥有方法 return 枚举器是允许我们链接方法的原因。
您没有测试来确保 new_select!
return 在没有给出块时是一个枚举器,所以也许这不是预期的,但为什么不试一试呢?
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!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.each { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.with_index.each { |n,i| i>2 }
#=> [4, 5]
a #=> [4, 5]
回来为我在 Bloc 的课程作业提供更多帮助。决定让你们参与解决我在使用 Monkey 修补数组 Class 时遇到的问题。这项作业有 8 个规范 需要满足,但我现在只剩下一个了,我不知道该怎么做。
我只会向您提供我遇到问题的部分的 RSpec 和书面要求,因为其他一切似乎都已通过。此外,我将包括他们给我的入门脚手架,这样就不会对代码造成混淆或无用的添加。
这里是 Array Class Monkey Patch 的书面要求:
编写一个新的
new_map
方法,在Array
class 的实例上调用。它应该使用它调用的数组作为隐式 (self
) 参数,但其他方面的行为相同。 (不完整)写一个new_select!行为类似于 select 的方法,但会改变调用它的数组。它可以使用 Ruby 的内置集合 select 方法。 (完成)
以下是数组 class 需要满足的 RSpecs:
Note: "returns an array with updated values" Is the only Spec not passing.
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
查看此处的规范:
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 }
并获取 [3,4,5,6]
.
Ruby Array Class:
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()
调用new_map()里面的each()方法。所有方法都由某个对象调用,即点左侧的对象,称为 receiver。例如,当你写:
self.each {....}
self 是方法调用 each() 的接收者。
如果您不指定 receiver
而只写:
each {....}
...然后对于接收器 ruby 使用当时分配给 self
变量的任何对象。在上面的 new_map() 中,ruby 会将调用 new_map() 方法的 Array 分配给 self,因此 each() 将遍历该 Array 中的项目。
您必须对 self 变量小心一点,因为 ruby 会在不通知您的情况下不断更改 self 变量的值。因此,您必须 知道 ruby 在您的代码中的任何特定点分配给 self 变量的内容——这是经验带来的。不过,如果你想知道在你的代码中的某个特定点,什么对象 ruby 分配给了 self,你可以简单地写:
puts self
如果有经验的ruby玩家看到你在new_map()里面写self.each {...}
,他们会惊叫起来,但在我看来代码清晰度胜过代码技巧,因为初学者在那里写自己更有意义,继续做吧。当您获得更多经验并想炫耀时,您可以在不需要时消除显式接收器。这与显式 returns:
def some_method
...
return result
end
和隐含的returns:
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 来调用块,而不是在参数变量中捕获块。但是,在某些情况下,您需要在参数变量中捕获块,例如如果您想将块传递给另一种方法。
关于new_select!
我想说几句。
select 对比 select!
你有:
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!. You said 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]
显式与隐式接收器
请注意,我编写此代码时没有为方法 Array#replace 和 Array#select
提供任何显式接收器。当没有显式接收者时,Ruby 假定它是 self
,即 a
。因此,Ruby 评估 replace(select(&block))
就好像它是用显式接收器编写的:
self.replace(self.select(&block))
是否包含 self.
由您决定。有些红宝石有,有些则没有。您会注意到 self.
未包含在 Ruby 中实现的内置方法 Ruby 中。另一件事:在某些情况下需要 self.
以避免歧义。例如,如果 taco=
是实例变量 @taco
的 setter,则必须编写 self.taco = 7
来告诉 Ruby 您指的是 setter ] 方法。如果您写 taco = 7
,Ruby 将假定您要创建一个局部变量 taco
并将其值设置为 7。
两种形式select!
您的方法 new_select!
是 select!
的直接替代方法吗?也就是说,这两种方法在功能上是否等效?如果您查看 Array#select!
的文档,您会看到它有两种形式,一种是您模仿的,另一种是您尚未实现的。
如果 select!
没有给出一个块,它 return 是一个枚举器。现在你为什么要那样做?假设你想写:
a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
#=> [1, 2]
select!
有没有被阻止?不,所以它必须 return 一个枚举器,它成为方法 Enumerator#with_index.
让我们试试看:
enum0 = a.select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:select!>
现在:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index>
您可以将 enum1
视为 "compound enumerator"。仔细看定义enum1
.
我们可以通过将枚举器转换为数组来查看枚举器的元素:
enum1.to_a
# => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
这五个元素中的每一个都被传递给块并由Enumerator#each (which calls Array#each分配给块变量。
够了,但关键是在没有给出块时拥有方法 return 枚举器是允许我们链接方法的原因。
您没有测试来确保 new_select!
return 在没有给出块时是一个枚举器,所以也许这不是预期的,但为什么不试一试呢?
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!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.each { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.with_index.each { |n,i| i>2 }
#=> [4, 5]
a #=> [4, 5]