Ricerca…


introduzione

Kotlin fornisce molti metodi di estensione su raccolte e iterabili per l'applicazione di operazioni in stile funzionale. Un tipo di Sequence dedicato consente la composizione lenta di diverse di tali operazioni.

Osservazioni

A proposito di pigrizia

Se si desidera elaborare una catena pigro, è possibile convertire in una Sequence utilizzando asSequence() prima della catena. Alla fine della catena di funzioni, di solito si ottiene anche una Sequence . Quindi puoi usare toList() , toSet() , toMap() o qualche altra funzione per materializzare la Sequence alla fine.

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

Perché non ci sono tipi?!?

Noterai che gli esempi di Kotlin non specificano i tipi. Questo perché Kotlin ha l'inferenza di tipo completo ed è completamente sicuro al momento della compilazione. Più che Java perché ha anche i tipi nullable e può aiutare a prevenire il temuto NPE. Quindi questo in Kotlin:

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

equivale a:

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

Poiché Kotlin sa cosa sono le people e che people.age è Int l'espressione di filtro consente solo il confronto con un Int e that people.name è una String pertanto il passo della map produce un List<String> ( List di String di sola lettura).

Ora, se le people fossero forse null , come in una List<People>? poi:

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

Restituisce un List<String>? che avrebbe bisogno di essere controllato con il nulla ( o usare uno degli altri operatori di Kotlin per valori nullable, vedi questo modo idiomatico di Kotlin per gestire valori nullable e anche il modo Idiomatic di gestire l'elenco nullable o vuoto in Kotlin )

Riutilizzo di flussi

In Kotlin, dipende dal tipo di raccolta se può essere consumata più di una volta. Una Sequence genera un nuovo iteratore ogni volta e, a meno che non asserisca "usa una sola volta", può reimpostarsi all'inizio ogni volta che viene eseguito. Pertanto, mentre il seguente non funziona nello stream di Java 8, ma funziona 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

E in Java per ottenere lo stesso comportamento:

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

Pertanto in Kotlin il fornitore dei dati decide se resettare e fornire un nuovo iteratore oppure no. Ma se vuoi vincolare intenzionalmente una Sequence ad una iterazione una sola volta, puoi usare la funzione constrainOnce() per Sequence come segue:

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. 

Guarda anche:

Accumula nomi in una lista

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

Converti elementi in stringhe e concatenali, separati da virgole

// Java:
String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString() // ", " is used as separator, by default

Calcolo della somma degli stipendi del dipendente

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

Raggruppa dipendenti per dipartimento

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

Calcolo della somma degli stipendi per dipartimento

// 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 }}

Studenti di partizione in passaggio e in mancanza

// 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 }

Nomi di membri maschili

// Java:
List<String> namesOfMaleMembersCollect = 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 }

Nomi di gruppo di membri in roster per genere

// 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 } }   

Filtra un elenco in un altro elenco

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

Trovare una stringa più corta è una lista

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

Diversi tipi di stream n. 2: usare pigramente il primo oggetto se esiste

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

Diversi tipi di stream # 3 - iterate una gamma di numeri interi

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

Diversi tipi di stream # 4 - iterare un array, mappare i valori, calcolare la media

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

Diversi tipi di stream # 5 - lazily itera un elenco di stringhe, mappa i valori, converti in Int, trova max

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

Diversi tipi di stream # 6 - iterare pigramente un flusso di Ints, mappare i valori, stampare i risultati

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

Diversi tipi di flussi n. 7: ripetizione pigramente Doppio, mappa in Int, mappa in stringa, stampa ciascuno

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

È in corso il conteggio degli elementi in un elenco dopo il filtro

// 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') }

Funzionamento dei flussi: filtro, maiuscolo, quindi ordinare un elenco

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

Diversi tipi di stream n. 1: desiderosi di utilizzare il primo oggetto se esiste

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

oppure, creare una funzione di estensione su String chiamata 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)

Vedi anche: funzione apply()

Vedi anche: Funzioni di estensione

Vedi anche: ?. Operatore di chiamate sicure e in generale nullability: http://stackoverflow.com/questions/34498562/in-kotlin-what-is-the-idiomatic-way-to-deal-with-nullable-values-referencing-o/34498563 # 34498563

Raccogli l'esempio n. 5: trova persone con età legale, stringa formattata di output

// 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.

E come nota a margine, in Kotlin possiamo creare semplici classi di dati e creare un'istanza dei dati di test come segue:

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

Raccogli l'esempio n. 6: raggruppa persone per età, età di stampa e nomi insieme

// 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, un caso più interessante qui per Kotlin. Prima le risposte sbagliate per esplorare le varianti della creazione di una Map da una raccolta / sequenza:

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

E ora per la risposta corretta:

// 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!!

Avevamo solo bisogno di unire i valori corrispondenti per comprimere gli elenchi e fornire un trasformatore per joinToString a joinToString per passare joinToString Person a Person.name .

Raccogli l'esempio # 7a - Nomi delle mappe, unisci insieme al delimitatore

// Java (verbose):
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    

// Java (concise)
String names = persons.stream().map(p -> p.name.toUpperCase()).collect(Collectors.joining(" | "));
// Kotlin:
val names = persons.map { it.name.toUpperCase() }.joinToString(" | ")

Raccogli l'esempio # 7b - Raccogli con SummarizingInt

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

Ma è meglio creare una funzione di estensione, 2 in realtà per abbinare gli stili in Kotlin stdlib:

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

Ora hai due modi per usare le nuove funzioni summarizingInt :

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

// or

val stats3 = persons.summarizingInt { it.age }

E tutti questi producono gli stessi risultati. Possiamo anche creare questa estensione per lavorare su Sequence e per i tipi primitivi appropriati.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow