2010-11-25 10 views
1

J'ai un simple morceau de code Scala. Je boucle séquentiellement à travers une liste de chaînes, et je veux compter l'occurrence de chaque chaîne que je recueille comme tuples (String, Int) dans la liste r. La partie dans la fonction principale doit rester (donc pas groupBy ou quelque chose). Ma question concerne la fonction de mise à jour:Puis-je compter l'occurrence dans 1 boucle dans ce fragment Scala?

maintenant, je fais d'abord un find, puis j'ajoute un nouveau tuple à r s'il n'existe pas. Si elle existe, je fais une boucle sur r et met à jour le compteur pour la chaîne correspondante.

la fonction de mise à jour peut être modifiée de sorte qu'il est plus efficace? Peut-on mettre à jour en une seule itération (en ajoutant s'il n'existe pas, en mettant à jour le compteur s'il existe)?

Merci

var r = List[(String, Int)]() // (string, count) 

def update(s: String, l: List[(String, Int)]) : List[(String, Int)] = { 
    if (r.find(a => a._1 == s) == None) { 
    (s, 1) :: r // add a new item if it does not exist 
    } else { 
    for (b <- l) yield { 
     if (b._1 == s) { 
     (b._1, b._2 + 1) // update counter if exists 
     } else { 
     b // just yield if no match 
     } 
    } 
    } 
} 

def main(args : Array[String]) : Unit = { 
    val l = "A" :: "B" :: "A" :: "C" :: "A" :: "B" :: Nil 

    for (s <- l) r = update(s, r) 

    r foreach println 
} 
+1

Je ne comprends pas exactement ce que votre objection à 'groupBy' est. Il semble dire que le code de la définition 'main' ne peut pas être changé par la solution; Cela pourrait aussi signifier que «l» ne peut pas être changé. Alors, pourriez-vous clarifier s'il vous plaît? –

+0

principalement parce que je veux aussi travailler avec des collections paresseuses. Ou pouvez-vous également utiliser 'groupBy' pour les collections paresseuses et' Streams' (ou 'Source.getLines')? Si oui, donnez-moi un exemple. –

Répondre

4

Si vous ne souhaitez pas utiliser groupBy ou travailler avec des collections paresseux (cours d'eau), cela pourrait être la voie à suivre:

ss.foldLeft(Map[String, Int]())((m, s) => m + (s -> (m.getOrElse(s, 0) + 1))) 
+1

qui ne renvoie un 'Map' au lieu d'un' list', cependant, et n'est pas un remplaçant pour remplacer 'update' (même si je vois @JanWillem a accepté votre réponse :). Ce –

+0

est vrai, le type de retour est différent, mais il ne se conforme à l'objectif que je veux atteindre: « ... Je veux compter l'apparition de chaque chaîne ... » –

22

Je vous suggère d'aller pour le style fonctionnel et d'utiliser la puissance des collections de Scala:

ss.groupBy(identity).mapValues(_.size) 
+2

's => s' est prédéfini comme' identity' – Landei

+0

'mapValues' à la place? –

+4

Cette solution arrive si souvent, elle devrait vraiment aller dans la bibliothèque de base. J'appellerais la méthode 'tally'. –

1

Quelque chose comme cela fonctionne aussi:

val l = List("A","B","A","C","A","B") 

l.foldLeft(Map[String,Int]()) { 
    case (a: Map[String, Int], s: String) => { 
    a + (s -> (1 + a.getOrElse(s, 0))) 
    } 
} 

res3: scala.collection.immutable.Map[String,Int] = Map((A,3), (B,2), (C,1)) 
1

Votre solution actuelle est horriblement lent, principalement en raison du choix de r comme List. Mais votre itération dans toute la liste en cas de mise à jour peut être améliorée, au moins. Je l'écris comme ce

def update(s: String, l: List[(String, Int)]) : List[(String, Int)] = { 
    l span (_._1 != s) match { 
    case (before, (`s`, count) :: after) => before ::: (s, count + 1) :: after 
    case _ => (s, 1) :: l 
    } 
} 

En utilisant span évite d'avoir à rechercher dans la liste deux fois. En outre, nous ajoutons simplement after, sans avoir à recommencer.

+0

Merci! C'est une rétroaction précieuse! –