2010-11-24 4 views
4

Je suis assez nouveau à Scala mais j'aime savoir quelle est la meilleure façon de résoudre ce problème. Dites que j'ai une liste d'articles et que je veux connaître le montant total des articles qui sont des chèques. Je pouvais faire quelque chose comme ceci:Scala vals vs vars

val total = items.filter(_.itemType == CHECK).map(._amount).sum 

qui me donnerait ce que je dois, la somme de tous les chèques dans une variable immuable. Mais il le fait avec ce qui semble être 3 itérations. Une fois pour filtrer les contrôles, encore une fois pour cartographier les montants puis la somme. Une autre façon serait de faire quelque chose comme:

var total = new BigDecimal(0) 
for (
    item <- items 
    if item.itemType == CHECK 
) total += item.amount 

Cela me donne le même résultat mais avec 1 itération et une variable mutable qui semble bien aussi. Mais si je voulais extraire plus d'informations, disons le nombre total de vérifications, cela nécessiterait plus de compteurs ou de variables modifiables, mais je n'aurais pas à recommencer à parcourir la liste. Cela ne semble pas être la manière "fonctionnelle" de réaliser ce dont j'ai besoin.

var numOfChecks = 0 
var total = new BigDecimal(0) 
items.foreach { item => 
    if (item.itemType == CHECK) { 
     numOfChecks += 1 
     total += item.amount 
    } 
} 

Donc, si vous vous trouvez avoir besoin d'un tas de compteurs ou totaux sur une liste est-il préférable de garder les variables mutables ou vous inquiétez pas à ce sujet faire quelque chose le long des lignes de:

val checks = items.filter(_.itemType == CHECK) 
val total = checks.map(_.amount).sum 
return (checks.size, total) 

qui semble plus facile à lire et utilise uniquement vals

+1

L'expression 'for' est en fait compilée en' map', en 'filter' et en' foreach', ce qui équivaut à votre premier exemple. – Theo

+1

Ce n'est plus précis: le pour la compréhension utilise withFilter, pas de filtre, sauf si la collection n'a pas de méthode withFilter. – extempore

Répondre

6

Vous peut utiliser foldLeft pour que:

(0 /: items) ((total, item) => 
    if(item.itemType == CHECK) 
     total + item.amount 
    else 
     total 
) 

Le code suivant retourne un tuple (nombre de contrôles -> somme des montants):

((0, 0) /: items) ((total, item) => 
    if(item.itemType == CHECK) 
     (total._1 + 1, total._2 + item.amount) 
    else 
     total 
) 
+0

oublié de plier/cela me donnerait toutes mes données de retour dans un joli tuple –

8

Une autre façon de résoudre votre problème dans une itération serait d'utiliser des vues ou itérateurs:

items.iterator.filter(_.itemType == CHECK).map(._amount).sum 

ou

items.view.filter(_.itemType == CHECK).map(._amount).sum 

de cette façon, l'évaluation de l'expression est retardée jusqu'à ce que l'appel de sum.

Si vos articles sont des classes de cas, vous pouvez écrire comme ceci:

items.iterator collect { case Item(amount, CHECK) => amount } sum 
+0

merci/je n'étais pas au courant de la méthode 'view' –

7

Je trouve que parler de faire « trois itérations » est un peu trompeur - après tout, chaque itération fait moins de travail que une seule itération avec tout. Il ne s'ensuit donc pas automatiquement que l'itération trois fois prendra plus de temps que l'itération une fois.

Création d'objets temporaires, maintenant que est un problème, car vous allez rencontrer de la mémoire (même si elle est mise en cache), ce qui n'est pas le cas de l'itération unique. Dans ces cas, view aidera, même si elle ajoute plus d'appels de méthode pour faire le même travail. Heureusement, JVM va optimiser cela. Voir Moritzanswer pour plus d'informations sur les vues.