简体   繁体   English

Ruby(猴子修补阵列)

[英]Ruby (Monkey Patching Array)

Back for more help on my coursework at Bloc. 返回获取更多有关Bloc课程的帮助。 Decided to bring you guys in on a problem I'm having with Monkey Patching the Array Class. 决定带给你们一些我正在使用Monkey Patching the Array Class的问题。 This assignment had 8 specs to be met and I'm stuck now with one left and I'm not sure what to do. 这个任务有8个规格可以满足,我现在卡在一个左边,我不知道该怎么办。

I'm only going to give you the RSpecs and written requirements for the part that I'm having trouble w/ since everything else seems to be passing. 我只会给你RSpecs和我遇到麻烦的部分的书面要求,因为其他一切似乎都在传递。 Also I'll include the starter scaffolding that they gave me to begin with so there's no confusion or useless additions to the code. 此外,我将包括他们开始给我的启动脚手架,因此代码中没有混淆或无用的添加。


Here are the written requirements for the Array Class Monkey Patch: 以下是Array Class Monkey Patch的书面要求:

  • Write a new new_map method that is called on an instance of the Array class. 编写一个在Array类的实例上调用的新new_map方法。 It should use the array it's called on as an implicit ( self ) argument, but otherwise behave identically. 它应该使用它被调用的数组作为隐式( self )参数,但在其他方面表现相同。 ( INCOMPLETE ) 未完成

  • Write a new_select! 写一个new_select! method that behaves like the select, but mutates the array on which it's called. 行为与select类似的方法,但会改变调用它的数组。 It can use Ruby's built-in collection select method. 它可以使用Ruby的内置集合选择方法。 ( COMPLETE ) 完整


Here are the RSpecs that need to be met regarding the Array class: 以下是有关Array类需要满足的RSpec:

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

Here is the scaffolding they started me with: 这是他们开始使用的脚手架:

class Array
  def new_map
  end

  def new_select!(&block)
  end
end

Lastly, here is my code: 最后,这是我的代码:

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 Array Class: Ruby数组类:

 map { |item| block } → new_ary 

A block is like a method, and you specify a block after a method call, for example: block就像一个方法,并在方法调用后指定一个块,例如:

[1, 2, 3].map() {|x| x*2} #<---block
           ^
           |
       method call(usually written without the trailing parentheses)

The block is implicitly sent to the method, and inside the method you can call the block with yield . 该块被隐式发送到该方法,并且在该方法内部,您可以使用yield调用该块。

yield -> calls the block specified after a method call. yield - >调用方法调用后指定的块。 In ruby, yield is equivalent to yield() , which is conceptually equivalent to calling the block like this: block() . 在ruby中, yield等价于yield() ,这在概念上等同于调用块: block()

yield(x) -> calls the block specified after a method call sending it the argument x, which is conceptually equivalent to calling the block like this: block(x) . yield(x) - >调用在方法调用之后指定的块,向它发送参数x,这在概念上等同于调用块: block(x)

So, here is how you can implement new_map(): 那么,这是你如何实现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]

This comment is a bit advanced, but you don't actually have to write self.each() to call the each() method inside new_map(). 这个注释有点高级,但实际上你不必编写self.each()来调用self.each()中的each()方法。 All methods are called by some object, ie the object to the left of the dot, which is called the receiver . 所有方法都由某个对象调用,即点左侧的对象,称为接收器 For instance, when you write: 例如,当你写:

self.each {....}

self is the receiver of the method call each(). self是方法调用each()的接收者。

If you do not specify a receiver and just write: 如果您没有指定receiver并且只写:

each {....}

...then for the receiver ruby uses whatever object is assigned to the self variable at that moment. ...然后对于接收器,ruby使用当时分配给self变量的任何对象。 Inside new_map() above, ruby will assign the Array that calls the new_map() method to self, so each() will step through the items in that Array. 在上面的new_map()中,ruby会将调用new_map()方法的Array分配给self,因此each()将遍历该Array中的项目。

You have to be a little bit careful with the self variable because ruby constantly changes the value of the self variable without telling you. 你必须对自变量有点小心,因为ruby不断地改变自变量的值而不告诉你。 So, you have to know what ruby has assigned to the self variable at any particular point in your code--which comes with experience. 因此,您必须知道 ruby在代码中的任何特定点为自变量赋予了什么 - 这是经验所带来的。 Although, if you ever want to know what object ruby has assigned to self at some particular point in your code, you can simply write: 虽然,如果您想知道ruby在代码中的某个特定点为自己分配了什么对象,您可以简单地写:

