如何从数组中求平均值?

如果我有一个数组:

[0,4,8,2,5,0,2,6]

平均得到3.375。


这台电脑上没有ruby,但在这个程度上应该可以工作:

values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
 total += val
end

average = total/values.size

试试这个:

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

请注意.to_f,它可以避免整数除法带来的任何问题。你还可以:

arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5

您可以像另一位评论者建议的那样将其定义为Array的一部分,但您需要避免整数除法,否则您的结果将是错误的。而且,这并不适用于所有可能的元素类型(显然,平均值只适用于可以求平均值的东西)。但如果你想走这条路,可以用这个:

class Array
  def sum
    inject(0.0) { |result, el| result + el }
  end

  def mean 
    sum / size
  end
end

如果您以前没有见过inject,那么它并没有看起来那么神奇。它遍历每个元素,然后对其应用累加器值。然后将累加器传递给下一个元素。在本例中,累加器只是一个反映之前所有元素之和的整数。

编辑:评论者戴夫·雷提出了一个很好的改进。

编辑:评论者格伦·杰克曼的提议,使用arr.inject(:+)。To_f也很好,但如果你不知道发生了什么,可能有点太聪明了。:+是一个符号;当传递给inject时,它对每个元素对累加器值应用由符号命名的方法(在本例中为加法操作)。


a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length

class Array
  def sum 
    inject( nil ) { |sum,x| sum ? sum+x : x }
  end

  def mean 
    sum.to_f / size.to_f
  end
end

[0,4,8,2,5,0,2,6].mean

a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375

不使用instance_eval的版本如下:

a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375

我希望Math.average(values),但没有这样的运气。

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f

[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

简短,但使用实例变量


我认为最简单的答案是

list.reduce(:+).to_f / list.size

我不喜欢公认的解决方案

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

它并不是纯粹的功能性的。 我们需要一个变量来计算。最后的尺寸。

为了从函数上解决这个问题我们需要关注两个 值:所有元素的和,以及元素的个数。

[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
    [ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5   

Santhosh改进了这个解决方案:不是参数r是一个数组,我们可以使用解构立即将它分解为两个变量

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   [ sum + ele, size + 1 ]
end.inject(:/)

如果你想看看它是如何工作的,添加一些看跌期权:

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   r2 = [ sum + ele, size + 1 ]
   puts "adding #{ele} gives #{r2}"
   r2
end.inject(:/)

adding 5 gives [5.0, 1]
adding 6 gives [11.0, 2]
adding 7 gives [18.0, 3]
adding 8 gives [26.0, 4]
=> 6.5

我们也可以使用结构体而不是数组来包含sum和count,但我们必须先声明结构体:

R=Struct.new(:sum, :count)
[5, 6, 7, 8].inject( R.new(0.0, 0) ) do |r,ele|
    r.sum += ele
    r.count += 1
    r
end.inject(:/)

为了让公众开心,还有另一个解决方案:

a = 0, 4, 8, 2, 5, 0, 2, 6
a.reduce [ 0.0, 0 ] do |(s, c), e| [ s + e, c + 1 ] end.reduce :/
#=> 3.375

让我在竞争中引入一些可以解决零除问题的东西

a = [1,2,3,4,5,6,7,8]
a.reduce(:+).try(:to_f).try(:/,a.size) #==> 4.5

a = []
a.reduce(:+).try(:to_f).try(:/,a.size) #==> nil

但是,我必须承认,“try”是一个Rails助手。但你可以很容易地解决这个问题:

class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end

顺便说一句:我认为空列表的平均值为零是正确的。零的平均值是零,不是0。这就是预期行为。但是,如果您更改为:

class Array;def avg;reduce(0.0,:+).try(:/,size);end;end

空数组的结果不会像我预期的那样是一个异常,而是返回NaN…我在Ruby中从未见过这种情况。;-)似乎是Float类的特殊行为…

0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375

解决除零,整数除法,易于阅读。如果您选择让空数组返回0,则可以轻松修改。

我也喜欢这个变体,但是有点啰嗦。

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : [a.reduce(:+), a.size.to_f].reduce(:/)
=> 3.375

arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375

Ruby版本>= 2.4有一个Enumerable#sum方法。

要获得浮点平均值,可以使用Integer#fdiv

arr = [0,4,8,2,5,0,2,6]

arr.sum.fdiv(arr.size)
# => 3.375

对于旧版本:

arr.reduce(:+).fdiv(arr.size)
# => 3.375

你可以试试下面的方法:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0

一些顶级解决方案的基准测试(按效率最高的顺序排列):

大阵:

array = (1..10_000_000).to_a

Benchmark.bm do |bm|
  bm.report { array.instance_eval { reduce(:+) / size.to_f } }
  bm.report { array.sum.fdiv(array.size) }
  bm.report { array.sum / array.size.to_f }
  bm.report { array.reduce(:+).to_f / array.size }
  bm.report { array.reduce(:+).try(:to_f).try(:/, array.size) }
  bm.report { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size }
  bm.report { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) }
end


    user     system      total        real
0.480000   0.000000   0.480000   (0.473920)
0.500000   0.000000   0.500000   (0.502158)
0.500000   0.000000   0.500000   (0.508075)
0.510000   0.000000   0.510000   (0.512600)
0.520000   0.000000   0.520000   (0.516096)
0.760000   0.000000   0.760000   (0.767743)
1.530000   0.000000   1.530000   (1.534404)

小数组:

array = Array.new(10) { rand(0.5..2.0) }

Benchmark.bm do |bm|
  bm.report { 1_000_000.times { array.reduce(:+).to_f / array.size } }
  bm.report { 1_000_000.times { array.sum / array.size.to_f } }
  bm.report { 1_000_000.times { array.sum.fdiv(array.size) } }
  bm.report { 1_000_000.times { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size } }
  bm.report { 1_000_000.times { array.instance_eval { reduce(:+) / size.to_f } } }
  bm.report { 1_000_000.times { array.reduce(:+).try(:to_f).try(:/, array.size) } }
  bm.report { 1_000_000.times { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) } }
