Welche Java 8 Stream.collect-Entsprechungen sind in der Standard-Kotlin-Bibliothek verfügbar?

180

In Java 8 gibt Stream.collectes Aggregationen für Sammlungen. In Kotlin existiert dies nicht auf die gleiche Weise, außer vielleicht als Sammlung von Erweiterungsfunktionen in der stdlib. Es ist jedoch nicht klar, welche Äquivalenzen für verschiedene Anwendungsfälle gelten.

Am oberen Rand des JavaDoc fürCollectors befinden sich beispielsweise Beispiele für Java 8, und wenn Sie sie nach Kolin portieren , können Sie die Java 8-Klassen nicht verwenden, wenn Sie eine andere JDK-Version verwenden. Daher sollten sie wahrscheinlich anders geschrieben werden.

In Bezug auf Online-Ressourcen, die Beispiele für Kotlin-Sammlungen zeigen, sind sie in der Regel trivial und nicht wirklich mit denselben Anwendungsfällen vergleichbar. Was sind gute Beispiele, die wirklich zu den für Java 8 dokumentierten Fällen passen Stream.collect? Die Liste dort ist:

  • Sammeln Sie Namen in einer Liste
  • Sammeln Sie Namen in einem TreeSet
  • Konvertieren Sie Elemente in Zeichenfolgen und verketten Sie sie durch Kommas getrennt
  • Berechnen Sie die Summe der Gehälter des Mitarbeiters
  • Mitarbeiter der Gruppe nach Abteilungen
  • Berechnen Sie die Summe der Gehälter nach Abteilung
  • Teilen Sie die Schüler in Bestehen und Nichtbestehen auf

Mit Details im oben verlinkten JavaDoc.

Hinweis: Diese Frage wurde absichtlich vom Autor geschrieben und beantwortet ( Selbst beantwortete Fragen ), sodass die idiomatischen Antworten auf häufig gestellte Kotlin-Themen in SO vorhanden sind. Auch um einige wirklich alte Antworten zu klären, die für Alphas von Kotlin geschrieben wurden und für das heutige Kotlin nicht korrekt sind.

Jayson Minard
quelle
In Fällen, in denen Sie keine andere Wahl haben, als zu verwenden collect(Collectors.toList())oder ähnliches, kann dieses Problem auftreten: stackoverflow.com/a/35722167/3679676 (das Problem mit Problemumgehungen)
Jayson Minard

Antworten:

255

In der Kotlin-Standardliste gibt es Funktionen für Durchschnitt, Anzahl, Unterscheidung, Filtern, Finden, Gruppieren, Verbinden, Zuordnen, Min, Max, Partitionieren, Schneiden, Sortieren, Summieren, zu / von Arrays, zu / von Listen, zu / von Karten , Vereinigung, Ko-Iteration, alle funktionalen Paradigmen und mehr. Sie können diese also verwenden, um kleine 1-Liner zu erstellen, und Sie müssen nicht die kompliziertere Syntax von Java 8 verwenden.

Ich denke, das einzige, was in der integrierten Java 8- CollectorsKlasse fehlt, ist die Zusammenfassung (aber in einer anderen Antwort auf diese Frage ist eine einfache Lösung) .

Eine Sache, die in beiden fehlt, ist das Stapeln nach Anzahl, was in einer anderen Antwort zum Stapelüberlauf zu sehen ist und auch eine einfache Antwort enthält. Ein weiterer interessanter Fall ist dieser ebenfalls aus Stack Overflow: Idiomatischer Weg, um mit Kotlin eine Sequenz in drei Listen zu verschütten . Wenn Sie etwas Stream.collectfür einen anderen Zweck erstellen möchten, lesen Sie Benutzerdefinierte Stream.collect in Kotlin

BEARBEITEN 11.08.2017: In kotlin 1.2 M2 wurden Sammelvorgänge mit Chunked / Window hinzugefügt, siehe https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/


Es ist immer gut, die API-Referenz für kotlin.collections als Ganzes zu durchsuchen, bevor Sie neue Funktionen erstellen, die dort möglicherweise bereits vorhanden sind.

Hier sind einige Konvertierungen von Java 8- Stream.collectBeispielen in das Äquivalent in Kotlin:

Sammeln Sie Namen in einer Liste

// Java:  
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Kotlin:
val list = people.map { it.name }  // toList() not needed

Konvertieren Sie Elemente in Zeichenfolgen und verketten Sie sie durch Kommas getrennt

// Java:
String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString(", ")

