Ruby:使用数组和循环 - 简单移动平均线
Ruby: Working with Arrays & Loops - The Simple Moving Average
在 ruby 中使用简单移动平均线,并想出了这段代码,特别是三天移动平均线:
a = [3, 3, 3, 3, 5, 9, 7, 8, 9, 11]
sma = []
for i in 2 ... 9 do
sma.push((a[i] + a[i-1] + a[i-2])/3.0)
end
这段代码通过了,看起来很简单,但是,如果数组包含数百个项目怎么办?我将如何为 50 天的平均值添加 50 个项目?是否有不同的方法或者是否应该在代码中嵌套另一个循环?
*另外,我知道有适合此类操作的 gem,但我更感兴趣的是从头开始创建它。
方法 Enumerable#each_cons 非常适合此计算:
def moving_average(a, ndays, precision)
a.each_cons(ndays).map { |e| e.reduce(&:+).fdiv(ndays).round(precision) }
end
a = [3, 4, 1, 2, 5, 9, 7, 8, 9, 11]
moving_average(a,3,2)
#=> [2.67, 2.33, 2.67, 5.33, 7.0, 8.0, 8.0, 9.33]
对于这个例子,
enum = a.each_cons(3)
#=> #<Enumerator: [3, 4, 1, 2, 5, 9, 7, 8, 9, 11]:each_cons(3)>
枚举器enum
传递到块中的值可以通过将enum
转换为数组来获得:
enum.to_a
#=> [[3, 4, 1], [4, 1, 2], [1, 2, 5], [2, 5, 9],
# [5, 9, 7], [9, 7, 8], [7, 8, 9], [8, 9, 11]]
map
然后将这些元素中的每一个转换为平均值。
如果a
和ndays
很大,可以实现更高的效率,如下所示。
def moving_average(a,ndays,precision)
(0..a.size-ndays-1).each_with_object([a[0,ndays].sum]) do |i,arr|
arr << arr.last - a[i] + a[i+ndays]
end.map { |tot| tot.fdiv(ndays).round(precision) }
end
moving_average(a,3,2)
#=> [2.67, 2.33, 2.67, 5.33, 7.0, 8.0, 8.0, 9.33]
合计后
tot1 = [3, 4, 1].sum
#=> 8
需要计算第一个 3 天移动平均线,计算第二个 3 天移动平均线需要 [4, 1, 2]
的总和,计算如下:
tot2 = tot1 + 2 - 3
#=> 7
其中 2
是 [4, 1, 2]
的最后一个元素,3
是 [3, 4, 1]
的第一个元素。
虽然此示例没有节省时间,但可以看出,当 a
和 ndays
较大时,计算前一个总计的每个总计将节省时间,因为计算复杂性降低了从 O(n^2)
到 O(n)
,其中 n = a.size
.
在 ruby 中使用简单移动平均线,并想出了这段代码,特别是三天移动平均线:
a = [3, 3, 3, 3, 5, 9, 7, 8, 9, 11]
sma = []
for i in 2 ... 9 do
sma.push((a[i] + a[i-1] + a[i-2])/3.0)
end
这段代码通过了,看起来很简单,但是,如果数组包含数百个项目怎么办?我将如何为 50 天的平均值添加 50 个项目?是否有不同的方法或者是否应该在代码中嵌套另一个循环?
*另外,我知道有适合此类操作的 gem,但我更感兴趣的是从头开始创建它。
方法 Enumerable#each_cons 非常适合此计算:
def moving_average(a, ndays, precision)
a.each_cons(ndays).map { |e| e.reduce(&:+).fdiv(ndays).round(precision) }
end
a = [3, 4, 1, 2, 5, 9, 7, 8, 9, 11]
moving_average(a,3,2)
#=> [2.67, 2.33, 2.67, 5.33, 7.0, 8.0, 8.0, 9.33]
对于这个例子,
enum = a.each_cons(3)
#=> #<Enumerator: [3, 4, 1, 2, 5, 9, 7, 8, 9, 11]:each_cons(3)>
枚举器enum
传递到块中的值可以通过将enum
转换为数组来获得:
enum.to_a
#=> [[3, 4, 1], [4, 1, 2], [1, 2, 5], [2, 5, 9],
# [5, 9, 7], [9, 7, 8], [7, 8, 9], [8, 9, 11]]
map
然后将这些元素中的每一个转换为平均值。
如果a
和ndays
很大,可以实现更高的效率,如下所示。
def moving_average(a,ndays,precision)
(0..a.size-ndays-1).each_with_object([a[0,ndays].sum]) do |i,arr|
arr << arr.last - a[i] + a[i+ndays]
end.map { |tot| tot.fdiv(ndays).round(precision) }
end
moving_average(a,3,2)
#=> [2.67, 2.33, 2.67, 5.33, 7.0, 8.0, 8.0, 9.33]
合计后
tot1 = [3, 4, 1].sum
#=> 8
需要计算第一个 3 天移动平均线,计算第二个 3 天移动平均线需要 [4, 1, 2]
的总和,计算如下:
tot2 = tot1 + 2 - 3
#=> 7
其中 2
是 [4, 1, 2]
的最后一个元素,3
是 [3, 4, 1]
的第一个元素。
虽然此示例没有节省时间,但可以看出,当 a
和 ndays
较大时,计算前一个总计的每个总计将节省时间,因为计算复杂性降低了从 O(n^2)
到 O(n)
,其中 n = a.size
.