Come ottenere una media da un array?
Se ho l'array:
[0,4,8,2,5,0,2,6]
La media mi darebbe 3.375.
Come ottenere una media da un array?
Se ho l'array:
[0,4,8,2,5,0,2,6]
La media mi darebbe 3.375.
Risposte:
Prova questo:
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
Nota .to_f
, che ti consigliamo di evitare problemi dalla divisione di numeri interi. Puoi anche fare:
arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5
Puoi definirlo come Array
suggerito da un altro commentatore, ma devi evitare la divisione di interi o i tuoi risultati saranno errati. Inoltre, questo non è generalmente applicabile a ogni possibile tipo di elemento (ovviamente, una media ha senso solo per le cose che possono essere mediate). Ma se vuoi seguire questa strada, usa questo:
class Array
def sum
inject(0.0) { |result, el| result + el }
end
def mean
sum / size
end
end
Se non l'hai mai visto inject
prima, non è magico come potrebbe apparire. Esegue l'iterazione su ciascun elemento e quindi applica un valore di accumulatore ad esso. L'accumulatore viene quindi passato all'elemento successivo. In questo caso, il nostro accumulatore è semplicemente un numero intero che riflette la somma di tutti gli elementi precedenti.
Modifica: commentatore Dave Ray ha proposto un bel miglioramento.
Modifica: la proposta del commentatore Glenn Jackman, usando arr.inject(:+).to_f
, è anche carina ma forse un po 'troppo intelligente se non sai cosa sta succedendo. Il :+
è un simbolo; quando viene passato per l'iniezione, applica il metodo indicato dal simbolo (in questo caso, l'operazione di aggiunta) a ciascun elemento rispetto al valore dell'accumulatore.
arr.inject(0.0) { |sum,el| sum + el } / arr.size
.
inject
dell'interfaccia, menzionata nella documentazione. L' to_proc
operatore è &
.
Array#inject
è eccessivo qui. Basta usare #sum
. Ad esempioarr.sum.to_f / arr.size
a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375
Una versione di questo che non utilizza instance_eval
sarebbe:
a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
instance_eval
ti consente di eseguire il codice specificando solo a
una volta, quindi può essere concatenato con altri comandi. Vale a dire random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f }
invecerandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
self
all'interno di quel blocco, si verificherebbero problemi.) instance_eval
È più per metaprogrammazione o DSL.
Credo che la risposta più semplice sia
list.reduce(:+).to_f / list.size
reduce
è un metodo del Enumerable
mixin usato da Array
. E nonostante il suo nome, sono d'accordo con @ShuWu ... a meno che tu non stia usando Rails che implementa sum
.
Speravo in Math.average (valori), ma nessuna tale fortuna.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
metodo, quindi questa sembra essere una risposta corretta dopo 6 anni, degna del premio Nostradamus.
Le versioni di Ruby> = 2.4 hanno una somma enumerabile # metodo .
E per ottenere una media in virgola mobile, puoi usare Integer # fdiv
arr = [0,4,8,2,5,0,2,6]
arr.sum.fdiv(arr.size)
# => 3.375
Per le versioni precedenti:
arr.reduce(:+).fdiv(arr.size)
# => 3.375
Alcuni benchmarking delle migliori soluzioni (in ordine di efficienza):
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
anziché 0?
Consentitemi di mettere in competizione qualcosa che risolva la divisione per zero problemi:
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 ammettere, tuttavia, che "provare" è un aiuto di Rails. Ma puoi risolverlo facilmente:
class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end
A proposito: penso che sia corretto che la media di una lista vuota sia nulla. La media di nulla è nulla, non 0. Quindi questo è il comportamento previsto. Tuttavia, se si passa a:
class Array;def avg;reduce(0.0,:+).try(:/,size);end;end
il risultato per gli array vuoti non sarà un'eccezione come mi aspettavo, ma invece restituisce NaN ... Non l'avevo mai visto prima in Ruby. ;-) Sembra essere un comportamento speciale della classe Float ...
0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
cosa non mi piace della soluzione accettata
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
è che non funziona davvero in modo puramente funzionale. abbiamo bisogno di una variabile arr per calcolare la dimensione arr alla fine.
per risolverlo in modo puramente funzionale dobbiamo tenere traccia di due valori: la somma di tutti gli elementi e il numero di elementi.
[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
[ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5
Santhosh è migliorato su questa soluzione: invece che l'argomento r è un array, possiamo usare la destrutturazione per separarlo immediatamente in due variabili
[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele|
[ sum + ele, size + 1 ]
end.inject(:/)
se vuoi vedere come funziona, aggiungi alcuni put:
[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
Potremmo anche usare una struttura anziché una matrice per contenere la somma e il conteggio, ma poi dobbiamo prima dichiarare la struttura:
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
usato nel rubino, grazie per questo!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Per divertimento pubblico, l'ennesima soluzione:
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
.Stavo facendo la stessa cosa abbastanza spesso, quindi ho pensato che fosse prudente estendere la Array
lezione semplicemente con un sempliceaverage
metodo . Non funziona per nulla oltre a una matrice di numeri come numeri interi o float o decimali, ma è utile quando lo usi nel modo giusto.
Sto usando Ruby on Rails, quindi l'ho inserito config/initializers/array.rb
ma puoi posizionarlo ovunque incluso all'avvio, ecc.
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
Risolve la divisione per zero, la divisione intera ed è facile da leggere. Può essere facilmente modificato se si sceglie di avere un array vuoto restituito 0.
Mi piace anche questa variante, ma è un po 'più prolissa.
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
Questo metodo può essere utile.
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
Breve ma con variabile d'istanza
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
piuttosto che creare una variabile di istanza.
Puoi provare qualcosa di simile al seguente:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0