Como obter uma média de uma matriz?
Se eu tiver a matriz:
[0,4,8,2,5,0,2,6]
A média me daria 3.375.
Como obter uma média de uma matriz?
Se eu tiver a matriz:
[0,4,8,2,5,0,2,6]
A média me daria 3.375.
Respostas:
Tente o seguinte:
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
Observe o .to_f
que você deseja evitar problemas da divisão inteira. Você também pode fazer:
arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5
Você pode defini-lo como parte de Array
outro usuário sugerido, mas precisa evitar a divisão de números inteiros ou seus resultados estarão errados. Além disso, isso geralmente não é aplicável a todos os tipos de elementos possíveis (obviamente, uma média só faz sentido para coisas que podem ser calculadas em média). Mas se você quiser seguir esse caminho, use o seguinte:
class Array
def sum
inject(0.0) { |result, el| result + el }
end
def mean
sum / size
end
end
Se você nunca viu inject
antes, não é tão mágico quanto parece. Ele itera sobre cada elemento e, em seguida, aplica um valor acumulador a ele. O acumulador é então entregue ao próximo elemento. Nesse caso, nosso acumulador é simplesmente um número inteiro que reflete a soma de todos os elementos anteriores.
Editar: O comentarista Dave Ray propôs uma boa melhoria.
Edit: A proposta do comentarista Glenn Jackman, usando arr.inject(:+).to_f
, também é legal, mas talvez um pouco inteligente demais, se você não sabe o que está acontecendo. O :+
é um símbolo; quando passado para injetar, aplica o método nomeado pelo símbolo (neste caso, a operação de adição) a cada elemento em relação ao valor do acumulador.
arr.inject(0.0) { |sum,el| sum + el } / arr.size
.
inject
interface, mencionada na documentação. O to_proc
operador é &
.
Array#inject
é um exagero aqui. Apenas use #sum
. Exemploarr.sum.to_f / arr.size
a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375
Uma versão disso que não usa instance_eval
seria:
a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
instance_eval
permite executar o código enquanto especifica apenas a
uma vez, para que ele possa ser encadeado com outros comandos. Ou seja, em random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f }
vez derandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
self
dentro desse bloco, teria problemas.) instance_eval
É mais para metaprogramação ou DSL.
Eu acredito que a resposta mais simples é
list.reduce(:+).to_f / list.size
reduce
é um método do Enumerable
mixin usado por Array
. E, apesar do nome, concordo com o @ShuWu ... a menos que você esteja usando o Rails que implementa sum
.
Eu estava esperando Math.average (values), mas não tive essa sorte.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
método, portanto essa parece ser uma resposta correta após 6 anos, digna do prêmio Nostradamus.
As versões do Ruby> = 2.4 têm um método # sum enumerável.
E para obter uma média de ponto flutuante, você pode usar o número inteiro # fdiv
arr = [0,4,8,2,5,0,2,6]
arr.sum.fdiv(arr.size)
# => 3.375
Para versões mais antigas:
arr.reduce(:+).fdiv(arr.size)
# => 3.375
Alguns testes comparativos das principais soluções (na ordem das mais eficientes):
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)
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
nil
vez de 0?
Deixe-me colocar algo em competição que resolva a divisão pelo problema zero:
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
Devo admitir, no entanto, que "try" é um ajudante do Rails. Mas você pode facilmente resolver isso:
class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end
BTW: Eu acho correto que a média de uma lista vazia seja nula. A média de nada é nada, não 0. Portanto, esse é o comportamento esperado. No entanto, se você mudar para:
class Array;def avg;reduce(0.0,:+).try(:/,size);end;end
o resultado para Arrays vazios não será uma exceção, como eu esperava, mas retorna NaN ... Eu nunca vi isso antes em Ruby. ;-) Parece ser um comportamento especial da classe Float ...
0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
o que eu não gosto na solução aceita
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
é que realmente não funciona de maneira puramente funcional. precisamos de uma variável arr para calcular arr.size no final.
para resolver isso puramente funcionalmente, precisamos acompanhar dois valores: a soma de todos os elementos e o número de elementos.
[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
[ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5
Santhosh aprimorou esta solução: em vez de o argumento r ser uma matriz, poderíamos usar a desestruturação para separá-la imediatamente em duas variáveis
[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele|
[ sum + ele, size + 1 ]
end.inject(:/)
se você quiser ver como funciona, adicione alguns putos:
[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
Também poderíamos usar uma estrutura em vez de uma matriz para conter a soma e a contagem, mas depois temos que declarar a estrutura primeiro:
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
ruby, obrigado por isso!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Para diversão do público, mais uma solução:
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
Array#average
.Eu fazia a mesma coisa com frequência, então achei prudente estender a Array
classe apenas com um average
método simples . Ele não funciona para nada além de uma matriz de números como números inteiros ou flutuantes ou decimais, mas é útil quando você o usa corretamente.
Estou usando Ruby on Rails, então coloquei isso, config/initializers/array.rb
mas você pode colocá-lo em qualquer lugar que esteja incluído na inicialização, etc.
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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375
Resolve dividir por zero, divisão inteira e é fácil de ler. Pode ser facilmente modificado se você escolher que uma matriz vazia retorne 0.
Também gosto dessa variante, mas é um pouco mais prolixo.
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
Este método pode ser útil.
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,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
Variável de instância curta, mas usando
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
vez de criar uma variável de instância.
Você pode tentar algo como o seguinte:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0