Berechnen Sie die Summe der Gehälter des Mitarbeiters

// Java:
int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val total = employees.sumBy { it.salary }

Mitarbeiter der Gruppe nach Abteilungen

// Java:
Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin:
val byDept = employees.groupBy { it.department }

Berechnen Sie die Summe der Gehälter nach Abteilung

// Java:
Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                     Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

Teilen Sie die Schüler in Bestehen und Nichtbestehen auf

// Java:
Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
// Kotlin:
val passingFailing = students.partition { it.grade >= PASS_THRESHOLD }

Namen männlicher Mitglieder

// Java:
List<String> namesOfMaleMembers = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());
// Kotlin:
val namesOfMaleMembers = roster.filter { it.gender == Person.Sex.MALE }.map { it.name }

Gruppennamen der Mitglieder im Kader nach Geschlecht

// Java:
Map<Person.Sex, List<String>> namesByGender =
      roster.stream().collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.mapping(
                Person::getName,
                Collectors.toList())));
// Kotlin:
val namesByGender = roster.groupBy { it.gender }.mapValues { it.value.map { it.name } }   

Filtern Sie eine Liste in eine andere Liste

// Java:
List<String> filtered = items.stream()
    .filter( item -> item.startsWith("o") )
    .collect(Collectors.toList());
// Kotlin:
val filtered = items.filter { it.startsWith('o') } 

Suche nach der kürzesten Zeichenfolge einer Liste

// Java:
String shortest = items.stream()
    .min(Comparator.comparing(item -> item.length()))
    .get();
// Kotlin:
val shortest = items.minBy { it.length }

Zählen von Elementen in einer Liste, nachdem der Filter angewendet wurde

// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();
// Kotlin:
val count = items.filter { it.startsWith('t') }.size
// but better to not filter, but count with a predicate
val count = items.count { it.startsWith('t') }

und weiter geht's ... In allen Fällen war keine spezielle Falz-, Reduzierungs- oder andere Funktionalität erforderlich, um nachzuahmen Stream.collect. Wenn Sie weitere Anwendungsfälle haben, fügen Sie diese in die Kommentare ein und wir können sehen!

Über Faulheit

Wenn Sie eine Kette verzögert verarbeiten möchten, können Sie Sequencesie asSequence()vor der Kette in eine Verwendung konvertieren . Am Ende der Funktionskette erhalten Sie normalerweise Sequenceauch ein. Dann können Sie verwenden toList(), toSet(), toMap()oder eine andere Funktion die materialisieren Sequenceam Ende.

// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()

Warum gibt es keine Typen?!?

Sie werden feststellen, dass in den Kotlin-Beispielen die Typen nicht angegeben sind. Dies liegt daran, dass Kotlin über eine vollständige Typinferenz verfügt und zur Kompilierungszeit vollständig typsicher ist. Mehr als Java, da es auch nullfähige Typen hat und dabei helfen kann, die gefürchtete NPE zu verhindern. Also das in Kotlin:

val someList = people.filter { it.age <= 30 }.map { it.name }

ist das gleiche wie:

val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }

Weil Kotlin weiß, was peopleist, und das people.ageist Intdaher der Filterausdruck nur einen Vergleich mit einem Int, und das people.nameist ein, Stringdaher maperzeugt der Schritt ein List<String>(schreibgeschützt Listvon String).

Nun, wenn peoplees möglich wäre null, wie List<People>?damals:

val someList = people?.filter { it.age <= 30 }?.map { it.name }

Gibt eine zurück List<String>?, die auf Null geprüft werden müsste ( oder verwenden Sie einen der anderen Kotlin-Operatoren für nullfähige Werte. Siehe diese Kotlin-idiomatische Methode zum Umgang mit nullbaren Werten und auch die idiomatische Methode zum Behandeln von nullbaren oder leeren Listen in Kotlin ).

Siehe auch:

Jayson Minard
quelle
Gibt es ein Äquivalent zu Java8s parallelStream () in Kotlin?
Arnab
Die Antwort über unveränderliche Sammlungen und Kotlin ist die gleiche Antwort für @arnab hier für parallele, andere Bibliotheken existieren, verwenden Sie sie: stackoverflow.com/a/34476880/3679676
Jayson Minard
2
@arnab Vielleicht möchten Sie sich die Kotlin-Unterstützung für Java 7/8-Funktionen (insbesondere kotlinx-support-jdk8) ansehen, die Anfang dieses Jahres zur Verfügung gestellt wurde: Diskussion.kotlinlang.org/t/jdk7-8-features-in -kotlin-1-0 / 1625
Roborative
Ist es wirklich idiomatisch, 3 verschiedene "it" -Referenzen in einer Anweisung zu verwenden?
Hermann
2
Es ist eine Präferenz, in den obigen Beispielen habe ich sie kurz gehalten und nur bei Bedarf einen lokalen Namen für einen Parameter angegeben.
Jayson Minard
46

