I have some custom fields that each have a value that can be retrieved using a BehaviorSubject<Value>. What fields are shown are based on what I get from the API, so in the end I have n amount of BehaviorSubject<Value>s. I would like to group these values together into an Observable<List<Value>> where the list contains latest values from these fields (order is irrelevant). The problem however is that these fields are not all available at the same time because they're created while the UI loads so I cannot use Observable.combineLatest with the list of subjects.
What I currently have done is I have created the following variable:
private val values = BehaviorSubject.create<Pair<Int, Value>>()
I use this subject to subscribe to all of the field's subjects but map the subjects first with their position and make a pair of it.
fieldSubject.map {
Pair(position, value)
}.subscribe(values)
What I then want to do is group the values based on their position in the pair and get an Observable<List<Value>> where the list contains the latest values from each position. However I don't know how to proceed after grouping them using groupBy:
values.groupBy {
it.first
}
This results in an Observable<GroupedObservable<Pair, Value>>>. Finally this is how I think I should get to Observable<List<Value>>, but I don't know what to do from here.
groupBy doesn't seem helpful to me here.
Accumulating values is generally done using scan, here you can use a transformation like:
values.scanWith({ arrayOfNulls<Value>(N) }) { acc, (index, value) ->
acc.copyOf().apply {
set(index, value)
}
}
.map { it.filterNotNull() }
You could probably get away without copyOf(), but I think I encountered problems in the past when the accumulator function wasn't pure.
By the way, you can write position to value instead of Pair(position, value).
Also, you can use merge to get an Observable<Pair<Int, Value>> instead of creating a BehaviorSubject and subscribing it manually to all fields:
Observable.mergeArray(
fieldX.map { 0 to it },
fieldY.map { 1 to it },
fieldZ.map { 2 to it }
// ...
)
So overall, you could have a function that does it all for you:
inline fun <reified T : Any> accumulateLatest(vararg sources: Observable<out T>): Observable<List<T>> {
return Observable.merge(sources.mapIndexed { index, observable ->
observable.map { index to it }
})
.scanWith({ arrayOfNulls<T>(sources.size) }) { acc, (index, value) ->
acc.copyOf().apply {
set(index, value)
}
}
.map { it.filterNotNull() }
}
And then just call:
accumulateLatest(fieldX, fieldY, fieldZ)
.subscribe {
println("Latest list: $it")
}
Related
Hey i have nested list and i wanted find first occurrence index value.
data class ABC(
val key: Int,
val value: MutableList<XYZ?>
)
data class XYZ)
val isRead: Boolean? = null,
val id: String? = null
)
I added code which find XYZ object, but i need to find index. So how can i achieved in efficient way. How can i improve my code?
list?.flatMap { list ->
list.value
}?.firstOrNull { it?.isRead == false }
If you would like to stick to functional style then you can do it like this:
val result = list.asSequence()
.flatMapIndexed { outer, abc ->
abc.value.asSequence()
.mapIndexed { inner, xyz -> Triple(outer, inner, xyz) }
}
.find { it.third?.isRead == false }
if (result != null) {
val (outer, inner) = result
println("Outer: $outer, inner: $inner")
}
For each ABC item we remember its index as outer and we map/transform a list of its XYZ items into a list of tuples: (outer, inner, xyz). Then flatMap merges all such lists (we have one list per ABC item) into a single, flat list of (outer, inner, xyz).
In other words, the whole flatMapIndexed() block changes this (pseudo-code):
[ABC([xyz1, xyz2]), ABC([xyz3, xyz4, xyz5])]
Into this:
[
(0, 0, xyz1),
(0, 1, xyz2),
(1, 0, xyz3),
(1, 1, xyz4),
(1, 2, xyz5),
]
Then we use find() to search for a specific xyz item and we acquire outer and inner attached to it.
asSequence() in both places changes the way how it works internally. Sequences are lazy, meaning that they perform calculations only on demand and they try to work on a single item before going to another one. Without asSequence() we would first create a full list of all xyz items as in the example above. Then, if xyz2 would be the one we searched, that would mean we wasted time on processing xyz3, xyz4 and xyz5, because we are not interested in them.
With asSequence() we never really create this flat list, but rather perform all operations per-item. find() asks for next item to check, mapIndexed maps only a single item, flatMapIndexed also maps only this single item and if find() succeed, the rest of items are not processed.
In most cases using sequences here could greatly improve the performance. In some cases, like for example when lists are small, sequences may degrade the performance by adding an overhead. However, the difference is very small, so it is better to leave it as it is.
As we can see, functional style may be pretty complicated in cases like this. It may be a better idea to use imperative style and good old loops:
list.indicesOfFirstXyzOrNull { it?.isRead == false }
inline fun Iterable<ABC>.indicesOfFirstXyzOrNull(predicate: (XYZ?) -> Boolean): Pair<Int, Int>? {
forEachIndexed { outer, abc ->
abc.value.forEachIndexed { inner, xyz ->
if (predicate(xyz)) {
return outer to inner
}
}
}
return null
}
In Kotlin, you can use the indexOf() function that returns the index of the first occurrence of the given element, or -1 if the array does not contain the element.
Example:
fun findIndex(arr: Array<Int>, item: Int): Int {
return arr.indexOf(item)
}
Trying to sort the custom components on defined order, and other subjects should be after that desired order: Like if there is any other subject except the defined List ie "GK" then it should be on last position etc.
However I am getting Null Pointer Exception due to subject is not defined in the requireList if scheduleCommandList have the subject which is not in requiredList. How can I overcome this?
Desired Order List is Below:
private val requiredList: HashMap<String, Int> = hashMapOf(
"Maths" to 0,
"Physics" to 1,
"Science" to 2,
)
Sorting function to sort the List:
private fun sortCommandList(scheduleCommandList: ArrayList<BaseComponent>): ArrayList<BaseComponent> {
val comparator = Comparator { o1: BaseComponent, o2: BaseComponent ->
return#Comparator requiredList[o1.name]!! - requiredList[o2.name]!!
}
val copy = arrayListOf<BaseComponent>().apply { addAll(scheduleCommandList) }
copy.sortWith(comparator)
return copy
}
It seems you understand the problem correctly. If an item is not present in requiredList then you still try to compare their required positions and this causes NullPointerException. Remember that you should use !! only in cases when you are sure there can't be a null. In this case null is possible and we have to handle it somehow. The easiest is to replace it with Int.MAX_VALUE which places the item at the end. Also, this code can be really much simpler:
private fun sortCommandList(scheduleCommandList: List<BaseComponent>): List<BaseComponent> {
return scheduleCommandList.sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
It can be even better to create this utility as extension function:
private fun List<BaseComponent>.mySort(): List<BaseComponent> {
return sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
Then we can simplify the name of the function, because it is implicit that it is used to sort BaseComponent objects.
I have a problem that I really cannot solve.. Maybe You may help me. I need to sort an object list from API return that contains filters. The problem is those filters are dynamic, The object Order (the problematic filter) :
class Order(val field : String, val direction: String)
The field is an object attribute (column), the direction can be ASC or DESC.
The JSON can return more than one filter, so this can be :
order : {
field : "id",
direction : "ASC"
},
{
field : "creationDate"
direction : "DESC"
}
The problem is, I don't know how to create a dynamic function that can create a perfect sort in my list. I know I've got to do this :
return list.sortedWith(compareBy(List::id).thenByDescending(List::creationDate))
But Dynamically.. wow
KT
You can create a map from a property name to the comparator that compares orders by that property:
val comparators = mapOf<String, Comparator<Order>>(
"field" to compareBy { it.field },
"direction" to compareBy { it.direction }
)
Then you can pick comparators from that map by the given property names, change their sorting order with Comparator.reversed() extension function, and finally combine all these comparators into the single resulting comparator with Comparator.then(Comparator) function:
val givenOrder = listOf("field" to "ASC", "direction" to "DESC")
val resultingOrder = givenOrder
.map { (fieldName, direction) ->
comparators[fieldName]!!.let { if (direction == "DESC") it.reversed() else it }
}
.reduce { order, nextComparator -> order.then(nextComparator) }
val sortedList = list.sortedWith(resultingOrder)
I am guessing that the second ordering oly applies to those where the first is the same value
sortedWith + compareBy
compareBy takes a vararg of selectors which is just a array, so it can be constructed
val selectors: Array<(T) -> Comparable<*>?> = orders.map { TODO() }.toArray()
list.sortedWith(compareBy(*selectors))
i am thinking some extra function go go though all possible fields you could sort and uses either it.field or -(it.field) to create the selectors
also see this answer: Sort collection by multiple fields in Kotlin
I have a pice of code in Swift that reduces a list of TVSchedule objects into an array of TVMatch pobjects. Each TVSchedule, has a property called events, that is a list of TVMatches.
The code in swift is the following:
var matches: [TVMatch] {
let slots = timeSlots.reduce(into: [TVMatch]()) { (result, schedule) in
result.append(contentsOf: schedule.events)
}
return slots
}
I'm trying to do the same reduce in Kotlin and the code I have is the following:
val matches: ArrayList<TVMatch>
get() {
val slots = timeSlots.fold(arrayListOf<TVMatch>()) { result, schedule ->
result.addAll(schedule.events)
}
return slots
}
However, the Kotlin code gives me a type error, and does not compile. What is the problem here?
addAll returns a boolean, but the return value of the fold-operation should be of same type as the given initial object (in this case ArrayList).
You can solve that one easily by just adding result after your addAll-statement, e.g.:
result.addAll(schedule.events)
result // this is now the actual return value of the fold-operation
Alternatively just use apply or similar instead:
result.apply {
addAll(schedule.events)
} // result is the return value then
Note that you can actually simplify altogether using flatMap to just (side-note: if you use this approach the matches are evaluated only once of course, but flatMap is the star here anyway ;-))):
val matches = timeSlots.flatMap { it.events } // this is a new list! (note, if you do several mappings in a row, you may want to use timeSlots.asSequence().flatMap { }.map { }.toList() / or .toMutableList() instead
Alternatively if you really require the matches to be of type ArrayList, use flatMapTo instead:
val matches = timeSlots.flatMapTo(ArrayList()) { it.events }
You can of course keep the get() if you must, or just move the getting of the matches to its own function, e.g.:
fun getMatches() = timeSlots.flatMapTo(ArrayList()) { it.events }
Am I crazy, or can't you just replace the code with
val matches: List<TVMatch>
get() = timeSlots.flatMap { schedule -> schedule.events }
?
RxJava Kotlin flatmap don't return separated objects from splitted string. instead it returns List
val source: Observable<String> = Observable.just("521934/2342/FOXTROT")
.flatMap{Observable.fromArray(it.split("/"))}
.subscribe{Log.d(TAG, "$it")}
It returns list:
[521934, 2342, FOXTROT]
But book (Thomas Nield : Learning RxJava / 2017 / Page 114) says it has to return separated strings
521934
2342
FOXTROT
example from book
http://reactivex.io/documentation/operators/flatmap.html says that it returns Single object. In my case I got Single List object. So, documentation says true. But I want to get result as in book example!
How I can split the list and get separated objects?
Make use of flatMapIterable, so you can get a stream of the items from the list:
Observable.just("521934/2342/FOXTROT")
.flatMap { input -> Observable.fromArray(input.split("/")) }
.flatMapIterable { items -> items }
.subscribe { item -> Log.d(TAG, item) }
Just use fromIterable:
Observable.just("521934/2342/FOXTROT")
.flatMap { Observable.fromIterable(it.split("/")) }
.subscribe{
Log.d(TAG, "$it")
}
In case of array you would have to use a spread operator additionaly since fromArray takes a vararg argument list:
Observable.fromArray(*arrayOf("521934","2342","FOXTROT"))