Ruby配列から平均を作成するにはどうすればよいですか?


209

どのように配列から平均を見つけるのでしょうか?

配列がある場合:

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

平均すると3.375になります。


11
これらの数値の平均として21.75を取得している場合、何かが非常に間違っています...
ceejayoz '27

2
どのように21.75を取得したかわかりませんが、そのデータのセットの平均/平均は3.375で、合計は27です。21.75を生成する集計関数の種類はわかりません。もう一度確認して、平均値が実際の値であることを確認してください!
ポールサシク2009

2
21.75をどこから取得したのかわかりません。電卓で0 + 48 + 2 + 5 + 0 + 2 + 6のようなものを押す必要がありました!
ドッティ

16
これもruby-on-railsとタグ付けされているため、ActiveRecord配列を平均化する場合は、アクティブレコードの計算を検討する価値があります。Person.average(:age、:country => 'Brazil')は、ブラジルの人々の平均年齢を返します。かなりクール!
Kyle Heironimus、

回答:


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)もいいですが、何が起こっているのかわからない場合は、少し賢すぎるかもしれません。:+シンボルです。injectに渡されると、シンボルで指定されたメソッド(この場合は加算演算)が各要素にアキュムレータ値に対して適用されます。


6
to_fと?注入する初期値を渡すことによって、操作者:arr.inject(0.0) { |sum,el| sum + el } / arr.size
デイブ・レイ

103
または:arr.inject(:+)。to_f / arr.size#=> 3.375
glenn jackman '27

5
これは、Arrayに含めることができるすべての型に一般化できるわけではないので、Arrayクラスに追加する必要はないと思います。
サラ・メイ

8
@John:これは、正確にはSymbol#to_proc変換ではありません。これinjectは、ドキュメントで言及されているインターフェースの一部です。to_procオペレータがあります&
チャック、

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を使用しますが、これは正確です。プログラマーは、何が正しいのか、なぜそれが正しいのかを理解し、そして伝播するように奨励されるべきです。平均、真のような些細な操作の場合、「賢い」必要はありません。しかし、「リデュース」が些細な場合に何であるかを理解することで、それをはるかに複雑な問題に適用し始めることができます。賛成票。
2012

3
なぜここでinstance_evalが必要なのですか?
tybro0103

10
instance_evala一度だけ指定してコードを実行できるため、他のコマンドとチェーンすることができます。すなわち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
それを見つけるのに少し時間がかかりました- reduceEnumerable使用するミックスインの方法です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
#sumがRailsによって追加されたことに気付きませんでした!ご指摘いただきありがとうございます。
デニーアブラハム

11
2016年のクリスマス(Ruby 2.4)以降、Array sumメソッドがあるため、これは6年後の正解であり、ノストラダムス賞に値するものです。
steenslag 2016年


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。また、OSによって結果は異なります。私のMacでは、これらの方法のいくつかはLinuxドロップレットの2倍遅い
konung

また、合計方法では、合計を計算する代わりに、範囲に対してガウスの公式を使用します。
サントッシュ2018

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.5ではなく2を返す[2,3] .meanで試してください。
John Feminella、2009

1
空の配列の合計がnil0ではなく合計である必要があるのはなぜですか?
Andrew Grimm、

1
[]と[0]の違いがわかるからです。そして、私は考えて本当の平均はto_iを利用したり、0に上記nilを置き換えることができますしたいすべての人
astropanic

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.sizeを計算するには、変数arrが必要です。

これを純粋に機能的に解決するには、すべての要素の合計と要素の数という2つの値を追跡する必要があります。

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

Santhoshはこのソリューションを改善しました。引数rが配列である代わりに、構造化を使用してすぐに2つの変数に分解できます。

[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

injectメソッドに渡された配列は分散できます。arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@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メソッドでクラスを拡張するのが賢明だと思いました。Integers、Floats、Decimalsなどの数値の配列以外には機能しませんが、正しく使用すると便利です。

私は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を返すように選択すると、簡単に変更できます。

私もこの亜種が好きですが、少し冗長です。

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、つまり27/8 = 3を示します
Ajay Barot

コメントしてくださってありがとうございます。私は質問の元のポスターが3.375として回答を求めていることを知っています。これは、変数 'var'に浮動小数点値(つまり0.0)を指定したときにこのメソッドが行うことです。Munim Munna私はuに同意する必要があります。確かに同様のansがあります。
キショールブダトキ

0

配列を繰り返す必要なし(例:ワンライナーに最適):

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.