2013-03-05 21 views
6

Ho codice Scala similiIncremento variabile in un foldLeft

var i = 1 
for(e <- array) { 
    acc += e * i 
    i += 1 
} 

devo moltiplicare il primo elemento della matrice di 1, il prossimo a 2, il successivo 3 e così via aggiungendo tutto in un accumulatore. Sento che c'è un modo migliore per farlo in Scala, magari anche con il folding?

risposta

5
val x = List(1,1,1,1,1,1) 
(((0,1) /: x){case ((acc, mult), l) => (acc + (l * mult), mult + 1) })._1 

In altre parole, iniziando con un accumulatore di 0 e un moltiplicatore di 1, piegare ogni elemento della lista in, cambiando l'accumulatore di acc + (l * mult) e incrementare il moltiplicatore 1. ottenere il moltiplicatore finale presso anche la fine, quindi chiamiamo ._1 per ottenere solo l'accumulatore.

Modifica: come @RexKerr punta nella sua risposta di seguito (e nel commento), se le prestazioni sono un problema importante, allora è meglio usare un metodo ricorsivo esplicito.

+0

risposta incredibile, grazie! – chrissphinx

10

preferisco zipWithIndex che è più semplice da leggere:

array.zipWithIndex.map { case (e, i) => e * (i + 1) }.sum 
+0

ma poi esegui iterazioni sull'array due volte dalla mia comprensione di quel metodo, corretto? – chrissphinx

+0

Tre volte. zipWithIndex e mappa entrambi iterare sull'array e sum è implementato tramite foldLeft. Quindi il mio approccio non è certamente il più veloce – maxmc

+0

sicuramente più leggibile, comunque – chrissphinx

2

Non sono sicuro se quello che suggerisco è un modo migliore per farlo o no, in quanto è più funzionale (== esso deve svolgere più lento):

(0 /: (array zipWithIndex)) {(a, i) => (i._1 * (i._2 + 1)) + a}

Ciò esegue foldLeft su una matrice che è prodotto con il metodo zipWithIndex in http://www.scala-lang.org/api/current/index.html#scala.Array

zipWithIndex cancella semplicemente gli elementi di una collezione con i loro indici.

15

"Meglio" dipende da quali sono i tuoi obiettivi. Breve e chiaro? Probabilmente

{ for (i <- array.indices; e = array(i)) yield (i+1)*e }.sum 

o

array.indices.map(i => (i+1)*array(i)).sum 

(o leggermente più veloce, dal momento che si creano i prodotti intermedi, come si va:

array.indices.iterator.map(i => (i+1)*array(i)).sum 

).

Di solito dovresti essere breve e chiaro.

Veloce? Allora avrete bisogno di andare vecchia scuola:

var i = 0 
var acc = 0 
while (i < array.length) { 
    acc += (i+1)*array(i) 
    i += 1 
} 

o l'uso ricorsione

def sum(a: Array[Int], i: Int = 0, acc: Int = 0): Int = 
    if (i >= a.length) acc else sum(a, i+1, (i+1)*a(i) + acc) 
sum(array) 
+0

È probabile che la ricorsione esplicita sia più veloce di una piega? L'unico vantaggio che posso vedere non è quello di allocare la tupla ... – Impredicative

+4

@Impredicative - Se stai facendo operazioni leggere come l'aggiunta di numeri interi, non allocare la tupla è enorme (10 volte più veloce). Inoltre, le raccolte non sono specializzate per le primitive, il che significa che ogni numero intero deve essere inserito in una scatola, mentre la ricorsione può usare tipi primitivi. Anche enorme (entrambi insieme danno ~ 20x-30x di accelerazione). –

+0

Entrambi ottimi punti! – Impredicative