Scala Wie kann ich die Anzahl der Vorkommen in einer Liste zählen?


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

Ich möchte es so implementieren: list.count(2)(gibt 3 zurück).


Ich weiß nicht, ob es einen richtigen Weg gibt, um die Größe einer Liste in Scala zu ermitteln, aber für Ihre Situation könnten Sie eine Sequenz verwenden.
Qusay Fantazia

Ist diese Frage noch unbeantwortet? Fragen, weil Sie vielleicht vergessen haben, eine zu akzeptieren.
Tobias Kolb

Antworten:


150

Eine etwas sauberere Version einer der anderen Antworten lautet:

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

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

Geben Sie ein Mapmit einer Zählung für jedes Element in der ursprünglichen Sequenz:

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

In der Frage wird gefragt, wie die Anzahl eines bestimmten Elements ermittelt werden kann. Bei diesem Ansatz würde die Lösung das Zuordnen des gewünschten Elements zu seinem Zählwert wie folgt erfordern:

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

2
Was ist "Identität"?
Igorock

4
Es ist die Identitätsfunktion, wie hier diskutiert . Die Funktion groupByerfordert eine Funktion, die auf Elemente angewendet wird, damit sie diese gruppieren kann. Eine Alternative zum Gruppieren der Zeichenfolgen in der Antwort nach ihrer Identität könnte beispielsweise das Gruppieren nach ihrer Länge ( groupBy(_.size)) oder nach ihrem ersten Buchstaben ( groupBy(_.head)) sein.
Ohruunuruus

2
Der Nachteil ist, dass viele nutzlose Sammlungen (weil nur die Größe benötigt wird) erstellt werden.
Yann Moisan

Was wäre, wenn ich in diesem Ausdruck eine Akkumulator-Map definieren wollte, anstatt eine neue Map zu erstellen?
Tobias Kolb

127

Scala-Sammlungen haben count:list.count(_ == 2)


48

Ich hatte das gleiche Problem wie Sharath Prabhal und bekam eine andere (für mich klarere) Lösung:

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

Mit als Ergebnis:

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

44
Eine etwas sauberere Version ists.groupBy(identity).mapValues(_.size)
ohruunuruus

1
@ohruunuruus dies sollte eine Antwort sein (vs Kommentar); Ich würde gerne begeistert abstimmen, wenn es so wäre (und es als die beste Antwort auswählen, wenn ich das OP wäre);
Doug

1
@ Doug etwas neu in SO und war nicht sicher, aber glücklich zu verpflichten
ohruunuruus

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

gibt

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

Beachten Sie, dass Sie durch (i=>i)integrierte identityFunktion ersetzen können:

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

liebe kurze Lösungen mit eingebauten Bibliotheken
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
aber es ergibt die num. Vorkommen für jeden Wert so oft wie der Wert auftritt - scheint ineffizient und nicht sehr nützlich ...
Erik Kaplun

13

Ab Scala 2.13der groupMapReduce tut Verfahren , dass in einem Durchlauf durch die 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

Dies:

  • groups Listenelemente (Gruppenteil der Gruppe MapReduce)

  • maps jeweils gruppiert Wert Auftreten bis 1 (Teil der Gruppen Karte Map Reduce)

  • reduces Werte innerhalb einer Gruppe von Werten ( _ + _) durch Summieren (reduzieren Sie einen Teil von groupMap Reduce ).

Dies ist eine One-Pass-Version dessen, was übersetzt werden kann durch:

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

Schön, das ist, wonach ich gesucht habe. Ich fand es traurig, dass selbst Java-Streams (die in einigen Aspekten nicht gut sind) dies in einem einzigen Durchgang zulassen, während Scala dies nicht konnte.
Dici

8

Ich hatte das gleiche Problem, wollte aber mehrere Elemente auf einmal zählen.

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


Vielleicht Streamergibt die Verwendung und die akzeptierte Antwort Ihr Ziel "One Go" plus klareren Code.
Juanchito

Diese Lösung wiederholt die Liste nur einmal mit groupBy, und Map führt dies zweimal durch.
Ruloweb

7

Wenn Sie es so verwenden möchten, müssen list.count(2)Sie es mit einer impliziten Klasse implementieren .

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

Kurze Antwort:

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

Lange Antwort:

Mit Scalaz gegeben.

import scalaz._, Scalaz._

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

dann alle diese (in der Reihenfolge von weniger vereinfacht bis mehr vereinfacht)

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))

Ausbeute

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

6

Es ist interessant festzustellen, dass die Karte mit dem Standardwert 0, die absichtlich für diesen Fall entwickelt wurde, die schlechteste Leistung aufweist (und nicht so präzise wie 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]) {
    }

produziert

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

Es ist merkwürdig, dass die meisten prägnanten groupByschneller sind als selbst veränderbare Karten!


3
Ich bin etwas misstrauisch gegenüber diesem Benchmark, da nicht klar ist, wie groß die Daten sind. Die groupByLösung führt eine aus toLower, die anderen jedoch nicht. Warum auch eine Musterübereinstimmung für die Karte verwenden? Verwenden Sie einfach mapValues. Also rollen Sie das zusammen und Sie erhalten def woGrouped(w: Word): Map[Char, Int] = w.groupBy(identity).mapValues(_.size)- versuchen Sie das und überprüfen Sie die Leistung für verschiedene Größenlisten. Schließlich in den anderen Lösungen, warum a) deklarieren mapund b) machen es eine var? Tun w.foldLeft(Map.empty[Char, Int])...
Sie es

1
Vielen Dank für die Bereitstellung weiterer Daten (meine Stimme wurde geändert :). Ich denke, der Grund dafür ist, dass die Implementierung von groupBy eine veränderbare Map von Builders verwendet, die für iterative Inkremente optimiert sind. Anschließend konvertiert es die veränderbare Karte mit a in eine unveränderliche MapBuilder. Es gibt wahrscheinlich auch einige faule Bewertungen unter der Haube, um die Dinge schneller zu machen.
Samthebest

@samthebest Sie suchen einfach den Zähler und erhöhen ihn. Ich sehe nicht, was dort zwischengespeichert werden kann. Der Cache muss sowieso eine Karte der gleichen Art sein.
Val

Ich sage nicht, dass es irgendetwas zwischenspeichert. Ich stelle mir vor, dass die Leistungssteigerung durch die Verwendung von Builders und möglicherweise durch eine verzögerte Bewertung verursacht wird.
Samthebest

@samthebest faule Auswertung = verzögerte Auswertung (Aufruf mit Namen) + Caching. Sie können nicht über verzögerte Auswertung sprechen, aber nicht über Caching.
Val

4

Ich habe die Größe der Liste nicht mit erhalten length, sondern sizeals eine, die die obige Antwort aufgrund des hier gemeldeten Problems vorschlug .

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

3

Hier ist eine weitere 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

mit Katzen

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 Iterationen durch die ursprüngliche Sequenz! Auch seq.groupBy(identity).mapValues(_.size)geht nur zweimal durch.
WeaponsGrade

Die Anzahl der Iterationen spielt für eine kleine Zeichenfolge wie "Alphabet" möglicherweise keine Rolle, aber wenn es um Millionen von Elementen in einer Sammlung geht, spielen Iterationen sicherlich eine Rolle!
WeaponsGrade

2

Versuchen Sie dies, sollte funktionieren.


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

Es wird 3 zurückgeben


Wie unterscheidet sich das von der Antwort von xiefei vor sieben Jahren?
JWVH

0

Hier ist ein ziemlich einfacher Weg, dies zu tun.

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.