Scala comment puis-je compter le nombre d'occurrences dans une liste


101
val list = List(1,2,4,2,4,7,3,2,4)

Je veux l'implémenter comme ceci: list.count(2)(renvoie 3).


Je ne sais pas s'il existe un moyen approprié d'obtenir la taille d'une liste dans scala, mais pour votre situation, vous pouvez utiliser une séquence.
Qusay Fantazia

Cette question est-elle toujours sans réponse? Demander parce que vous avez peut-être oublié d'en accepter un.
Tobias Kolb

Réponses:


150

Une version un peu plus claire de l'une des autres réponses est:

val s = Seq("apple", "oranges", "apple", "banana", "apple", "oranges", "oranges")

s.groupBy(identity).mapValues(_.size)

donnant un Mapavec un compte pour chaque élément dans la séquence d'origine:

Map(banana -> 1, oranges -> 3, apple -> 3)

La question demande comment trouver le nombre d'un élément spécifique. Avec cette approche, la solution nécessiterait de mapper l'élément souhaité à sa valeur de comptage comme suit:

s.groupBy(identity).mapValues(_.size)("apple")

2
qu'est-ce que «l'identité»?
Igorock

4
C'est la fonction d'identité, comme discuté ici . La fonction groupBynécessite une fonction qu'elle applique aux éléments afin de savoir comment les regrouper. Une alternative au regroupement des chaînes dans la réponse par leur identité pourrait être, par exemple, le regroupement par leur longueur ( groupBy(_.size)) ou par leur première lettre ( groupBy(_.head)).
ohruunuruus

2
L'inconvénient est que beaucoup de collections inutiles (car seule la taille est nécessaire) sont créées.
Yann Moisan

Et si je voulais définir une carte d'accumulateur dans cette expression au lieu de créer une nouvelle carte?
Tobias Kolb

128

Les collections scala ont count:list.count(_ == 2)


48

J'ai eu le même problème que Sharath Prabhal, et j'ai eu une autre solution (pour moi plus claire):

val s = Seq("apple", "oranges", "apple", "banana", "apple", "oranges", "oranges")
s.groupBy(l => l).map(t => (t._1, t._2.length))

Avec comme résultat:

Map(banana -> 1, oranges -> 3, apple -> 3)

45
Une version un peu plus propre ests.groupBy(identity).mapValues(_.size)
ohruunuruus

1
@ohruunuruus cela devrait être une réponse (vs commentaire); j'adorerais voter avec enthousiasme, si c'était le cas (et le sélectionner comme meilleure réponse si j'étais l'OP);
doug

1
@doug un peu nouveau dans SO et n'était pas sûr, mais heureux d'obliger
ohruunuruus

27
list.groupBy(i=>i).mapValues(_.size)

donne

Map[Int, Int] = Map(1 -> 1, 2 -> 3, 7 -> 1, 3 -> 1, 4 -> 3)

Notez que vous pouvez remplacer (i=>i)par une identityfonction intégrée:

list.groupBy(identity).mapValues(_.size)

aime les solutions courtes utilisant des bibliothèques intégrées
Rustam Aliyev

14
val list = List(1, 2, 4, 2, 4, 7, 3, 2, 4)
// Using the provided count method this would yield the occurrences of each value in the list:
l map(x => l.count(_ == x))

List[Int] = List(1, 3, 3, 3, 3, 1, 1, 3, 3)
// This will yield a list of pairs where the first number is the number from the original list and the second number represents how often the first number occurs in the list:
l map(x => (x, l.count(_ == x)))
// outputs => List[(Int, Int)] = List((1,1), (2,3), (4,3), (2,3), (4,3), (7,1), (3,1), (2,3), (4,3))

1
mais cela donne le num. occurrences pour chaque valeur autant de fois que la valeur se produit - semble inefficace et pas très utile ...
Erik Kaplun

14

En commençant Scala 2.13, la méthode groupMapReduce fait cela en un seul passage dans la liste:

// val seq = Seq("apple", "oranges", "apple", "banana", "apple", "oranges", "oranges")
seq.groupMapReduce(identity)(_ => 1)(_ + _)
// immutable.Map[String,Int] = Map(banana -> 1, oranges -> 3, apple -> 3)
seq.groupMapReduce(identity)(_ => 1)(_ + _)("apple")
// Int = 3