puts self

Experienced rubyists will crow and cluck their tongues if they see you write self.each {...} inside new_map(), but in my opinion code clarity trumps code trickiness , and because it makes more sense to beginners to write self there, go ahead and do it. 经验丰富的Ruby开发者将乌鸦和咯咯叫舌头,如果他们看到你写self.each {...}里面new_map(),但在我看来代码的清晰度胜过代码trickiness,因为它更有意义,初学者写自那里,去前进并做到这一点。 When you get a little more experience and want to show off, then you can eliminate explicit receivers when they aren't required. 当您获得更多经验并想要炫耀时,您可以在不需要时消除显式接收器。 It's sort of the same situation with explicit returns: 这与具有明确回报的情况类似:

def some_method
    ...
    return result
end

and implicit returns: 和隐含的回报:

def some_method
    ...
    result
end

Note that you could write new_map() like this: 请注意,您可以像这样编写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]

Compare that to the example that uses yield(). 将其与使用yield()的示例进行比较。 When you use yield(), it's as if ruby creates a parameter variable named yield for you in order to capture the block. 当你使用yield()时,就好像ruby为你创建一个名为yield的参数变量来捕获块。 However, with yield you use a different syntax to call the block, namely () , or if their are no arguments for the block, you can eliminate the parentheses--just like you can when you call a method. 但是,使用yield会使用不同的语法来调用块,即() ,或者如果它们不是块的参数,则可以删除括号 - 就像调用方法时一样。 On the other hand, when you create your own parameter variable to capture a block, eg def new_map(&my_block) , you have to use a different syntax to call the block: 另一方面,当您创建自己的参数变量来捕获块时,例如def new_map(&my_block) ,您必须使用不同的语法来调用块:

  1. my_block.call(arg1, ...)

or: 要么:

  1. myblock[arg1, ...]

Note that #2 is just like the syntax for calling a method--except that you substitute [] in place of () . 请注意,#2就像调用方法的语法一样 - 除了用[]代替()

Once again, experienced rubyists will use yield to call the block instead of capturing the block in a parameter variable. 有经验的rubyists将再次使用yield来调用块而不是在参数变量中捕获块。 However, there are situations where you will need to capture the block in a parameter variable, eg if you want to pass the block to yet another method. 但是,在某些情况下,您需要在参数变量中捕获块,例如,如果要将块传递给另一个方法。

Looking at the spec here: 看看这里的规格:

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 looks like they just want you to re-write Array.map so that it will work with any given block. 看起来他们只是想让你重新编写Array.map以便它可以与任何给定的块一起使用。 In your implementation you are telling the method to work in a very specific way, namely to call .to_s on all of the array elements. 在您的实现中,您告诉方法以非常特定的方式工作,即在所有数组元素上调用.to_s But you don't want it to always stringify the array elements. 但是你不希望它总是对数组元素进行字符串化。 You want it to do to each element whatever block is provided when the method is called. 在调用方法时,您希望它对每个元素执行任何块提供。 Try this: 尝试这个:

class Array
  def new_map 
    new_array = []
    each do |num|
      new_array << yield(num)
    end
    new_array
  end
end

Notice in my example that the method definition doesn't specify any particular operation to be performed on each element of self . 请注意,在我的示例中,方法定义未指定要对self每个元素执行的任何特定操作。 It simply loops over each element of the array, yields the element ( num ) to whatever block was passed when .new_map was called, and shovels the result into the new_array variable. 它只是循环遍历数组的每个元素,产生元素( num )到调用.new_map时传递的任何块,并将结果铲到new_array变量中。

With that implementation of .new_map and given array = [1,2,3,4] , you can call either array.new_map(&:to_s) ( the block is where the arbitrary operation to be performed on each element of the array is specified) and get ["1","2","3","4"] or you can call array.new_map { |e| e + 2 } 通过.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 } and get [3,4,5,6] . array.new_map { |e| e + 2 }并获得[3,4,5,6]

I would like to say a few words about new_select! 我想谈谈new_select! .

select vs select! 选择vs选择!

You have: 你有:

class Array
  def new_select!(&block)
    self.select!(&block)
  end
end

which you could instead write: 您可以改为写:

