ChatGPT解决这个技术问题 Extra ChatGPT

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

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

如果我有数组:

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

平均会给我 3.375。

如果你得到 21.75 作为这些数字的平均值,那就大错特错了……
dotty,不知道你是如何得到 21.75 的,但该组数据的平均值/平均值是 3.375,总和是 27。我不确定哪种聚合函数会产生 21.75。请仔细检查并确保平均值确实是您所追求的!
我不知道我从哪里得到 21.75。必须在计算器上按 0+48+2+5+0+2+6 之类的东西!
由于这也被标记为 ruby-on-rails,如果您正在平均 ActiveRecord 数组,则值得研究活动记录计算。 Person.average(:age, :country => 'Brazil') 返回巴西人的平均年龄。很酷!

J
Joshua Pinter

尝试这个:

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,也很好,但如果您不知道发生了什么,可能有点太聪明了。 :+ 是一个符号;当传递给注入时,它会将符号命名的方法(在本例中为加法运算)应用于每个元素的累加器值。


你可以消除 to_f 和 ?运算符通过将初始值传递给注入:arr.inject(0.0) { |sum,el| sum + el } / arr.size
或者: arr.inject(:+).to_f / arr.size # => 3.375
我认为这不值得添加到 Array 类,因为它不能推广到 Arrays 可以包含的所有类型。
@John:这不完全是 Symbol#to_proc 转换——它是文档中提到的 inject 接口的一部分。 to_proc 运算符是 &
如果您使用的是 Rails,Array#inject 在这里是多余的。只需使用 #sum。例如arr.sum.to_f / arr.size
B
BBonifield
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

我不认为这太聪明了。我认为它习惯性地解决了这个问题。即,它使用reduce,这是完全正确的。应该鼓励程序员理解什么是正确的,为什么它是正确的,然后传播。对于像平均这样的微不足道的操作,真的,不需要“聪明”。但是通过了解什么是“减少”对于一个琐碎的案例,人们就可以开始将它应用到更复杂的问题上。赞成。
为什么这里需要 instance_eval ?
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
我不知道,以这种方式使用 instance_eval 似乎很奇怪,并且它有很多与之相关的陷阱,这使得这种方法成为一个坏主意,IMO。 (例如,如果您尝试访问该块内 self 上的实例变量或方法,您会遇到问题。)instance_eval 更多用于元编程或 DSL。
@Ajedi32 我同意,不要在您的应用程序代码中使用它。然而,能够粘贴到我的 repl (:
U
Udo Held

我相信最简单的答案是

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

我花了一点时间才找到它——reduceArray 使用的 Enumerable mixin 的一种方法。尽管它的名字,我同意@ShuWu ...除非您使用的是实现sum的Rails。
我在这里看到了解决方案,我知道这看起来非常整洁,但我担心如果我将来阅读我的代码,他们会喜欢胡言乱语。感谢您提供干净的解决方案!
在我的系统上,这比接受的答案快 3 倍。
M
Mohammad

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

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

我没有意识到#sum 是由 Rails 添加的!感谢您指出了这一点。
在 2016 年圣诞节 (Ruby 2.4) 之后,Array 有一个 sum 方法,因此这似乎是 6 年后的正确答案,值得诺查丹玛斯奖。
S
Santhosh

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

s
stevenspiel

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

大数组:

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)

您的基准测试有点错误。 benchmark/ips 实际上更适合此类比较。另外我建议使用一个随机填充负数和正数以及浮点数的数组,以获得更真实的结果。您会发现 instance_eval 比 array.sum.fdiv 慢。浮动约 8 倍。对于整数,大约 x1.12。此外,不同的操作系统会给出不同的结果。在我的 Mac 上,其中一些方法比我的 Linux Droplet 慢 2 倍
sum 方法也使用高斯公式,在范围上而不是计算总和。
D
Dorian

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

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

我喜欢.then
A
Andrew Grimm
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,3].mean,它返回 2 而不是 2.5。
为什么空数组的和应该是 nil 而不是 0?
因为你可以得到 [] 和 [0] 之间的区别。而且我认为每个想要真正平均值的人都可以使用 to_i 或将上面的 nil 替换为 0
h
hurikhan77

让我将一些东西带入竞争中,以解决除零问题:

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

B
Boris Stitnicky

对于公共娱乐,还有另一种解决方案:

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

如果这在投票中更高,我将无法理解!很好。
清晰胜于聪明,这段代码不清楚。
b
bjelli

我不喜欢接受的解决方案

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

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

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

这是我第一次看到在 ruby 中使用 end.method,谢谢!
传递给注入方法的数组可以分散。 arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
@Santhosh:是的,这更具可读性!不过,我不会称其为“分散”,而是称其为“解构”tony.pitluga.com/2011/08/08/destructuring-with-ruby.html
s
saret

这台电脑上没有红宝石,但这种程度的东西应该可以工作:

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

average = total/values.size

y
yvzlkrkt

另一个简单的解决方案

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

V
Victor

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

蛮力

[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 的一种安全方法。

为简化起见,您可以使用 RefinementsArray 类进行猴子修补,并且更改将仅在使用细化的类的范围内可用! :)

您可以在您正在学习的课程中使用细化,然后您就可以开始了。

改进

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

J
Joshua Pinter

添加数组#average。

我经常做同样的事情,所以我认为用一个简单的 average 方法扩展 Array 类是明智的。除了像整数、浮点数或小数这样的数字数组之外,它对任何东西都不起作用,但是当你正确使用它时它很方便。

我正在使用 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.to_d / self.size
  end

end

如果平均值是十进制值,则不起作用。例如:[1, 2, 3, 4].average。它返回 2 而不是 2.5。如果有人需要 Monkey Patch 的完整解决方案,可以在这里找到:stackoverflow.com/a/65337711/7644846
@Victor 你是对的!感谢您的提醒。对我来说,这似乎是一个非常明显的遗漏。我通过在除以 size 之前将 sum 转换为 BigDecimal 来修复它。现在,[1, 2, 3, 4].average #=> 0.25e1。再次感谢。
e
erik
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length

由于整数除法,这将返回不正确的值。例如,如果 a 为 [2, 3],则预期结果为 2.5,但您将返回 2。
M
Matt Stevens
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

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

K
Kishor Budhathoki

这种方法可能会有所帮助。

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

欢迎在这里堆栈溢出问题的原始海报希望答案为 3.375,您的解决方案给出 3。即 27 / 8 = 3。
谢谢您的意见。我知道这个问题的原始海报想要答案为 3.375,这就是这个方法的作用,因为我给变量“var”一个浮点值(即 0.0)。 Munim Munna 我必须同意你的观点,确实有一个类似的答案。
s
seasonalz

print array.sum / array.count 是我如何做到的


s
stevec

我真的很喜欢定义一个 mean() 方法,以便我的代码更具表现力。

我通常想默认忽略 nil,所以这是我定义的

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

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

如果您想保留 nil,只需删除 both .compact


M
MMafiews

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

arr.sum(0.0) / arr.size

请参阅这篇文章以获取参考:https://andycroll.com/ruby/calculate-a-mean-average-from-a-ruby-array/


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

简短但使用实例变量


我会做 a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size 而不是创建实例变量。
d
de-russification

您可以尝试以下方法:

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

当平均值应该是十进制值时不起作用。使用此数组测试此代码:a = [1, 2, 3, 4]。平均值应为 2.5 而不是 2.0。它应该是 a.sum.to_f / a.length.to_f