Ce:

  • groupéléments de liste s (partie de groupe du groupe MapReduce)

  • maps chaque occurrence de valeur groupée à 1 (mapper une partie du groupe Map Réduire)

  • reduces valeurs dans un groupe de valeurs ( _ + _) en les additionnant (réduire une partie de groupMap Réduire ).

Ceci est une version en un seul passage de ce qui peut être traduit par:

seq.groupBy(identity).mapValues(_.map(_ => 1).reduce(_ + _))

Bien, c'est ce que je cherchais, j'ai trouvé triste que même les flux Java (qui ne sont pas bons à certains égards) permettent cela en un seul passage alors que Scala ne le pouvait pas.
Dici

9

J'ai rencontré le même problème, mais je voulais compter plusieurs éléments en une seule fois.

val s = Seq("apple", "oranges", "apple", "banana", "apple", "oranges", "oranges")
s.foldLeft(Map.empty[String, Int]) { (m, x) => m + ((x, m.getOrElse(x, 0) + 1)) }
res1: scala.collection.immutable.Map[String,Int] = Map(apple -> 3, oranges -> 3, banana -> 1)

https://gist.github.com/sharathprabhal/6890475


peut-être en utilisant Streamet la réponse acceptée vous donnera votre objectif de "one go" plus un code plus clair.
juanchito le

Cette solution itère la liste une seule fois, en utilisant groupBy, puis map le fera deux fois.
ruloweb

7

Si vous voulez l'utiliser comme list.count(2)vous devez l'implémenter en utilisant une classe implicite .

implicit class Count[T](list: List[T]) {
  def count(n: T): Int = list.count(_ == n)
}

List(1,2,4,2,4,7,3,2,4).count(2)  // returns 3
List(1,2,4,2,4,7,3,2,4).count(5)  // returns 0

7

Réponse courte:

import scalaz._, Scalaz._
xs.foldMap(x => Map(x -> 1))

Longue réponse:

En utilisant Scalaz , donné.

import scalaz._, Scalaz._

val xs = List('a, 'b, 'c, 'c, 'a, 'a, 'b, 'd)

