如何从Ruby数组创建平均值?


209

如何从数组中找到平均值?

如果我有数组:

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

平均会给我3.375。


11
如果您得到的平均数字是21.75,那是很不对劲……
ceejayoz

2
有点,不确定如何获得21.75,但是该组数据的平均值/平均值为3.375,总和为27。我不确定哪种聚合函数会产生21.75。请仔细检查并确保平均值确实符合您的要求!
Paul Sasik,09年

2
我不知道我从哪里得到21.75。必须在计算器上按0 + 48 + 2 + 5 + 0 + 2 + 6之类的东西!

16
由于这也被标记为“ ruby​​-on-rails”,因此,如果要对ActiveRecord数组求平均,则值得研究活动记录计算。Person.average(:age,:country =>'巴西')返回来自巴西的人们的平均年龄。太酷了!
凯尔·赫罗尼穆斯

Answers:


259

试试这个:

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,它可能会像看起来那样神奇。它遍历每个元素,然后对其应用累加器值。然后将累加器移至下一个元素。在这种情况下,我们的累加器只是一个整数,它反映所有先前元素的总和。

编辑:评论员Dave Ray提出了一个不错的改进。

编辑:评论员格伦·杰克曼(Glenn Jackman)的建议使用arr.inject(:+).to_f也是不错的,但是如果您不知道发生了什么,也许有点太聪明。的:+是一个符号; 当传递给注入时,它将对累加器值应用由符号命名的方法(在本例中为加法运算)到每个元素。


6
您可以消除to_f和?运算符通过传递初始值来注入:arr.inject(0.0) { |sum,el| sum + el } / arr.size
戴夫·雷

103
或者:arr.inject(:+)。to_f / arr.size#=> 3.375
格伦·杰克曼

5
我认为这不应该添加到Array类中,因为它不能推广到Arrays可以包含的所有类型。
莎拉·梅

8
@John:这不完全是Symbol#to_proc的转换— inject是文档中提到的接口的一部分。该to_proc运营商&
Chuck

21
如果您使用的是Rails,Array#inject在这里过大了。只需使用#sum。例如arr.sum.to_f / arr.size
nickh

113
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

4
我认为这不太聪明。我认为它可以解决问题。即,它使用reduce,这是完全正确的。应该鼓励程序员了解正确的内容,正确的原因,然后进行传播。对于琐碎的操作,例如平均值,真实操作,不需要“聪明”。但是通过了解“减少”对于一个平凡的情况,然后可以将其应用于更复杂的问题。投票。
pduey 2012年

3
为什么在这里需要instance_eval?
tybro0103

10
instance_eval使您仅指定a一次即可运行代码,因此可以将其与其他命令链接在一起。即random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } 代替random = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
本杰明·曼斯

2
我不知道,以这种方式使用instance_eval似乎很奇怪,并且它具有许多与之相关的陷阱,使这种方法成为一个坏主意,IMO。(例如,如果您尝试访问self该块内的实例变量或方法,则会遇到问题。)instance_eval元编程或DSL的使用更多。
Ajedi32

1
@ Ajedi32我同意,不要在您的应用程序代码中使用它。这是却很高兴能够将其粘贴到我的REPL(:
animatedgif

94

我相信最简单的答案是

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

1
我花了一点时间来找到它- reduce是的方法Enumerable所使用的mixin Array。尽管它有名字,但我同意@ShuWu ...,除非您使用的是实现了的Rails sum
汤姆·哈里森

我在这里看到了解决方案,我知道它们看起来非常整洁,但是如果将来以后阅读我的代码,恐怕他们会觉得很乱。感谢您的干净解决方案!
atmosx 2014年

在我的系统上,这比接受的答案快3倍。
sergio

48

我希望得到Math.average(values),但是没有这种运气。

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

3
我没有意识到Rails添加了#sum!感谢您指出了这一点。
丹尼·亚伯拉罕

11
在2016年圣诞节(Ruby 2.4)之后,Array 提供一种sum方法,因此这似乎是6年后的正确答案,值得Nostradamus奖。
steenslag

38

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

9

最佳解决方案的一些基准测试(以最有效的顺序):

大数组:

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)

您的基准测试有误。基准/ ips实际上对于此类比较而言更好。我也建议使用随机填充的带有负数和正数以及浮点数的数组,以获得更真实的结果。您会发现instance_eval比array.sum.fdiv慢。浮点数大约是8倍。整数约为x1.12。另外,不同的操作系统将给出不同的结果。在我的Mac上,其中某些方法的速度是Linux Droplet上的2倍
konung

同样,求和方法在范围上使用高斯公式代替计算总和。
Santhosh

4
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

2
由于整数除法,这将返回不正确的值。尝试使用例如[2,3] .mean,它返回2而不是2.5。
约翰·费米内拉

1
为什么一个空数组的总和不为nil0?
安德鲁·格林

1
因为您可以获得[]和[0]之间的差异。而且我认为每个想要真正中庸的人都可以使用to_i或将上述nil替换为0
astropanic 2011年

4

让我带些东西来解决零分问题:

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

但是,我必须承认,“尝试”是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

4

我对接受的解决方案不满意的地方

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

它不能真正以纯粹的功能方式工作。我们需要一个变量arr来最后计算arr.size。

为了从功能上解决这个问题,我们需要跟踪两个值:所有元素的总和以及元素的数量。

[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

我们还可以使用结构而不是数组来包含总和和计数,但是随后我们必须首先声明该结构:

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(:/)

这是我第一次看到end.method用在红宝石中,谢谢!
Epigene

传递给注入方法的数组可以分散。arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh '18年

@Santhosh:是的,这更具可读性!我不会称其为“分散”,而是称其为“销毁” tony.pitluga.com/2011/08/08/destructuring-with-ruby.html
bjelli


2

在这台PC上没有红宝石,但在某种程度上应该可以工作:

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

average = total/values.size

2

添加Array#average

我经常做同样的事情,所以我认为Array用一个简单的average方法扩展类是审慎的做法。除整数,浮点数或小数之类的数字数组外,它对其他任何功能均不起作用,但正确使用它非常方便。

我正在使用Ruby on Rails,因此已将其放置在其中,config/initializers/array.rb但是您可以将其放置在引导等包含的任何位置。

config/initializers/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 / self.size
  end

end

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

4
由于整数除法,这将返回不正确的值。例如,如果是[2,3],预期的结果是2.5,但是你会回到2
约翰Feminella

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

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

我也喜欢这个变体,但是有点罗word。

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


1

此方法可能会有所帮助。

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])

1
欢迎堆栈溢出这里的问题楼主想要的答案为3.375和您的解决方案提供3. I,E8分之27= 3
阿贾伊巴罗特

谢谢您的意见。我知道问题的原始发布者希望答案为3.375,这就是该方法的作用,因为我为变量'var'赋予了浮点值(即0.0)。Munim Munna我必须同意你的观点,确实存在类似的问题。
Kishor Budhathoki

0

无需重复排列(例如,非常适合单线):

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


-1

您可以尝试以下操作:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.