I have two streams where each stream has a different set of values and a different amount:
runBlocking {
val flowA = flow {
mutableListOf<Int>(0, 4, 9).forEach {
emit(it)
}
}
val flowB = flow {
mutableListOf<Int>(1, 2, 3, 5, 6, 7, 8).forEach {
emit(it)
}
}
merge(flowA, flowB).collect{
Log.i(TAG, it.toString())
}
}
Is it possible to use Kotlin's Flow to merge these two streams so that the result is sorted? So the collected values should end up being:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
The values in each stream is already sorted. I just need to merge them. One very important thing however. I don't want to sort AFTER all the values have been collected. The sorting must be done as each value is emitted. My sample above is over simplified. In the real app, the source for each flow contains large arrays for each item. Waiting for all the values to be collected and then sorting is unacceptable as this would require a large amount of memory. But the basic concept for simple integer values should work for more complex data types as well.
Maybe the filter operator is what I need but that isn't clear as I have little experience with flows.
Disclaimer: This is the first time I've used Flow.
Even though the streams are "already sorted", it seems you cannot control the timing the elements will arrive from the two streams. So, you will only be able to get an ordered list by collecting all the elements, then sorting them.
This worked for me:
val sortedResults = flowA
.onCompletion { emitAll(flowB) }
.toCollection(mutableListOf())
.sorted()
println(sortedResults)
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
You could use the combine operator to get value from two or more flows and then a flatMapLatest operator like this:
val desiredFlow = combine(getFlowA(),getFlowB()) { a, b ->
val arr = IntArray(a.size + b.size)
var i = 0
var j = 0
var k = 0
while (i < a.size && j < b.size)
arr[k++] = if (a[i] < b[j]) a[i++] else b[j++]
while (i < a.size)
arr[k++] = a[i++]
while (j < b.size)
arr[k++] = b[j++]
arr
}.flatMapLatest { result ->
flow {
emit(result.toMutableList())
}
}
fun getFlowA(): Flow<MutableList<Int>> {
return flow {
emit(mutableListOf<Int>(0,4,9))
}
}
fun getFlowB(): Flow<MutableList<Int>> {
return flow {
emit(mutableListOf(1,2,3,4,5,6,7,8))
}
}
I'm from the Android dev world and not expert with Flows so kindly pardon me if isn't what you expected, but this produces the final output as:
[0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9]
After going through the question, I have two ideas either using a flatten merge or using a delay.
The first idea looks something like this.
runBlocking {
val flowA = flow {
mutableListOf<Int>(0, 4, 9).forEach {
emit(it)
}
}
val flowB = flow {
mutableListOf<Int>(1, 2, 3, 5, 6, 7, 8).forEach {
emit(it)
}
}
val newList = mutableListOf<Int>()
val a = flowOf(flowA, flowB).flattenMerge().collect { value ->
when {
newList.isEmpty() -> newList.add(value)
newList.last() <= value -> newList.add(value)
newList.last() > value -> {
//sorting values as they arrive
val i = newList.lastIndex
newList.add(value)
val newValue = newList[i]
newList[i] = newList[i + 1]
newList[i + 1] = newValue
}
}
}
Log.i(TAG, newList.toString())
}
In the second one, add appropriate delays to your first 2 flows.
PS:-
Android Studio gives a warning while using flattenMerge.
This declaration is in a preview state and can be changed in a backwards-incompatible manner with a best-effort migration. Its usage should be marked with '#kotlinx.coroutines.FlowPreview' or '#OptIn(kotlinx.coroutines.FlowPreview::class)' if you accept the drawback of relying on preview API
I don't think you can do this using the built-in flow operators, but you should certainly be able to implement your own. You can use channelFlow for that purpose. This is a versatile way to build a flow that gives us a coroutine scope to work in, and lets us emit items by sending to a channel.
fun <T> mergeOrdered(flowA: Flow<T>, flowB: Flow<T>) = channelFlow {
val channelA = flowA.produceIn(this)
val channelB = flowB.produceIn(this)
var a = channelA.receive()
var b = channelB.receive()
while (isActive) {
if (a < b) {
send(a)
a = channelA.receive()
} else {
send(b)
b = channelB.receive()
}
}
}
This simple example doesn't handle what happens when flowA and flowB run out of elements, but that should be easy enough to add.
Related
I want to create a list using Kotlin that contains items of another list, based on endDate equals to startDate and .. etc
Example:
listOf(
{id1, startDate=1, endDate=3},
{id3, startDate=5, endDate=6},
{id2, startDate=3, endDate=5},
{id4, startDate=10, endDate=12},
{id5, startDate=12, endDate=13},
{id6, startDate=13, endDate=16})
result listOf[{id1}, {id2}, {id3}], [{id4}, {id5}, {id6}] // these are two items
With the given dataset, this problem looks innocent at a first glance, but may grow to a more complex problem quickly. Imagine a dataset that has the potential of multiple, possible results. Should longest possible chains be preferred, or a result with balanced chain size?
A naive implementation may be like this (written inside a Kotest).
data class ListItem(
val id: String,
val startDate: Int,
val endDate: Int
)
given("another StackOverflow issue") {
val coll = listOf(
ListItem("id1", startDate = 1, endDate = 3),
ListItem("id3", startDate = 5, endDate = 6),
ListItem("id2", startDate = 3, endDate = 5),
ListItem("id4", startDate = 10, endDate = 12),
ListItem("id5", startDate = 12, endDate = 13),
ListItem("id6", startDate = 13, endDate = 16)
)
`when`("linking chain") {
/** final result ends up here */
val chains: MutableList<MutableList<ListItem>> = mutableListOf()
/** populate dequeue with items ordered by startDate */
val arrayDeque = ArrayDeque(coll.sortedBy { it.startDate })
/** loop is iterated at least once, hence do/while */
do {
/** add a new chain */
chains.add(mutableListOf())
/** get first element for chain */
var currentItem: ListItem = arrayDeque.removeFirst()
/** add first element to current chain */
chains.last().add(currentItem)
/** add items to current chain until chain is broken */
while (arrayDeque.any { it.startDate == currentItem.endDate }) {
/** get next element to add to chain and remove it from dequeue */
currentItem = arrayDeque
.first { it.startDate == currentItem.endDate }
.also { arrayDeque.remove(it) }
chains.last().add(currentItem)
}
} while (arrayDeque.any())
then("result should be as expected") {
chains.size shouldBe 2
chains.first().size shouldBe 3
chains.last().size shouldBe 3
chains.flatMap { it.map { innerItem -> innerItem.id } } shouldBe listOf(
"id1",
"id2",
"id3",
"id4",
"id5",
"id6",
)
}
}
}
I'm trying to refactor the following function to make it more Kotlin-idiomatic using modern functional language features:
fun foobar(): List<Account> {
var pageOffset = 0
val accounts: MutableList<Account> = ArrayList()
var chunk: List<Account> = accountsService.getAccounts(pageOffset, MAX_POLL_SIZE)
while (chunk.isNotEmpty()) {
accounts.addAll(chunk)
pageOffset += MAX_POLL_SIZE
chunk = accountsService.getAccounts(pageOffset, MAX_POLL_SIZE)
}
return accounts
}
My first attempt was to replace the mutable list with buildList, but it's still not quite functional style:
fun foobar2(): List<Account> {
var pageOffset = 0
return buildList {
var chunk: List<Account> = accountsService.getAccounts(pageOffset, MAX_POLL_SIZE)
while (chunk.isNotEmpty()) {
addAll(chunk)
pageOffset += MAX_POLL_SIZE
chunk = accountsService.getAccounts(pageOffset, MAX_POLL_SIZE)
}
}
}
Ideally, I would like to replace the whole while loop with something like accountsService.getAccounts(...).map { ... } but I can't figure out how to refactor a while loop that has this kind of "first chunk" followed by a number of further chunks. Can it be done?
You can do something like this:
fun foobar(): List<Account> =
generateSequence(0) { it + MAX_POLL_SIZE }.map { offset ->
accountsService.getAccounts(offset, MAX_POLL_SIZE)
}.takeWhile { it.isNotEmpty() }.flatten().toList()
generateSequence generates an infinite, lazy sequence starting with 0, MAX_POLL_SIZE, MAX_POLL_SIZE * 2, MAX_POLL_SIZE * 3, and so on. This is the sequence of page offsets for which you are getting accounts. We transform each page offset to the list of accounts it corresponds to, using map. After that, we specify an end to the infinite sequence using takeWhile.
Now we have a Sequence<List<Account>>, so we use flatten converts that into a Sequence<Account>, which can then be trivially converted to a List<Account>,
Only for context: I am trying to implement Playfair Cipher. It would be really helpful if you take a look at Playfair Cipher to understand my problem.
This program is just for some background:
fun main(){
println("Enter the message:")
var message:String = readLine()!!.toUpperCase()
println("Enter the key:")
var key:String = readLine()!!.toUpperCase()
var cipTable = Array(5){ Array(5){'X'}}
var j=0; //to iterate througm my key
for(innerArray in cipTable){
for(i in innerArray.indices){
if(key[j++] !in cipTable)
innerArray[i]+=key[j]
if(j==key.length) break
}
}
}
My main issue is with this part:
for(innerArray in cipTable){
for(i in innerArray.indices){
if(key[j++] !in cipTable)
I wanted to check if the key that I am going to insert in the matrix is already present in it or not. I also cannot use innerArray instead of cipTable as it would only check for char in the same row. Is there any way I can check if a char is present or not in the entire matrix?
For eg.:
fun main(){
var result = arrayOf(
intArrayOf(3, 2, 4),
intArrayOf(6, 7, 9),
intArrayOf(12, 11, 23)
)
//To check if 2 is present in the entire matrix/table
if(result.any { 2 !in it}) println("not present") else print("present")
}
Can you tell me what is wrong in this code because the output is not expected. Also is there any way I can use forEach for the same.
If I understand correctly, you want this for loop to result in a true or false based on whether any inner array has the same sequence and number of chars as the key String.
First of all, the inner array should be a CharArray instead of an Array<Char>, to avoid boxing.
val cipTable = Array(5) { CharArray(5) { 'X' } }
Then you can use all and contentEquals to check if any of the inner CharArrays are a match for the key.
val charArrayKey = key.toCharArray()
val isKeyInTable = cipTable.any { it.contentEquals(charArrayKey) }
If you want to skip the step of converting the key to a CharArray, you can manually check it like this:
val isKeyInTable =
cipTable.any { it.size == key.length && it.withIndex().all { (i, c) -> c == key[i] } }
I guess one way is to use extensions.
fun Array<IntArray>.has(x:Int):Boolean{
for(innerArray in this){
if(x in innerArray)
return true
}
return false
}
fun main(){
var result = arrayOf(
intArrayOf(3, 2, 4),
intArrayOf(6, 7, 9),
intArrayOf(12, 11, 23)
)
//To check if 43 or 4 is present in the entire matrix/table
if(result.has(43)) println("present") else println("not present")
if(result.has(4)) println("present") else println("not present")
}
I need to find only the numbers where the next number is the same: [1,2,2,3,4,4] should produce [2,4]. Since I need to peek at the next number, I figured I'd try out using a Peekable iterator and write a filter.
fn main() {
let xs = [1, 2, 2, 3, 4, 4];
let mut iter = xs.iter().peekable();
let pairs = iter.filter(move |num| {
match iter.peek() {
Some(next) => num == next,
None => false,
}
});
for num in pairs {
println!("{}", num);
}
}
I get an error:
error[E0382]: capture of moved value: `iter`
--> src/main.rs:6:15
|
5 | let pairs = iter.filter(move |num| {
| ---- value moved here
6 | match iter.peek() {
| ^^^^ value captured here after move
|
= note: move occurs because `iter` has type `std::iter::Peekable<std::slice::Iter<'_, i32>>`, which does not implement the `Copy` trait
I think this is because iter is being used by the closure, but it hasn't borrowed it, and it can't copy it.
How do I solve this problem of wanting to refer to the iterator inside a filter?
refer to the iterator inside a filter
I don't believe you can. When you call filter, it takes ownership of the base iterator:
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
P: FnMut(&Self::Item) -> bool,
Once you do that, it's gone. There is no more iter. In some similar cases, you can use Iterator::by_ref to mutably borrow the iterator, drive it for a while, then refer back to the original. That won't work in this case because the inner iterator would need to borrow it mutably a second time, which is disallowed.
find only the numbers where the next number is the same.
extern crate itertools;
use itertools::Itertools;
fn main() {
let input = [1, 2, 2, 3, 4, 4];
let pairs = input
.iter()
.tuple_windows()
.filter_map(|(a, b)| if a == b { Some(a) } else { None });
let result: Vec<_> = pairs.cloned().collect();
assert_eq!(result, [2, 4]);
}
Or if you wanted something using only the standard library:
fn main() {
let xs = [1, 2, 2, 3, 4, 4];
let mut prev = None;
let pairs = xs.iter().filter_map(move |curr| {
let next = if prev == Some(curr) { Some(curr) } else { None };
prev = Some(curr);
next
});
let result: Vec<_> = pairs.cloned().collect();
assert_eq!(result, [2, 4]);
}
Previously I was using this code:
private val mItems = ArrayList<Int>()
(1..item_count).mapTo(mItems) { it }
/*
mItems will be: "1, 2, 3, 4, 5, ..., item_count"
*/
Now, I am using a class instead of Int, but the class has Int member with name id.
class ModelClass(var id: Int = 0, var status: String = "smth")
So how can I use this method to fill the ArrayList in similar way?
//?
private val mItems = ArrayList<ModelClass>()
(1..item_count).mapTo(mItems) { mItems[position].id = it } // Something like this
//?
From the mapTo documentation:
Applies the given transform function to each element of the original collection and appends the results to the given destination.
Therefore, you just need to return the elements you want:
(1..item_count).mapTo(mItems) { ModelClass(it) }
If you are OK with any MutableList (which is often ArrayList or similar):
val mItems1 = MutableList(item_count) { i -> i }
val mItems2 = MutableList(item_count) { ModelClass(it) }