ruby - 泛化 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 个不同的结果。
有人可以帮我理解吗?
谢谢您的帮助!
解决方案
你的方法是如何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
.
推荐阅读
- networking - 如何在设置了 cookie 的情况下将请求重定向到不同的域/ip:port?
- windows-runtime - 是否可以为 Windows 运行时组件(C++/WinRT)添加版本?
- amazon-web-services - 为 Autoscaling Group 中的实例分配静态弹性 IP
- javascript - 我可以使用返回加权布尔值的函数来实现加权随机性吗?
- python-3.x - ImportError:无法从“countstring”导入名称“count_hi”
- pandas - 如何从 Google 表格上传到 BigQuery?我可以通过 Pandas DF 来完成吗?
- swagger-2.0 - 嵌套异常是 java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/std/ToStringSerializerBase
- javascript - Angular - 如何禁用保留值的字段
- psycopg2 - 使用python在Postgresql中提取大数据(最好是数据框格式)
- lit-element - 使用 lit-element 渲染多个页面之一