puis tous ceux-ci (dans l'ordre du moins simplifié au plus simplifié)

xs.map(x => Map(x -> 1)).foldMap(identity)
xs.map(x => Map(x -> 1)).foldMap()
xs.map(x => Map(x -> 1)).suml
xs.map(_ -> 1).foldMap(Map(_))
xs.foldMap(x => Map(x -> 1))

rendement

Map('b -> 2, 'a -> 3, 'c -> 2, 'd -> 1)

6

Il est intéressant de noter que la carte avec la valeur par défaut 0, conçue intentionnellement pour ce cas, montre les pires performances (et pas aussi concises que groupBy)

    type Word = String
    type Sentence = Seq[Word]
    type Occurrences = scala.collection.Map[Char, Int]

  def woGrouped(w: Word): Occurrences = {
        w.groupBy(c => c).map({case (c, list) => (c -> list.length)})
  }                                               //> woGrouped: (w: forcomp.threadBug.Word)forcomp.threadBug.Occurrences

  def woGetElse0Map(w: Word): Occurrences = {
        val map = Map[Char, Int]()
        w.foldLeft(map)((m, c) => m + (c -> (m.getOrElse(c, 0) + 1)) )
  }                                               //> woGetElse0Map: (w: forcomp.threadBug.Word)forcomp.threadBug.Occurrences

  def woDeflt0Map(w: Word): Occurrences = {
        val map = Map[Char, Int]().withDefaultValue(0)
        w.foldLeft(map)((m, c) => m + (c -> (m(c) + 1)) )
  }                                               //> woDeflt0Map: (w: forcomp.threadBug.Word)forcomp.threadBug.Occurrences

  def dfltHashMap(w: Word): Occurrences = {
        val map = scala.collection.immutable.HashMap[Char, Int]().withDefaultValue(0)
        w.foldLeft(map)((m, c) => m + (c -> (m(c) + 1)) )
    }                                             //> dfltHashMap: (w: forcomp.threadBug.Word)forcomp.threadBug.Occurrences

    def mmDef(w: Word): Occurrences = {
        val map = scala.collection.mutable.Map[Char, Int]().withDefaultValue(0)
        w.foldLeft(map)((m, c) => m += (c -> (m(c) + 1)) )
  }                                               //> mmDef: (w: forcomp.threadBug.Word)forcomp.threadBug.Occurrences

    val functions = List("grp" -> woGrouped _, "mtbl" -> mmDef _, "else" -> woGetElse0Map _
    , "dfl0" -> woDeflt0Map _, "hash" -> dfltHashMap _
    )                                  //> functions  : List[(String, String => scala.collection.Map[Char,Int])] = Lis
                                                  //| t((grp,<function1>), (mtbl,<function1>), (else,<function1>), (dfl0,<functio
                                                  //| n1>), (hash,<function1>))


    val len = 100 * 1000                      //> len  : Int = 100000
    def test(len: Int) {
        val data: String = scala.util.Random.alphanumeric.take(len).toList.mkString
        val firstResult = functions.head._2(data)

        def run(f: Word => Occurrences): Int = {
            val time1 = System.currentTimeMillis()
            val result= f(data)
            val time2 = (System.currentTimeMillis() - time1)
            assert(result.toSet == firstResult.toSet)
            time2.toInt
        }

        def log(results: Seq[Int]) = {
                 ((functions zip results) map {case ((title, _), r) => title + " " + r} mkString " , ")
        }

        var groupResults = List.fill(functions.length)(1)

        val integrals = for (i <- (1 to 10)) yield {
            val results = functions map (f => (1 to 33).foldLeft(0) ((acc,_) => run(f._2)))
            println (log (results))
                groupResults = (results zip groupResults) map {case (r, gr) => r + gr}
                log(groupResults).toUpperCase
        }

        integrals foreach println

    }                                         //> test: (len: Int)Unit


    test(len)
    test(len * 2)
// GRP 14 , mtbl 11 , else 31 , dfl0 36 , hash 34
// GRP 91 , MTBL 111

    println("Done")
    def main(args: Array[String]) {
    }

produit

grp 5 , mtbl 5 , else 13 , dfl0 17 , hash 17
grp 3 , mtbl 6 , else 14 , dfl0 16 , hash 16
grp 3 , mtbl 6 , else 13 , dfl0 17 , hash 15
grp 4 , mtbl 5 , else 13 , dfl0 15 , hash 16
grp 23 , mtbl 6 , else 14 , dfl0 15 , hash 16
grp 5 , mtbl 5 , else 13 , dfl0 16 , hash 17
grp 4 , mtbl 6 , else 13 , dfl0 16 , hash 16
grp 4 , mtbl 6 , else 13 , dfl0 17 , hash 15
grp 3 , mtbl 5 , else 14 , dfl0 16 , hash 16
grp 3 , mtbl 6 , else 14 , dfl0 16 , hash 16
GRP 5 , MTBL 5 , ELSE 13 , DFL0 17 , HASH 17
GRP 8 , MTBL 11 , ELSE 27 , DFL0 33 , HASH 33
GRP 11 , MTBL 17 , ELSE 40 , DFL0 50 , HASH 48
GRP 15 , MTBL 22 , ELSE 53 , DFL0 65 , HASH 64
GRP 38 , MTBL 28 , ELSE 67 , DFL0 80 , HASH 80
GRP 43 , MTBL 33 , ELSE 80 , DFL0 96 , HASH 97
GRP 47 , MTBL 39 , ELSE 93 , DFL0 112 , HASH 113
GRP 51 , MTBL 45 , ELSE 106 , DFL0 129 , HASH 128
GRP 54 , MTBL 50 , ELSE 120 , DFL0 145 , HASH 144
GRP 57 , MTBL 56 , ELSE 134 , DFL0 161 , HASH 160
grp 7 , mtbl 11 , else 28 , dfl0 31 , hash 31
grp 7 , mtbl 10 , else 28 , dfl0 32 , hash 31
grp 7 , mtbl 11 , else 28 , dfl0 31 , hash 32
grp 7 , mtbl 11 , else 28 , dfl0 31 , hash 33
grp 7 , mtbl 11 , else 28 , dfl0 32 , hash 31
grp 8 , mtbl 11 , else 28 , dfl0 31 , hash 33
grp 8 , mtbl 11 , else 29 , dfl0 38 , hash 35
grp 7 , mtbl 11 , else 28 , dfl0 32 , hash 33
grp 8 , mtbl 11 , else 32 , dfl0 35 , hash 41
grp 7 , mtbl 13 , else 28 , dfl0 33 , hash 35
GRP 7 , MTBL 11 , ELSE 28 , DFL0 31 , HASH 31
GRP 14 , MTBL 21 , ELSE 56 , DFL0 63 , HASH 62
GRP 21 , MTBL 32 , ELSE 84 , DFL0 94 , HASH 94
GRP 28 , MTBL 43 , ELSE 112 , DFL0 125 , HASH 127
GRP 35 , MTBL 54 , ELSE 140 , DFL0 157 , HASH 158
GRP 43 , MTBL 65 , ELSE 168 , DFL0 188 , HASH 191
GRP 51 , MTBL 76 , ELSE 197 , DFL0 226 , HASH 226
GRP 58 , MTBL 87 , ELSE 225 , DFL0 258 , HASH 259
GRP 66 , MTBL 98 , ELSE 257 , DFL0 293 , HASH 300
GRP 73 , MTBL 111 , ELSE 285 , DFL0 326 , HASH 335
Done

Il est curieux que la plus concise groupBysoit plus rapide que la carte même mutable!


3
Je me méfie un peu de cette référence car on ne sait pas quelle est la taille des données. La groupBysolution fonctionne toLowermais les autres non. Aussi pourquoi utiliser une correspondance de modèle pour la carte - utilisez simplement mapValues. Alors roulez ensemble et vous obtenez def woGrouped(w: Word): Map[Char, Int] = w.groupBy(identity).mapValues(_.size)- essayez cela et vérifiez les performances pour différentes listes de tailles. Enfin dans les autres solutions, pourquoi a) déclarermap et b) en faire une var ?? Just dow.foldLeft(Map.empty[Char, Int])...
samthebest