Weitere Beispiele finden Sie in allen Beispielen aus dem in Kotlin konvertierten Java 8 Stream Tutorial . Der Titel jedes Beispiels leitet sich aus dem Quellartikel ab:

Wie Streams funktionieren

// Java:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList.stream()
      .filter(s -> s.startsWith("c"))
      .map(String::toUpperCase)
     .sorted()
     .forEach(System.out::println);

// C1
// C2
// Kotlin:
val list = listOf("a1", "a2", "b1", "c2", "c1")
list.filter { it.startsWith('c') }.map (String::toUpperCase).sorted()
        .forEach (::println)

Verschiedene Arten von Streams # 1

// Java:
Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

Oder erstellen Sie eine Erweiterungsfunktion für den String ifPresent:

// Kotlin:
inline fun String?.ifPresent(thenDo: (String)->Unit) = this?.apply { thenDo(this) }

// now use the new extension function:
listOf("a1", "a2", "a3").firstOrNull().ifPresent(::println)

Siehe auch: apply()Funktion

Siehe auch: Erweiterungsfunktionen

Siehe auch: ?.Safe Call-Operator und allgemeine Nullbarkeit: Was ist in Kotlin die idiomatische Methode, um mit nullbaren Werten umzugehen, sie zu referenzieren oder zu konvertieren ?

Verschiedene Arten von Streams # 2

// Java:
Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

Verschiedene Arten von Streams # 3

// Java:
IntStream.range(1, 4).forEach(System.out::println);
// Kotlin:  (inclusive range)
(1..3).forEach(::println)

Verschiedene Arten von Streams # 4

// Java:
Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println); // 5.0    
// Kotlin:
arrayOf(1,2,3).map { 2 * it + 1}.average().apply(::println)

Verschiedene Arten von Streams # 5

// Java:
Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3
// Kotlin:
sequenceOf("a1", "a2", "a3")
    .map { it.substring(1) }
    .map(String::toInt)
    .max().apply(::println)

Verschiedene Arten von Streams # 6

// Java:
IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3    
// Kotlin:  (inclusive range)
(1..3).map { "a$it" }.forEach(::println)

Verschiedene Arten von Streams # 7

// Java:
Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3
// Kotlin:
sequenceOf(1.0, 2.0, 3.0).map(Double::toInt).map { "a$it" }.forEach(::println)

Warum bestellen Angelegenheiten

Dieser Abschnitt des Java 8 Stream-Lernprogramms ist für Kotlin und Java identisch.

Streams wiederverwenden

In Kotlin hängt es von der Art der Sammlung ab, ob sie mehrmals konsumiert werden kann. A Sequencegeneriert jedes Mal einen neuen Iterator, und wenn es nicht "nur einmal verwenden" behauptet, kann es jedes Mal, wenn es bearbeitet wird, auf den Start zurückgesetzt werden. Daher schlägt im Java 8-Stream zwar Folgendes fehl, funktioniert aber in Kotlin:

// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception
// Kotlin:  
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }

stream.forEach(::println) // b1, b2

println("Any B ${stream.any { it.startsWith('b') }}") // Any B true
println("Any C ${stream.any { it.startsWith('c') }}") // Any C false

stream.forEach(::println) // b1, b2

Und in Java, um das gleiche Verhalten zu erhalten:

// Java:
Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
          .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

Daher entscheidet der Anbieter der Daten in Kotlin, ob er zurückgesetzt und ein neuer Iterator bereitgestellt werden kann oder nicht. Wenn Sie Sequencejedoch eine Iteration absichtlich auf eine einmalige Iteration beschränken möchten , können Sie die constrainOnce()Funktion Sequencewie folgt verwenden:

val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
        .constrainOnce()

stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once. 

Erweiterte Funktionen

Sammle Beispiel 5 (ja, ich habe die bereits in der anderen Antwort übersprungen)

// Java:
String phrase = persons
        .stream()
        .filter(p -> p.age >= 18)
        .map(p -> p.name)
        .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

    System.out.println(phrase);
    // In Germany Max and Peter and Pamela are of legal age.    
// Kotlin:
val phrase = persons.filter { it.age >= 18 }.map { it.name }
        .joinToString(" and ", "In Germany ", " are of legal age.")

println(phrase)
// In Germany Max and Peter and Pamela are of legal age.

Nebenbei bemerkt können wir in Kotlin einfache Datenklassen erstellen und die Testdaten wie folgt instanziieren:

// Kotlin:
// data class has equals, hashcode, toString, and copy methods automagically
data class Person(val name: String, val age: Int) 

val persons = listOf(Person("Tod", 5), Person("Max", 33), 
                     Person("Frank", 13), Person("Peter", 80),
                     Person("Pamela", 18))

Sammeln Sie Beispiel 6

// Java:
Map<Integer, String> map = persons
        .stream()
        .collect(Collectors.toMap(
                p -> p.age,
                p -> p.name,
                (name1, name2) -> name1 + ";" + name2));

System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}    

Ok, ein interessanter Fall hier für Kotlin. Zuerst die falschen Antworten, um Variationen beim Erstellen eines Mapaus einer Sammlung / Sequenz zu untersuchen:

// Kotlin:
val map1 = persons.map { it.age to it.name }.toMap()
println(map1)
// output: {18=Max, 23=Pamela, 12=David} 
// Result: duplicates overridden, no exception similar to Java 8

val map2 = persons.toMap({ it.age }, { it.name })
println(map2)
// output: {18=Max, 23=Pamela, 12=David} 
// Result: same as above, more verbose, duplicates overridden

val map3 = persons.toMapBy { it.age }
println(map3)
// output: {18=Person(name=Max, age=18), 23=Person(name=Pamela, age=23), 12=Person(name=David, age=12)}
// Result: duplicates overridden again

val map4 = persons.groupBy { it.age }
println(map4)
// output: {18=[Person(name=Max, age=18)], 23=[Person(name=Peter, age=23), Person(name=Pamela, age=23)], 12=[Person(name=David, age=12)]}
// Result: closer, but now have a Map<Int, List<Person>> instead of Map<Int, String>

val map5 = persons.groupBy { it.age }.mapValues { it.value.map { it.name } }
println(map5)
// output: {18=[Max], 23=[Peter, Pamela], 12=[David]}
// Result: closer, but now have a Map<Int, List<String>> instead of Map<Int, String>

Und jetzt zur richtigen Antwort:

// Kotlin:
val map6 = persons.groupBy { it.age }.mapValues { it.value.joinToString(";") { it.name } }

println(map6)
// output: {18=Max, 23=Peter;Pamela, 12=David}
// Result: YAY!!

Wir mussten nur die übereinstimmenden Werte verbinden, um die Listen zu reduzieren und einen Transformator bereitzustellen jointToString, um von der PersonInstanz zur zu wechseln Person.name.

Sammeln Sie Beispiel 7

Ok, dies kann leicht ohne einen Brauch gemacht werden Collector, also lasst es uns auf Kotlin-Weise lösen und dann ein neues Beispiel erfinden, das zeigt, wie ein ähnlicher Prozess durchgeführt wird, für Collector.summarizingIntden es in Kotlin nicht nativ gibt.

// Java:
Collector<Person, StringJoiner, String> personNameCollector =
Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
        .stream()
        .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID    
// Kotlin:
val names = persons.map { it.name.toUpperCase() }.joinToString(" | ")

Es ist nicht meine Schuld, dass sie ein triviales Beispiel ausgewählt haben !!! Ok, hier ist eine neue summarizingIntMethode für Kotlin und ein passendes Beispiel:

SummarizingInt Beispiel

// Java:
IntSummaryStatistics ageSummary =
    persons.stream()
           .collect(Collectors.summarizingInt(p -> p.age));

System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}    
// Kotlin:

// something to hold the stats...
data class SummaryStatisticsInt(var count: Int = 0,  
                                var sum: Int = 0, 
                                var min: Int = Int.MAX_VALUE, 
                                var max: Int = Int.MIN_VALUE, 
                                var avg: Double = 0.0) {
    fun accumulate(newInt: Int): SummaryStatisticsInt {
        count++
        sum += newInt
        min = min.coerceAtMost(newInt)
        max = max.coerceAtLeast(newInt)
        avg = sum.toDouble() / count
        return this
    }
}

// Now manually doing a fold, since Stream.collect is really just a fold
val stats = persons.fold(SummaryStatisticsInt()) { stats, person -> stats.accumulate(person.age) }

