Entrambe queste interfacce definiscono un solo metodo
public operator fun iterator(): Iterator<T>
La documentazione dice che Sequence
dovrebbe essere pigro. Ma non è Iterable
anche pigro (a meno che non sia supportato da a Collection
)?
Entrambe queste interfacce definiscono un solo metodo
public operator fun iterator(): Iterator<T>
La documentazione dice che Sequence
dovrebbe essere pigro. Ma non è Iterable
anche pigro (a meno che non sia supportato da a Collection
)?
Risposte:
La differenza fondamentale risiede nella semantica e nell'implementazione delle funzioni di estensione stdlib per Iterable<T>
e Sequence<T>
.
Infatti Sequence<T>
, le funzioni di estensione si eseguono pigramente dove possibile, in modo simile alle operazioni intermedie di Java Streams . Ad esempio, ne Sequence<T>.map { ... }
restituisce un altro Sequence<R>
e non elabora effettivamente gli elementi fino a quando non viene chiamata un'operazione da terminale come toList
o fold
.
Considera questo codice:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Stampa:
before sum 1 2
Sequence<T>
è inteso per un utilizzo pigro e un pipelining efficiente quando si desidera ridurre il più possibile il lavoro svolto nelle operazioni del terminale , come per Java Streams. Tuttavia, la pigrizia introduce un po 'di overhead, che è indesiderabile per trasformazioni semplici comuni di raccolte più piccole e le rende meno performanti.
In generale, non esiste un buon modo per determinare quando è necessario, quindi in Kotlin la pigrizia stdlib viene resa esplicita ed estratta Sequence<T>
nell'interfaccia per evitare di usarla su tutti Iterable
i messaggi di default.
Perché Iterable<T>
, al contrario, le funzioni di estensione con semantica delle operazioni intermedie funzionano con entusiasmo, elaborano immediatamente gli elementi e ne restituiscono un altro Iterable
. Ad esempio, Iterable<T>.map { ... }
restituisce a List<R>
con i risultati della mappatura.
Il codice equivalente per Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Questo stampa:
1 2 before sum
Come detto sopra, Iterable<T>
non è pigro per impostazione predefinita, e questa soluzione si mostra bene: nella maggior parte dei casi ha una buona località di riferimento sfruttando così la cache della CPU, la previsione, il prefetching ecc. In modo che anche la copia multipla di una raccolta funzioni ancora bene abbastanza e funziona meglio in casi semplici con piccole raccolte.
Se è necessario un maggiore controllo sulla pipeline di valutazione, è prevista una conversione esplicita in una sequenza pigra con Iterable<T>.asSequence()
funzione.
map
, filter
e altri non contengono informazioni sufficienti per decidere se non dal tipo di raccolta di origine, e poiché la maggior parte delle raccolte sono anche iterabili, questo non è un buon indicatore per "essere pigri" perché è comunemente OVUNQUE. pigro deve essere esplicito per essere al sicuro.
Completamento della risposta del tasto di scelta rapida:
È importante notare come Sequence e Iterable itera attraverso i tuoi elementi:
Esempio di sequenza:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Risultato registro:
filtro - Mappa - Ciascuno; filtro - Mappa - Ciascuno
Esempio ripetibile:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filtro - filtro - Mappa - Mappa - Ciascuno - Ciascuno
Iterable
è mappatojava.lang.Iterable
all'interfaccia inJVM
ed è implementato da raccolte di uso comune, come List o Set. Le funzioni di estensione della raccolta su queste vengono valutate con entusiasmo, il che significa che tutte elaborano immediatamente tutti gli elementi nel loro input e restituiscono una nuova raccolta contenente il risultato.Ecco un semplice esempio di utilizzo delle funzioni di raccolta per ottenere i nomi delle prime cinque persone in un elenco la cui età è di almeno 21 anni:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Piattaforma di destinazione: JVM in esecuzione su kotlin v. 1.3.61 In primo luogo, il controllo dell'età viene eseguito per ogni singola persona nell'elenco, con il risultato inserito in un elenco nuovo di zecca. Quindi, la mappatura ai loro nomi viene eseguita per ogni Persona che è rimasta dopo l'operatore di filtro, finendo in un altro nuovo elenco (questo ora è un
List<String>
). Infine, c'è un ultimo nuovo elenco creato per contenere i primi cinque elementi dell'elenco precedente.Al contrario, Sequence è un nuovo concetto in Kotlin per rappresentare una raccolta di valori valutata pigramente. Le stesse estensioni di raccolta sono disponibili per l'
Sequence
interfaccia, ma restituiscono immediatamente istanze di Sequence che rappresentano uno stato elaborato della data, ma senza elaborare effettivamente alcun elemento. Per iniziare l'elaborazione, laSequence
deve essere terminata con un operatore terminale, questi sono fondamentalmente una richiesta alla Sequenza di materializzare i dati che rappresenta in qualche forma concreta. Gli esempi includonotoList
,toSet
esum
, per citarne solo alcuni. Quando questi vengono chiamati, verrà elaborato solo il numero minimo richiesto di elementi per produrre il risultato richiesto.Trasformare una raccolta esistente in una sequenza è piuttosto semplice, devi solo usare l'
asSequence
estensione. Come accennato in precedenza, è necessario aggiungere anche un operatore di terminale, altrimenti la sequenza non eseguirà mai alcuna elaborazione (di nuovo, pigro!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Piattaforma di destinazione: JVMRunning su kotlin v. 1.3.61 In questo caso, le istanze di Person nella sequenza vengono controllate per la loro età, se passano, il loro nome viene estratto e quindi aggiunto all'elenco dei risultati. Questo viene ripetuto per ogni persona nell'elenco originale finché non vengono trovate cinque persone. A questo punto, la funzione toList restituisce un elenco e il resto delle persone in
Sequence
non viene elaborato.C'è anche qualcosa in più di cui una sequenza è capace: può contenere un numero infinito di elementi. Con questa prospettiva, ha senso che gli operatori lavorino come fanno: un operatore su una sequenza infinita non potrebbe mai tornare se ha svolto il suo lavoro con entusiasmo.
Ad esempio, ecco una sequenza che genererà tante potenze di 2 quante sono richieste dal suo operatore di terminale (ignorando il fatto che questo traboccherebbe rapidamente):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Puoi trovare di più qui .
Java
(soprattuttoGuava
) i fan