end


    user     system      total        real
0.760000   0.000000   0.760000   (0.760353)
0.870000   0.000000   0.870000   (0.876087)
0.900000   0.000000   0.900000   (0.901102)
0.920000   0.000000   0.920000   (0.920888)
0.950000   0.000000   0.950000   (0.952842)
1.690000   0.000000   1.690000   (1.694117)
1.840000   0.010000   1.850000   (1.845623)

这个方法很有用。

def avg(arr)
  val = 0.0

  arr.each do |n|
    val += n
  end

  len = arr.length

  val / len 
end

p avg([0,4,8,2,5,0,2,6])

数组添加#平均水平。

我经常做同样的事情,所以我认为用一个简单的平均方法扩展Array类是谨慎的。除了整数、浮点数或小数之类的数字数组之外,它并不适用于任何东西,但当你正确使用它时,它很方便。

我使用Ruby on Rails,所以我把它放在配置/initializers/array中。Rb但是你可以把它放在boot中包含的任何地方,等等。

配置/初始化/ array.rb

class Array

  # Will only work for an Array of numbers like Integers, Floats or Decimals.
  #
  # Throws various errors when trying to call it on an Array of other types, like Strings.
  # Returns nil for an empty Array.
  #
  def average
    return nil if self.empty?

    self.sum.to_d / self.size
  end

end

无需重复数组(例如,非常适合一行程序):

[1, 2, 3, 4].then { |a| a.sum.to_f / a.size }

打印数组。求和/数组。计数是我做到的方式


还有一个简单的解决方案

arr = [0,4,8,2,5,0,2,6]
arr.sum(0.0) / arr.size

您可以根据需要选择以下解决方案之一。

Bruteforce

[0,4,8,2,5,0,2,6].sum.to_f / [0,4,8,2,5,0,2,6].size.to_f

=> 3.375

方法

def avg(array)
  array.sum.to_f / array.size.to_f
end  

avg([0,4,8,2,5,0,2,6])
=> 3.375

猴子打补丁

class Array
  def avg
    sum.to_f / size.to_f
  end
end

[0,4,8,2,5,0,2,6].avg
=> 3.375

但我不建议对Array类进行猴子补丁,这种做法是危险的,可能会对您的系统造成不良影响。

为了我们的好处,ruby语言提供了一个很好的特性来克服这个问题,即Refinements,这是一种安全的方法来对ruby进行monkey补丁。

为了简化,使用细化,您可以猴子修补数组类,并且更改将只在使用细化的类范围内可用!:)

您可以在您正在处理的类中使用细化,并且您已经准备好了。

细化

module ArrayRefinements
  refine Array do
    def avg
      sum.to_f / size.to_f
    end
  end
end

class MyClass
  using ArrayRefinements

  def test(array)
    array.avg
  end
end

MyClass.new.test([0,4,8,2,5,0,2,6])
=> 3.375

我非常喜欢定义一个mean()方法,这样我的代码更有表现力。

默认情况下我通常会忽略nil,这是我定义的

def mean(arr)
  arr.compact.inject{ |sum, el| sum + el }.to_f / arr.compact.size
end

mean([1, nil, 5])
=> 3.0

如果您想保留nils,只需删除两个.compact。


比.inject更快的解决方案是:

sum(0.0)/arr.size

参见这篇文章参考:https://andycroll.com/ruby/calculate-a-mean-average-from-a-ruby-array/