println(stats)
// output: SummaryStatisticsInt(count=4, sum=76, min=12, max=23, avg=19.0)

Es ist jedoch besser, eine Erweiterungsfunktion zu erstellen, 2 die tatsächlich zu den Stilen in Kotlin stdlib passt:

// Kotlin:
inline fun Collection<Int>.summarizingInt(): SummaryStatisticsInt
        = this.fold(SummaryStatisticsInt()) { stats, num -> stats.accumulate(num) }

inline fun <T: Any> Collection<T>.summarizingInt(transform: (T)->Int): SummaryStatisticsInt =
        this.fold(SummaryStatisticsInt()) { stats, item -> stats.accumulate(transform(item)) }

Jetzt haben Sie zwei Möglichkeiten, die neuen summarizingIntFunktionen zu verwenden:

val stats2 = persons.map { it.age }.summarizingInt()

// or

val stats3 = persons.summarizingInt { it.age }

Und all dies führt zu den gleichen Ergebnissen. Wir können diese Erweiterung auch erstellen, um an Sequenceund für geeignete primitive Typen zu arbeiten.

Vergleichen Sie zum Spaß den Java JDK-Code mit dem benutzerdefinierten Kotlin-Code , der zum Implementieren dieser Zusammenfassung erforderlich ist.

Jayson Minard
quelle
In Stream 5 gibt es kein Plus, zwei Karten anstelle von einer zu verwenden .map { it.substring(1).toInt() }: Wie Sie wissen, handelt es sich bei dem abgeleiteten Typ um einen Kotlin-Typ.
Michele d'Amico
stimmt, aber es gibt auch keinen Nachteil (aus Gründen der Vergleichbarkeit habe ich sie getrennt gehalten)
Jayson Minard
Der Java-Code kann jedoch problemlos parallel geschaltet werden. In vielen Fällen ist es daher besser, den Java-Stream-Code von Kotlin aus aufzurufen.
Howard Lovatt
@HowardLovatt Es gibt viele Fälle, in denen Parallelität nicht der richtige Weg ist, insbesondere in Umgebungen mit starker gleichzeitiger Nutzung, in denen Sie sich bereits in einem Thread-Pool befinden. Ich wette, der durchschnittliche Anwendungsfall ist NICHT parallel und es ist der seltene Fall. Aber natürlich haben Sie immer die Möglichkeit, Java-Klassen nach Belieben zu verwenden, und nichts davon war wirklich der Zweck dieser Frage und Antwort.
Jayson Minard
3

In einigen Fällen ist es schwierig, Anrufe collect(Collectors.toList())oder ähnliches zu vermeiden . In diesen Fällen können Sie mithilfe von Erweiterungsfunktionen wie den folgenden schneller zu einem Kotlin-Äquivalent wechseln:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())
fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()

Dann kannst du einfach stream.toList() oder stream.asSequence()zurück in die Kotlin-API wechseln. Ein Fall wie z. B. Files.list(path)zwingt Sie zu einem Fall , in dem StreamSie ihn möglicherweise nicht möchten, und diese Erweiterungen können Ihnen dabei helfen, wieder in die Standardsammlungen und die Kotlin-API zurückzukehren.

Jayson Minard
quelle
2

Mehr zur Faulheit

Nehmen wir die von Jayson gegebene Beispiellösung für "Berechnung der Summe der Gehälter nach Abteilungen":

val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

Um dies faul zu machen (dh zu vermeiden, dass im groupBySchritt eine Zwischenkarte erstellt wird ), ist die Verwendung nicht möglich asSequence(). Stattdessen müssen wir verwenden groupingByund bedienen fold:

val totalByDept = employees.groupingBy { it.dept }.fold(0) { acc, e -> acc + e.salary }

Für manche Menschen ist dies möglicherweise sogar besser lesbar, da es sich nicht um Karteneinträge handelt: die it.value Teil der Lösung war zunächst auch für mich verwirrend.

Da dies ein häufiger Fall ist und wir es vorziehen, nicht foldjedes Mal aufzuschreiben, ist es möglicherweise besser, nur eine generische sumByFunktion für Folgendes bereitzustellen Grouping:

public inline fun <T, K> Grouping<T, K>.sumBy(
        selector: (T) -> Int
): Map<K, Int> = 
        fold(0) { acc, element -> acc + selector(element) }

damit wir einfach schreiben können:

val totalByDept = employees.groupingBy { it.dept }.sumBy { it.salary }
ihr Mann
quelle