1
Merci d'avoir fourni plus de données (changé mon vote :). Je pense que la raison en est que l'implémentation de groupBy utilise une carte mutable de Builders qui sont optimisées pour les incréments itératifs. Il convertit ensuite la carte mutable en un immuable en utilisant un MapBuilder. Il y a probablement aussi une évaluation paresseuse sous le capot pour accélérer les choses.
samthebest

@samthebest Il vous suffit de rechercher le compteur et de l'incrémenter. Je ne vois pas ce qui peut y être mis en cache. Le cache doit de toute façon être une carte du même type.
Val

Je ne dis pas que ça cache quoi que ce soit. J'imagine que l'augmentation des performances provient de l'utilisation de Builders, et peut-être d'une évaluation paresseuse.
samthebest

@samthebest évaluation paresseuse = évaluation retardée (appel par nom) + mise en cache. Vous ne pouvez pas parler d'évaluation paresseuse mais pas de mise en cache.
Val

4

Je n'ai pas obtenu la taille de la liste en utilisant lengthmais plutôtsize tant que réponse ci-dessus suggérée en raison du problème signalé ici .

val list = List("apple", "oranges", "apple", "banana", "apple", "oranges", "oranges")
list.groupBy(x=>x).map(t => (t._1, t._2.size))

3

Voici une autre option:

scala> val list = List(1,2,4,2,4,7,3,2,4)
list: List[Int] = List(1, 2, 4, 2, 4, 7, 3, 2, 4)

scala> list.groupBy(x => x) map { case (k,v) => k-> v.length }
res74: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 2 -> 3, 7 -> 1, 3 -> 1, 4 -> 3)

3
scala> val list = List(1,2,4,2,4,7,3,2,4)
list: List[Int] = List(1, 2, 4, 2, 4, 7, 3, 2, 4)

scala> println(list.filter(_ == 2).size)
3

3

utiliser des chats

import cats.implicits._

"Alphabet".toLowerCase().map(c => Map(c -> 1)).toList.combineAll
"Alphabet".toLowerCase().map(c => Map(c -> 1)).toList.foldMap(identity)

2
Wow, 4 itérations dans la séquence originale! Même seq.groupBy(identity).mapValues(_.size)ne passe que deux fois.
WeaponsGrade

Nombre d'itérations ne peut pas importer pour une petite chaîne comme « Alphabet », mais en traitant avec des millions d'articles dans une collection, itérations certainement faire affaire!
WeaponsGrade

2

Essayez ceci, cela devrait fonctionner.


val list = List(1,2,4,2,4,7,3,2,4)
list.count(_==2) 

Il reviendra 3


1
En quoi cela diffère-t-il de la réponse de xiefei donnée il y a sept ans?
jwvh le

0

Voici un moyen assez simple de le faire.

val data = List("it", "was", "the", "best", "of", "times", "it", "was", 
                 "the", "worst", "of", "times")
data.foldLeft(Map[String,Int]().withDefaultValue(0)){
  case (acc, letter) =>
    acc + (letter -> (1 + acc(letter)))
}
// => Map(worst -> 1, best -> 1, it -> 2, was -> 2, times -> 2, of -> 2, the -> 2)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.