Ruby(猴子修补阵列)

时间:2015-05-09 17:52:32

标签: ruby-on-rails arrays ruby rspec

返回获取有关Bloc课程作业的更多帮助。决定带给你们一些我正在使用Monkey Patching the Array Class的问题。这个作业要达到 8 specs ,而我现在只剩一个了,我不知道该怎么做。

我只会向你提供RSpec和书面要求,因为我遇到了其他一切似乎正在传递的问题。我还要包括他们给我开始的起始脚手架,这样就没有混淆或无用的代码添加。

以下是Array Class Monkey Patch的书面要求:

  • 编写一个在new_map类的实例上调用的新Array方法。它应该使用它作为隐式(self)参数调用的数组,但在其他方面表现相同。 (的 INCOMPLETE

  • 写一个new_select!行为类似于select的方法,但会改变它所调用的数组。它可以使用Ruby的内置集合选择方法。 (的 COMPLETE

以下是有关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

2 个答案:

答案 0 :(得分:3)

查看此处的规范:

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_maparray = [1,2,3,4]的实现,您可以调用array.new_map(&:to_s)是对每个元素执行的任意操作的位置指定了数组)并获取["1","2","3","4"],或者您可以致电array.new_map { |e| e + 2 }并获取[3,4,5,6]

答案 1 :(得分:1)

我想谈谈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#replaceArray#select提供任何明确的接收器。当没有明确的接收者时,Ruby假定它是self,即a。因此,Ruby评估replace(select(&block)),好像它是用显式接收器编写的:

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

由您决定是否要包含self.。有些Rubiests有,有些则没有。您会注意到在Ruby中实现的Ruby内置方法中不包含self.。另一件事:在某些情况下需要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视为“复合枚举器”。在定义enum1时,请仔细查看Ruby返回的对象的描述(上图)。

我们可以通过将枚举器转换为数组来查看枚举器的元素:

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

这五个元素中的每一个都被传递到块并通过Enumerator#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]