【问题标题】:generalize map and reduce lab泛化 map 和 reduce 实验室
【发布时间】:2019-10-30 18:53:22
【问题描述】:

我在实验室工作 使用广义的 map 方法传递一个元素并通过返回多个结果来阻止。

在这一点上真的很挣扎。找到了一些回复,但它们对我来说真的没有意义。

代码如下:

def map(s)
  new = []
  i = 0
  while i < s.length
    new.push(yield(s[i]))
    i += 1
  end
  new
end

这是测试:

  it "returns an array with all values made negative" do
    expect(map([1, 2, 3, -9]){|n| n * -1}).to eq([-1, -2, -3, 9])
  end

  it "returns an array with the original values" do
    dune = ["paul", "gurney", "vladimir", "jessica", "chani"]
    expect(map(dune){|n| n}).to eq(dune)
  end

  it "returns an array with the original values multiplied by 2" do
    expect(map([1, 2, 3, -9]){|n| n * 2}).to eq([2, 4, 6, -18])
  end

  it "returns an array with the original values squared" do
    expect(map([1, 2, 3, -9]){|n| n * n}).to eq([1, 4, 9, 81])
  end
end

我不明白上面的代码如何给你这 4 种不同的结果。

有人可以帮我理解吗?

感谢您的帮助!

【问题讨论】:

  • 您的代码似乎缺少某些内容
  • 哪一部分没有意义?

标签: ruby methods


【解决方案1】:

map 方法的工作原理

要查看您的方法如何运行,让我们修改您的代码以添加一些中间变量和一些puts 语句以显示这些变量的值。

def map(s)
  new = []
  i = 0
  n = s.length
  puts "s has length #{n}"
  while i < n
    puts "i = #{i}"
    e = s[i]
    puts "  Yield #{e} to the block"
    rv = yield(e)
    puts "  The block's return value is #{rv}. Push #{rv} onto new"
    new.push(rv)
    puts "  new now equals #{new}"
    i += 1
  end
  puts "We now return the value of new"
  new
end

现在让我们使用其中一个感兴趣的块来执行该方法。

s = [1, 2, 3, -9]
map(s) { |n| n * 2 }
  #=> [2, 4, 6, -18] (return value of method)

显示如下。

s has length 4
i = 0
  Yield 1 to the block
  The block's return value is 2. Push 2 onto new
  new now equals [2]
i = 1
  Yield 2 to the block
  The block's return value is 4. Push 4 onto new
  new now equals [2, 4]
i = 2
  Yield 3 to the block
  The block's return value is 6. Push 6 onto new
  new now equals [2, 4, 6]
i = 3
  Yield -9 to the block
  The block's return value is -18. Push -18 onto new
  new now equals [2, 4, 6, -18]
We now return the value of new

使用不同的s 值和不同的块来执行这个修改后的方法可能会很有趣。

Array#map 的替代品?

这是Array#map(或Enumerable#map,但现在让我们只考虑Array#map)的替代品吗?正如您在顶层定义的那样,您的mapObject 类的实例方法:

Object.instance_methods.include?(:map) #=> true

它必须被调用map([1,2,3]) { |n| ... }Array#map 被调用[1,2,3].map { |n| ... }。因此,要让您的方法 map 替代 Array#map,您需要将其定义如下。

class Array
  def map
    new = []
    i = 0
    while i < length
      new.push(yield(self[i]))
      i += 1
    end
    new
  end
end

[1, 2, 3, -9].map { |n| n * 2 }
  #=> [2, 4, 6, -18]

简化

我们可以将这个方法简化如下。

class Array
  def map
    new = []
    each { |e| new << yield(e) }
    new
  end
end

[1, 2, 3, -9].map { |n| n * 2 }
  #=> [2, 4, 6, -18]

或者,更好:

class Array
  def map
    each_with_object([]) { |e,new| new << yield(e) }
  end
end

Enumerable#each_with_object

注意while i &lt; length 等价于while i &lt; self.length,因为self.,如果省略,是隐含的,因此是多余的。同样,each { |e| new &lt;&lt; yield(e) } 等价于self.each { |e| new &lt;&lt; yield(e) }each_with_object([]) { ... } 等价于self.each_with_object([]) { ... }

我们完成了吗?

如果我们仔细检查文档Array#map,我们会发现该方法有两种形式。第一个是map 占用一个块。我们的方法Array#map 模仿了这种行为,这是满足给定rspec 测试所需的唯一行为。

然而,还有第二种形式,map 没有给定一个块,在这种情况下它返回一个枚举数。这允许我们将方法链接到另一个方法。例如(使用 Ruby 的Array#map),

['cat', 'dog', 'pig'].map.with_index do |animal, i|
  i.even? ? animal.upcase : animal
end
  #=> ["CAT", "dog", "PIG"]

我们可以修改我们的Array#map 以合并第二个行为,如下所示。

class Array
  def map
    if block_given?
      each_with_object([]) { |e,new| new << yield(e) }
    else
      to_enum(:map)
    end
  end
end

[1, 2, 3, -9].map { |n| n * 2 }
  #=> [2, 4, 6, -18]
['cat', 'dog', 'pig'].map.with_index do |animal, i|
  i.even? ? animal.upcase : animal
end
  #=> ["CAT", "dog", "PIG"]

请参阅 Kernel#block_given?Object#to_enum

备注

您可以使用arr 而不是s 作为保存数组的变量,因为s 通常表示字符串,就像h 通常表示哈希一样。通常避免使用作为核心 Ruby 方法名称的变量和自定义方法的名称。这也反对你使用new作为变量名,因为有很多核心方法命名为new

【讨论】:

  • 如果练习是 write map,那么委托给 Array/Enumerable 有点作弊,不是吗? :)
  • @Sergio,哈!我没想到这是地图的替代品,尽管它在第一句话中位于前面和中心。
  • 知道了!谢谢!!
猜你喜欢
  • 2022-06-15
  • 2012-07-07
  • 1970-01-01
  • 2016-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-08
  • 2015-03-24
相关资源
最近更新 更多