Why do you recommend learning Sequence before you learn Flow?

What is Sequence?

What is the difference between Sequence and Collection?

When to use Sequence and when to use Collection.

Learn Flow. Why do you recommend learning Sequence first?

Because flows are more powerful than sequences, they support richer operations.

The flip side of this statement is that Sequence is simpler and easier to learn than Flow.

A Flow is a Sequence that supports asynchronous operations.

The difficulty of learning skyrocketed with the addition of asynchronous operations.

If you have trouble learning Flow, or are confused, learn Sequence first.

—- Sequence is also a container

Sequence and Collection are both containers for Kotlin.

Like lists, queues, and sets of collections, sequences can manage multiple data dynamically.

In Kotlin, Sequence and Collection have many of the same operation functions, such as map,filter, etc.

So it’s only natural that sequences can also be manipulated by iterators.

3. Data processing Sequence: Collection vs Sequence

Note: It is important to understand the data processing order, which is the essence of Sequence.

Note: It is important to understand the data processing order, which is the essence of Sequence.

1. Collection Processes data in batches
val wordList = listOf("The"."quick"."brown"."fox"."jumps"."over"."the"."lazy"."dog")
val lengthsList = wordList
    .filter { it.length > 3 }
    .map { it.length }
    .take(4)
println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)
Copy the code

In the code above, the order in which words are processed in wordList is shown:

As shown, each operation of a collection returns a new collection, and the next operation is performed on the new collection.

  1. Filter the collection wordList to form a new collectionCollection1=={“quick”,”brown”, “jumps”, “over”, “lazy”}
  2. Then theCollection1Perform the map operation, and a new collection is generatedCollection25,5,5,4,4 = = {}
  3. thenCollection2Perform the take operation and return the new collectionCollection35,5,5,4 = = {}

To get Collection3, two intermediate collections Collection1 and Collection2 are generated. As you can imagine, the more operations you do, the more memory you waste.

2. Stream data
val wordsSequence = listOf("The"."quick"."brown"."fox"."jumps"."over"."the"."lazy"."dog")
    .asSequence()// Convert the list to a sequence

