sortwith one of the POJO values? - android

I have random pojo values, and I want to sort by one of the POJO values, the structure of the values ​​I want to sort is as follows: A-1, A-2, A-3, A-4, A-5, .. .
I've tried to sort it but the value is really in order only between A-1 to A-9, while the value for A-10 is always under A-1.
here's the code I use:
temp.sortWith { lhs, rhs ->
rhs?.blok_kamar?.let {
lhs?.blok_kamar?.compareTo(
it, false
)}!!
}
Please help.

This is how lexicographical ordering works. "1000" is less than "2", despite 1000 being greater than 2. Look at the quotes. It seems like your A-1, A-2, ..., A-10 are Strings. You need to extract the numbers from those identifiers and compare them as Ints:
data class Data(val id: String)
fun main() {
val list = mutableListOf(
Data("A-1"),
Data("A-10"),
Data("A-2"),
Data("A-30"),
Data("A-3")
)
list.sortBy { it.id.substring(it.id.indexOf("-") + 1).toInt() }
println(list)
}
This prints:
[Data(id=A-1), Data(id=A-2), Data(id=A-3), Data(id=A-10), Data(id=A-30)]

Base on your code I do not know what temp actually is, so here is an idea to give you a start, based on a list:
val result = list
.sortedBy {
it.id.substringBefore("-") + "-" + it.id.substringAfter("-").padStart(3, '0')
}

Related

Big problem in Kotlin not cast dynamically like Apple in swift. How can i do?

I have a firebase realtime database
with this simple scheme:
admin
price1: 5
if i get database in kotlin:
val result = it.value as MutableMap<String, Any>
When i try to get price1
var price1 = result["price1"] as Long
price1 = price1 + 1
(PRICE1 can be Double or Int)
the problem is that if price 1 is 5.5 obviously app killed, but if price 1 is 5, works perfectly.
In swift, i put Double every time and it never gives problems
I find it a bit silly to have to check if it is a double or an int without a comma to be able to do the sum
// im doing this at the moment
var price1 = result["price1"].toString()
if (price1.contains(".")){
println(price1.toDouble() + 1)
}else{
println(price1.toInt() + 1)
}
Exist other simple way?
Thanks everyone
Kotlin is very strict about types, which is important for type safety.
In your case, you get a value of type Any out of result. It could be anything, not only an Int or a Double. You know that it can only be an Int or a Double, but the compiler doesn't. Many languages allow implicit stuff like type conversion (int to double), type widening (int to long), etc. But these are often sources of nasty errors. See also this discussion Does anybody find Kotlin’s Type Casting disgusting?
Regarding your code: To test a value for its type you use is.
Here is an example of how you could increment by one:
fun increment(value: Any): Any {
return when (value) {
is Double -> value + 1.0
is Int -> value + 1
else -> throw Exception("Value is neither a Double nor an Int")
}
}
And you would use it like this:
val result: MutableMap<String, Any> = mutableMapOf(
"price1" to 3,
"price2" to 3.45
)
var price1: Any = result["price1"]!! // 3
price1 = increment(price1)
println(price1) // 4
price1 = increment(price1)
println(price1) // 5
var price2: Any = result["price2"]!! // 3.45
price2 = increment(price2)
println(price2) // 4.45
price2 = increment(price2)
println(price2) // 5.45
I don't know if Kotlin will ever have union types. Then a declaration like this would be possible:
val result: MutableMap<String, [Int|Double]> // invalid code
In kotlin all numerable types like Long, Int, Double etc inherit abstract class Number
So your map declaration could be Map<String, Number>.
The Number may be easily converted to Double or any other numerable type and then you can work with it as you do in swift:
val map = hashMapOf<String, Number>(
"1" to 5.5,
"2" to 5
)
var value1 = requireNotNull(map["1"]).toDouble()
val value2 = requireNotNull(map["2"]).toDouble()
value1++
PS: never use serialization to string as a way to check a type, you can use is operator as #lukas.j suggested

Find first occurrence index value in efficent way in kotlin

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

Android Kotlin Map Multiple Array into a list of object

So in this case I have three array that I want to map into a list of object (The objects has three parameters as well).
I have three arrays allProductCodeList, allProductNameList, and allProductQtyList (Content of this array is from a Retrofit Client response)
allProductCodeList = response.body()?.data?.map { it?.stkProdcode }!!
allProductNameList = response.body()?.data?.map { it?.proName }!!
allProductQtyList = response.body()?.data?.map { it?.stkAllqty }!!
//I printed these arrays to LogCat so it is easier to see
Log.i("Order", allProductCodeList.toString())
Log.i("Order", allProductNameList.toString())
Log.i("Order", allProductQtyList.toString())
This is the content of the array I printed into the LogCat:
This is the Data class which I want to parse these arrays into:
data class ProcodeRecommendationListDataClass(
val procode: String?,
val productName: String?,
val qty: Int?
)
What I want to do is parse these three array into a list that will looks like:
[ProcodeRecommendationListDataClass("0100009","", 2),ProcodeRecommendationListDataClass("0100061","", 1),ProcodeRecommendationListDataClasslass("0100062","", 6)]
I've done it when I only have two arrays to map (I use this solution for it). But now it I have three arrays, I confused.
If there's any detail I miss to point out, Just let me know !
1. This is you three arrays
allProductCodeList = response.body()?.data?.map { it?.stkProdcode }!!
allProductNameList = response.body()?.data?.map { it?.proName }!!
allProductQtyList = response.body()?.data?.map { it?.stkAllqty }!!
2. Make A New List
List<ProcodeRecommendationListDataClass> finalList = List()
3. Run a for loop with any of three array size with indices;
for(pos in allProductCodeList.indices){
finalList.add(ProcodeRecommendationListDataClass(allProductCodeList[pos],
allProductNameList[pos],
allProductQtyList[pos] ))
}
Now finalList is your result.
One forward straight way is to use one more zip - someone once said all problems are solved with one more level of inderection:
allProductCodeList
.zip(allProductNameList)
.zip(allProductQtyList)
.map { (codeAndName, qt) ->
ProcodeRecommendationListDataClass(
codeAndName.first,
codeAndName.second,
qt
)
}
It doesn't look super pretty, but it should be ok.
Another way is to create your own zip that takes 2 lists:
fun <X, Y, Z, R> List<X>.zipWith(l1: List<Y>, l2: List<Z>, transform: (X, Y, Z) -> R): List<R> {
val length = min(min(size, l1.size), l2.size)
val result = mutableListOf<R>()
for (i in 0 until length) {
result.add(transform(get(i), l1[i], l2[i]))
}
return result
}
fun main() {
val k = allProductCodeList.zipWith(allProductNameList, allProductQtyList) { code, name, qt ->
ProcodeRecommendationListDataClass(
code,
name,
qt
)
}
println(k)
}
Basically extends a list of X that takes 2 other lists. It iterates through them applying the transform method (this is so you can map the elements as you go).
This will iterate always the smallest amount of elements - in other words, you won't get more elements than the smallest list. I can't be sure, but I assume the default implementation does something similar.
Why not just create objects in place?
val allProducts = response.body()?.data?.map {
ProcodeRecommendationListDataClass(it?.stkProdcode, it?.proName, it?.stkAllqty)
} ?: emptyList()
You can use mapIndexed instead of map. use index to get third data.
val list = allProductCodeList.zip(allProductNameList)
.mapIndexed { index, pair -> SomeClass(pair.first, pair.second,allProductQtyList[index]) }

Create dynamic sorting Comparator builder from API return

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

Combine the latest values of a nested Observable on demand

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

Categories

Resources