首页 > 解决方案 > 泛化 map 和 reduce 实验室

问题描述

我正在一个实验室使用广义映射方法来传递一个元素并阻止返回多个结果。

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

这是代码:

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 个不同的结果。

有人可以帮我理解吗?

谢谢您的帮助!

标签: rubymethods

解决方案


你的方法是如何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)的替代品吗?正如您在顶层定义的那样,您map是该类的实例方法Object

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 < length等价于while i < self.length, 因为self., 如果省略, 是隐含的, 因此是多余的。同样,each { |e| new << yield(e) }等价于self.each { |e| new << 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"]

看到内核#block_given?Object#to_enum

笔记

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


推荐阅读