val lengthsSequence = wordsSequence
    .filter { it.length > 3 }
    .map { it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars")
println(lengthsSequence.toList())// End operations: get the result as a list.
Copy the code

In the code above, the order in which words are processed in wordList is shown:

The elements in the figure, Sequence, are processed one by one.

Element 1 performs all operations, then element 2 performs all operations… (Of course, an element, which may be filtered out in one operation, will not be processed in later operations) returns the result.

  1. The first element in the wordListTheAfter the filter operation, it is filtered out, so the following operations will not be carried out.
  2. Second elementquickFirst, toquickperformfilterOperation, filter passes, and thenquickThe map operation is performed to generate the elements5And then to5Add to the list by performing the take operation.
  3. And so on, the third element, the fourth element… When take(4) has collected all four elements, the following elements are no longer iterated

Sequence operates like an IO stream. When we read a file on disk, the data is read in waves to InputStream, rather than being returned to InputStream after all data is loaded.

3. Advantages of streaming operation

More flexible, efficient, low memory consumption.

Can handle large amounts of data.

3.1. Deal with large amounts of data more efficiently

If our database has 1 million words, we need to perform the filter, map, and take operations in the demo above.

Collection:

val wordList = queryAMillionWrods()// Query 1,000,000 pieces of data
val lengthsList = wordList
    .filter { ... }
    .map { ... }
    .take(4)...Copy the code

To use Collection, you must first read all 1 million pieces of data and store them into the Collection wordList, and then each operation generates a temporary Collection.

It’s a waste to think about, guys

And the performance is also unimaginable, we just need to take four words that meet the conditions.

But we walked through a million pieces of data.

Sequence

val wordsSequence = sequence {
  for(i in 1.10000.) {val wordList = queryAThousandWrods(i)// query 10000 times, 100 words each time
    yieldAll(wordList)// Send out 100 pieces of data}}val lengthsSequence = wordsSequence
    .filter { ... }
    .map { ... }
    .take(4)

println("Lengths of first 4 words longer than 3 chars")
println(lengthsSequence.toList())
Copy the code

Nice, boys. Isn’t that nice.

    1. The whole process produces only one list, lengthssequence.tolist ()
  1. When all four words match, no more traversal is performed. The for loop is automatically broken.

    So, most likely, the loop ends once.

    If four of the 100 words found in the first query already meet the criteria, the second loop will not be triggered.

Just ask if you are wonderful, bobbin ~

3.2. Easily implement data subscription

This is the well-known skill of StateFlow.

We’ll talk about that separately later.

4. Timing of data traversal: Collection vs Sequence

Let’s take a look at the following code:

Set the Collection:

listOf("The"."quick"."brown").map { 
  println("map $it")
  it
}
Copy the code

Sequence of the Sequence:

listOf("The"."quick"."brown").asSequence().map { 
  println("map $it")
  it
}
Copy the code

What do these two pieces of code print?

What do these two pieces of code print?

What do these two pieces of code print?

The answer is:

The first piece of code, the map prints each element.

The second code Sequence does not print any logs.

Why is that?

Each operation of a Collection is executed almost immediately and returns a new Collection.
Sequence operations are divided into intermediate operations and terminal operations.

If the return value is Sequence, the operation is an intermediate operation. These operations do not trigger data transmission and traversal.

For example, the following are intermediate operations:

map(transform: (T) -> R): Sequence<R>
filter(predicate: (T) -> Boolean): Sequence<T>
take(n: Int): Sequence<T>
/ / etc.
Copy the code

Otherwise this operation is the end operation. Only terminal operations on Sequence trigger transmitting and traversal of data.

We also call this data stream cold flow.

For example, the following are terminal operations:

forEach(action: (T) -> Unit) :Unit
first(): T
count(): Int
fold(initial: R, operation: (acc: R, T) -> R): R
/ / etc.
Copy the code

5. Create Sequence

1. sequenceOf

val numbersSequence = sequenceOf("four"."three"."two"."one")
Copy the code

2. Iterable. AsSequence Converts any set to Sequence

val numbers = listOf("one"."two"."three"."four")
val numbersSequence = numbers.asSequence()
Copy the code

3. Construct the Sequence using the generateSequence function

Create a sequence by generateSequence.

Infinite sequences:

fun createSequence1(a){
  val firstElement = 3
  //class kotlin.sequences.ConstrainedOnceSequence
  val sequence = generateSequence(firstElement) {
    it + 2
  }
  val result = sequence.take(5).toList()
  println("result=$result")//result=[3, 5, 7, 9, 11]
  //println("sequence2 count=${sequence.count()}")// causes an infinite loop because the sequence is infinite
}
Copy the code

Finite size sequence:

All elements are considered complete when generateSequence encounters the first null element. Null does not act as element.

fun createSequence2(a){
  val firstElement = 3
  val sequence = generateSequence(firstElement) {
    if (it < 10) {
      it + 2
    }else if(it < 20) {null
    }else {
      it + 20}}val result = sequence.toList()
  println("result=$result")//result=[3, 5, 7, 9, 11]
  println("count=${sequence.count()}")//count=5
}
Copy the code

4. Construct the sequence by using the sequence+yield function

fun createSequence3(a) {
//class kotlin.sequences.SequencesKt__SequenceBuilderKt$sequence$$inlined$Sequence$1
  val seq = sequence {// This is a coroutine body
    println("sequence.begin")
    println("yield 1")
    yield(1)
    println("yield 2")
    yield(2)
    println("YieldAll three, four, five." ")
    yieldAll(listOf(3.4.5))
    println("yieldAll 6, 7, 8")
    yieldAll(sequenceOf(6.7.8))
    println("yieldAll 9, 10, 11")
    yieldAll(generateSequence(9) {
      if (it <= 11) {
        it + 1
      } else {
        null
      }
    })
    println("sequence.end")}val result = seq.toList()
  println("result=$result")//result=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  val count = seq.count()
  println("count=$count")//count=12
}
Copy the code
The sequence function
public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>. () - >Unit): Sequence<T> 


@RestrictsSuspension
public abstract class SequenceScope<in T> internal constructor() {
  public abstract suspend fun yield(value: T)
  public abstract suspend fun yieldAll(iterator: Iterator<T>)
  public suspend fun yieldAll(elements: 可迭代<T>)
  public suspend fun yieldAll(sequence: Sequence<T>)
}
Copy the code
4.1 Sequence The sequence function is used to create sequence objects.

The block argument to the sequence function is a coroutine body. And is an anonymous extension function of SequenceScope.

4.2 Yield and yieldAll functions are used to transmit data to the stream.

The sequencescope.yield (value) function is a member of SequenceScope.

Yield is a suspend function, so it must be executed in a coroutine. And the Scope of the coroutine must be of type SequenceScope.

The following code will report an error:

Error message:

Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine Scope ---- Yeild () function calls can only be limited to the specified Coroutine scope. The SequenceScope. How does Kotlin achieve this constraint? Because SequenceScope's annotation RestrictsSuspension.Copy the code
4.3 Important Summary:
  1. The block argument to the sequence is a coroutine body and is an extension of the SequenceScope.
  2. Sequencescope.yield (value) is a suspend function that can only be executed in the coroutine body of SequenceScope.

The difference between Sequece and Flow

Sequece does not switch threads, nor does it switch coroutines.

That is, the operations on the Sequence chain are all executed in the same thread.

Flow: Officially called Asynchronous Flow. Asynchrony is the most important difference between flows and sequences.

Asynchronous flow may seem more complicated than the normal multi-coroutine, multi-threaded concept because it also has the concept of parallel flow.

In the future, when we talk about Flow, we’ll talk more about parallel flows.

The following Flow operations do not exist for Sequence operations:
1, a Flow. FlowOn (context: CoroutineContext)
2, Flow. LaunchIn (scope: CoroutineScope): Job
3, flow. buffer(capacity: Int = BUFFERED)
flatMapMerge()
collect()
conflate()
combine()
catch(a)/ / etc.
Copy the code
Uncle selected articles:
  • MVI Architecture pattern? Who the hell is doing it? Official Framework Guide Upgrade
  • Componentized blossom, ask you sweet not sweet
  • How Kotlin can Make Java Development Happier
  • How to optimize the android process name function?
  • Breaking your mind, does Java divide by zero necessarily crash?




I’ve heard that programmers who like likes are more beautiful and love learning