class Array
  def new_select!
    self.select! { |e| yield(e) }
  end
end

This uses the method Array#select! 这使用方法Array#select! . You said Array#select could be used, but made no mention of select! 你说可以使用Array #select ,但是没有提到select! . If you cannot use select! 如果你不能使用select! , you must do something like this: ,你必须做这样的事情:

class Array
  def new_select!(&block)
    replace(select(&block)) 
  end
end

Let's try it: 我们来试试吧:

a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
  #=> [1, 3, 5] 
a #=> [1, 3, 5] 

Explicit vs implicit receivers 显式vs隐式接收器

Notice that I have written this without any explicit receivers for the methods Array#replace and Array#select . 请注意,我没有为Array#replaceArray#select方法提供任何明确的接收器。 When there is no explicit receiver, Ruby assumes that it is self , which is the a . 当没有明确的接收者时,Ruby假定它是self ,即a Therefore, Ruby evaluates replace(select(&block)) as though it were written with explicit receivers: 因此,Ruby评估replace(select(&block)) ,好像它是用显式接收器编写的:

self.replace(self.select(&block)) 

It's up to you to decide if you want to include self. 由您来决定是否要包含self. . Some Rubiests do, some don't. 有些Rubiests有,有些则没有。 You'll notice that self. 你会注意到self. is not included in Ruby built-in methods implemented in Ruby. 不包含在Ruby中实现的Ruby内置方法中。 One other thing: self. 另一件事: self. is required in some situations to avoid ambiguity. 在某些情况下需要避免歧义。 For example, if taco= is the setter for an instance variable @taco , you must write self.taco = 7 to tell Ruby you are referring to the setter method. 例如,如果taco=是实例变量@taco的setter,则必须编写self.taco = 7来告诉Ruby你指的是setter方法。 If you write taco = 7 , Ruby will assume you want to create a local variable taco and set its value to 7. 如果你写taco = 7 ,Ruby会假设你要创建一个局部变量taco并将其值设置为7。

Two forms of select! 两种形式的选择!

Is your method new_select! 你的方法是new_select! a direct replacement for select! 直接替代select! ? That is, are the two methods functionally equivalent? 也就是说,两种方法在功能上是否相同? If you look at the docs for Array#select! 如果你看一下Array#select!的文档Array#select! , you will see it has two forms, the one you have mimicked, and another that you have not implemented. ,你会发现它有两种形式,一种你模仿的形式,另一种你没有实现的形式。

If select! 如果select! is not given a block, it returns an enumerator. 没有给出一个块,它返回一个枚举器。 Now why would you want to do that? 现在你为什么要这样做? Suppose you wish to write: 假设你想写:

a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
  #=> [1, 2] 

Has select! select! been given a block? 被给了一块? No, so it must return an enumerator, which becomes the receiver of the method Enumerator#with_index . 不,所以它必须返回一个枚举器,它成为方法Enumerator #with_index的接收者。

Let's try it: 我们来试试吧:

enum0 = a.select!
  #=> #<Enumerator: [1, 2, 3, 4, 5]:select!>

Now: 现在:

enum1 = enum0.with_index
  #=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index> 

You can think of enum1 as a "compound enumerator". 您可以将enum1视为“复合枚举器”。 Look carefully at the description (above) of the object Ruby returns when you define enum1 . 仔细查看定义enum1时Ruby返回的对象的描述(上面)。

We can see the elements of the enumerator by converting it to an array: 我们可以通过将枚举器转换为数组来查看枚举器的元素:

enum1.to_a
  # => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]

Each of these five elements is passed to the block and assigned to the block variables by Enumerator#each (which calls Array#each ). 这五个元素中的每一个都传递给块,并由Enumerator#each (它们调用Array#each )分配给块变量。

Enough of that, but the point is that having methods return enumerators when no block is given is what allows us to chain methods. 足够的,但关键是当没有给出块时让方法返回枚举器是允许我们链接方法的原因。

You do not have a test that ensures new_select! 您没有确保new_select!的测试new_select! returns an enumerator when no block is given, so maybe that's not expected, but why not give it a go? 当没有给出块时返回一个枚举器,所以也许这不是预期的,但为什么不给它一个去?

class Array
  def new_select!(&block)
    if block_given?
      replace(select(&block))
    else
      to_enum(:new_select!)
    end
  end
end

Try it: 试试吧:

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] 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM