Ruby(Monkey Patching Array)

gcuhipw9  于 2023-10-17  发布在  Ruby
关注(0)|答案(3)|浏览(109)

我遇到了一个问题与猴子修补数组类。此任务需要满足8个规范
我只给给予你的RSpecs和书面要求的一部分,我有麻烦w/,因为其他一切似乎是通过。
以下是Array Class Monkey Patch的书面要求:

  • 编写一个新的new_map方法,在Array类的示例上调用。它应该使用它所调用的数组作为隐式(self)参数,但其他行为相同。(不完整
  • 写一个new_select!方法,其行为类似于select,但会改变调用它的数组。它可以使用Ruby的内置集合select方法。(COMPLEX

以下是需要满足的关于Array类的RSpecs:
注意:“返回一个更新值的数组”是唯一不通过的Spec。

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 
    new_array = []
    self.each do |num|
      new_array << num.to_s
    end
    new_array
  end
  
  def new_select!(&block)
    self.select!(&block)
  end
end
iibxawm4

iibxawm41#

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()来调用new_map()中的each()方法。所有方法都是由某个对象调用的,即点左边的对象,称为接收器。例如,当你写:

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

如果有经验的rubyist看到你在new_map()中写self.each {...},他们会感到惊讶,但在我看来,* 代码清晰性 * 胜过代码技巧,而且因为初学者在那里写self更有意义,所以继续这样做吧。当你有了更多的经验并想炫耀时,你可以在不需要的时候消除明确的接收者。这与显式返回的情况类似:

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),你必须使用不同的语法来调用块:

  1. my_block.call(arg1, ...)
    或:
  2. myblock[arg1, ...]
    请注意,#2就像调用方法的语法一样--只是用[]代替了()
    同样,有经验的rubyist将使用yield来调用块,而不是在参数变量中捕获块。但是,在某些情况下,您需要在参数变量中捕获块,例如。如果你想把块传递给另一个方法。
ijnw1ujt

ijnw1ujt2#

看看这里的spec:

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]

wa7juj8i

wa7juj8i3#

我想对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]

显式与隐式接收器

注意,我在这里没有为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。

两种选择!

你的方法new_select!select!的直接替代品吗?也就是说,这两种方法在功能上是等价的吗?如果您查看Array#select!的文档,您将看到它有两种形式,一种是您模仿的,另一种是您尚未实现的。
如果select!没有被给定一个块,它返回一个枚举数。你为什么要这么做?假设你想写:

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

select!是否已被阻止?否,因此它必须返回一个枚举数,该枚举数将成为方法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视为“复合枚举器”。仔细查看Ruby在定义enum1时返回的对象的描述。
我们可以通过将枚举数转换为数组来查看枚举数的元素:

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

这五个元素中的每一个都被传递给块,并通过枚举器#each(调用Array#each)分配给块变量。
够了,但重点是,当没有给出块时,让方法返回枚举数是允许我们链接方法的。
你没有一个测试来确保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!
  #=> #<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]

相关问题