I have two functions (GetPodsOne and GetPodsTwo) that return me a big csv string. I then do some processing to discard the part of the string I don't want. See snippet below.
var podValues = execGetPodsOne()
val testPodValuesLst: List<String> = podValues.split(",").map { it -> it.substringAfterLast("/") }
testPodValuesLst.forEach { it ->
println("value from testPodList=$it")
}
podValues = execGetPodsTwo()
val sitPodValuesLst: List<String> = podValues.split(",").map { it -> it.substringAfterLast("/") }
sitPodValuesLst.forEach { it ->
println("value from sitPodList=$it")
}
This leaves me with two lists. See output of the above below:
value from testPodList=api-car-v1:0.0.118
value from testPodList=api-dog-v1:0.0.11
value from testPodList=api-plane-v1:0.0.36
value from sitPodList=api-car-v1:0.0.119
value from sitPodList=api-dog-v1:0.0.12
value from sitPodList=api-plane-v1:0.0.37
What i would like to do is end up with the objects inside a data class like below:
data class ImageVersions(val apiName: String, val testPodVersion: String, val sitPodVersion: String)
api-car-v1, 0.0.118, 0.0.119
api-dog-v1, 0.0.11, 0.0.12
api-plane-v1, 0.0.36, 0.0.37
I've used test and sit above but I'm going to have maybe another 5 environments eventually. Looking for a nice way to get the versions for each api and easily combine into that ImageVersions data class.
thanks
Considering that you're going to have maybe another 5 environments eventually, I tried to write something that will scale well:
enum class Env { Test, Sit }
data class ImageVersions(val apiName: String, val versions: Map<Env, String?>)
fun String.getNameAndVersion() = substringBefore(':') to substringAfter(':')
fun getVersions(envMap: Map<Env, List<String>>): List<ImageVersions> {
val envApiNameMap = envMap.mapValues { it.value.associate(String::getNameAndVersion) }
val allApiNames = envApiNameMap.flatMap { it.value.keys }.distinct()
return allApiNames.map { apiName ->
ImageVersions(apiName, envApiNameMap.mapValues { it.value[apiName] })
}
}
Playground example
So instead of separate val testPodVersion: String, val sitPodVersion: String, here you have a map. Now the structure of ImageVersions always remains the same irrespective of how many environments you have.
getNameAndVersion is a helper function to extract apiName and version from the original string.
getVersions accepts a list of versions corresponding to each environment and returns a list of ImageVersions
envApiNameMap is same as envMap just that the list is now a map of apiName and its version.
allApiNames contains all the available apiNames from all environments.
Then for every apiName, we take all the versions of that apiName from all the environments.
In future, if your have another environment, just add it in the Env enum and pass an extra map entry in the envMap of getVersions. You need not modify this function every time you have a new environment.
How about this:
val testPodValuesMap = testPodValuesLst.associate { it.split(':').zipWithNext().single() }
val sitPodValuesMap = sitPodValuesLst.associate { it.split(':').zipWithNext().single() }
val mergedMap = (testPodValuesMap.keys + sitPodValuesMap.keys).associateWith { key ->
testPodValuesMap.getValue(key) to sitPodValuesMap.getValue(key)
}
val imageVersions = mergedMap.map { (k, v) -> ImageVersions(k, v.first, v.second) }
println(imageVersions.joinToString("\n"))
which prints
ImageVersions(apiName=api-car-v1, testPodVersion=0.0.118, sitPodVersion=0.0.119)
ImageVersions(apiName=api-dog-v1, testPodVersion=0.0.11, sitPodVersion=0.0.12)
ImageVersions(apiName=api-plane-v1, testPodVersion=0.0.36, sitPodVersion=0.0.37)
As a first step I would extract the apiNames from both lists:
val apiNames = list1.map { it.replace("value from ", "").split("[=:]".toRegex())[1] }
.plus(list2.map { it.replace("value from ", "").split("[=:]".toRegex())[1] })
.distinct()
Then I'd create the ImageVersions instances by looping over apiNames:
val result = apiNames
.map { apiName ->
ImageVersions(
apiName,
(list1.firstOrNull { it.contains(apiName) } ?: "").split(":")[1],
(list2.firstOrNull { it.contains(apiName) } ?: "").split(":")[1]
)
}
.toList()
The reason to first extract the apiNames is, that apiNames missing in one of the two lists will still end up in the final result.
Kotlin Playground
Related
I receive data from Request information as list data (List) below code. That data has a "key" parameter by which I want to sort it.
data class ApplianceSetting(
#SerializedName("key") val key: String,
#SerializedName("value") var value: Any,
(...)
I have the required order in the SettingsUtilEnum and want to sort items by that.
After that, I can convert the list using map{} the data and use the function of Enum getSettingByMode() and get the list of Enum values. Then I will sort them and convert them again to List.
But that sounds too inefficient. Is there a better way.
enum class SettingsUtilEnum(
var settingKey: String,
override val order: Int = 99,
var settingName: String = "",
) : AbstractOrderEnum {
FIRST_MODE("first.mode", 0),
SECOND_MODE("second.mode", 1),
(...)
UNKNOWN_MODE("", 99);
companion object {
#JvmStatic
fun getSettingByMode(settingKey: String): SettingsUtilEnum? {
return values().find { it.settingKey == settingKey }
}
k
private fun initDataObserver() {
(activity as FavouriteActivity).viewModel.applianceSettings.observe(activity as FavouriteActivity
) { data ->
(controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
val adapter = (controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
// public final var data: List<ApplianceSetting>
// old code:
// data.settings
adapter.data = sortAndGetControlModes(data)
adapter.notifyDataSetChanged()
}
}
// TODO: sortAndGetControlModes
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.map {
getSettingByMode(it.key)
?: UNKNOWN_MODE.apply {
// If in future new modes are added -> put them as tail
settingKey = it.key
}
}.sortedBy { it.order }
// error i need to return again List<ApplianceSetting>
If you want to compare keys with theirs ASCII values you can just use sortBy { it.key }
If you want to expand possibilities of comparison you can use function sortedWith with passing custom comparator as argument.
Comparator used to compare its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
Example:
You can use it like that if you want to sort by integer value of key parameter:
data.settings.sortedWith { a, b ->
when {
a.key.toInt() < b.key.toInt() -> -1
a.key.toInt() > b.key.toInt() -> 1
else -> 0
}
}
I fixed it using sortedBy and as comparator I am using received value (order) from getSettingByMode(), if item is not found (null) I give him order value of 99 and put it on tail position:
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.sortedBy {
getSettingByMode(it.key)?.order ?:99
}
I'm confused about the different behaviour depending whether I use getters or delegated properties. Consider the following:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH GETTERS:
val partA get() = parts.a
val partB get() = parts.b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
This code splits the string raw into two parts the first time parts is accessed. All later calls to parts will return the cached parts, even if raw changes. Output:
#1
#2
#2
a=one, b=two
The value of raw is empty when Test is created, but the accessors aren't called until we've set raw to some string. When partA and partB are finally accessed, they contain the correct value.
If I use property delegation instead, the code no longer works:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH DELEGATION:
val partA by parts::a
val partB by parts::b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
All I've changed here is that partA is now delegated to parts::a, and the same for partB. For some strange reason, partA and partB are now accessed before the value of raw is set, so cachedParts is initilized with two empty parts. Output:
#2
#2
#1
a=, b=
Can someone explain what is going on here?
See what your delegated properties translate to in the documentation here. For example, partA translates to:
private val partADelegate = parts::a
val partA: String
get() = partADelegate.getValue(this, this::partA)
Notice that the callable reference expression part::a is used to initialise partADelegate. This expression is evaluated when the instance of Test is created, before println("#1").
To evaluate parts::a, parts must be first evaluated. After all, this is a reference to the a property of parts, not a reference to parts.
Therefore, parts ends up being evaluated before raw gets its value.
I have class A:
class A (private var z: String, private var y: String, private var x: Int)
I want to create a failsafe builder for it. The builder should return Either the list of Exceptions (e.g. when values are missing) or the created values. What is the recommended way to create something like this? Or is there a conceptually better approach?
My own approach to it:
sealed class ABuilderException {
object MissingXValue : ABuilderException()
object MissingYValue : ABuilderException()
object MissingZValue : ABuilderException()
}
import arrow.core.Either
import arrow.core.Option
import arrow.core.none
import arrow.core.some
class ABuilder {
private var x : Option<Int> = none()
private var y : Option<String> = none()
private var z : Option<String> = none()
fun withX(x : Int) : ABuilder {
this.x = x.some();
return this;
}
fun withY(y : String) : ABuilder {
this.y = y.some();
return this;
}
fun withZ(z : String) : ABuilder {
this.z = z.some();
return this;
}
fun build() : Either<A, List<ABuilderException>> {
var xEither = x.toEither { ABuilderException.MissingXValue }
var yEither = y.toEither { ABuilderException.MissingYValue }
var zEither = z.toEither { ABuilderException.MissingZValue }
// If all values are not an exception, create A
// otherwise: Return the list of exceptions
}
}
How could I best complete the build code?
I favor a solution that avoids deep nesting (e.g. orElse or similar methods) and avoids repeating values (e.g. by recreating Tuples), because this may lead to typos and makes it harder to add/remove properties later.
First you need to change the signature of build to:
fun build() : Either<List<ABuilderException>, A>
The reason for doing that is because Either is right biased - functions like map, flatMap etc operate on the Right value and are no-op in case the value is Left.
For combining Either values you can use zip:
val e1 = 2.right()
val e2 = 3.right()
// By default it gives you a `Pair` of the two
val c1 = e1.zip(e2) // Either.Right((2, 3))
// Or you can pass a custom combine function
val c2 = e1.zip(e2) { two, three -> two + three } // Either.Right(5)
However there is an issue here, in case of an error (one of them is Left) it will fail fast and give you only the first one.
To accumulate the errors we can use Validated:
val x = none<Int>()
val y = none<String>()
val z = none<String>()
// Validated<String, Int>
val xa = Validated.fromOption(x) { "X is missing" }
// Validated<String, String>
val ya = Validated.fromOption(y) { "Y is missing" }
// Validated<String, String>
val za = Validated.fromOption(z) { "Z is missing" }
xa.toValidatedNel().zip(
ya.toValidatedNel(),
za.toValidatedNel()
) { x, y, z -> TODO() }
Validated, like Either has a zip function for combining values. The difference is that Validated will accumulate the errors. In the lambda you have access to the valid values (Int, String, String) and you can create your valid object.
toValidatedNel() here converts from Validated<String, String> to Validated<Nel<String>, String> where Nel is a list that can NOT be empty. Accumulating errors as a List is common so it's built in.
For more you can check the Error Handling tutorial in the docs.
I have a List<Map<Branch,Pair<String, Any>>> that I would like to convert in a single Map<Branch,List<Pair<String, Any>>> .
So if I have an initial list with simply 2 elements :
List
1. branch1 -> Pair(key1,value1)
branch2 -> Pair(key2,value2)
2. branch1 -> Pair(key1a,value1a)
I want to end up with :
Map
branch1 -> Pair(key1,value1)
Pair(key1a,value1a)
branch2 -> Pair(key2,value2)
so a kind of groupBy, using all the values of the keys in the initially nested maps..
I have tried with
list.groupBy{it-> it.keys.first()}
but obviously it doesn't work, as it uses only the first key. I want the same, but using all keys as individual values.
What is the most idiomatic way of doing this in Kotlin ? I have an ugly looking working version in Java, but I am quite sure Kotlin has a nice way of doing it.. it's just that I am not finding it so far !
Any idea ?
Thanks
The following:
val result =
listOfMaps.asSequence()
.flatMap {
it.asSequence()
}.groupBy({ it.key }, { it.value })
will give you the result of type Map<Branch,List<Pair<String, Any>>> with the contents you requested.
val list: List<Map<Branch, Pair<String, Any>>> = listOf()
val map = list
.flatMap { it.entries }
.groupBy { it.key }
.mapValues { entry -> entry.value.map { it.value } }
I've managed to write this.
data class Branch(val name: String)
data class Key(val name: String)
data class Value(val name: String)
val sharedBranch = Branch("1")
val listOfMaps: List<Map<Branch, Pair<Key, Value>>> = listOf(
mapOf(sharedBranch to Pair(Key("1"), Value("1")),
Branch("2") to Pair(Key("2"), Value("2"))),
mapOf(sharedBranch to Pair(Key("1a"), Value("1a")))
)
val mapValues: Map<Branch, List<Pair<Key, Value>>> = listOfMaps.asSequence()
.flatMap { map -> map.entries.asSequence() }
.groupBy(Map.Entry<Branch, Pair<Key, Value>>::key)
.mapValues { it.value.map(Map.Entry<Branch, Pair<Key, Value>>::value) }
println(mapValues)
Is it appliable for your needs?
Everyone else is using flatMap, but you can also consider using fold, which is a common operation for reducing a larger collection into a smaller one. (For example, you can fold a list of integers into a single sum; here, a list of maps into a single map).
Perhaps others will find this easier to read than the flatMap versions above:
val listOfMaps: List<Map<Key, Value>> = ...
val mergedMaps: Map<Key, List<Value>> =
listOfMaps
.fold(mutableMapOf()) { acc, curr ->
curr.forEach { entry -> acc.merge(entry.key, listOf(entry.value)) { new, old -> new + old } }
acc
}
What the above code is doing:
Create a new, empty map. This will be acc (that is, the accumulator).
Iterate through our list of maps.
Work on one map (curr) at a time.
For the current map, run over each of its key/value pairs.
For each key/value, call merge on acc, passing in a list of size one (wrapping the value). If nothing is associated with the key yet, that list is added; otherwise, it is appended to the list already there.
Return the accumulating map, so it's used again in the next step.
Surprised nobody has mentioned the associate function.
val listy: List<Map<String, Int>> =
listOf(mapOf("A" to 1, "B" to 2), mapOf("C" to 3, "D" to 4))
val flattened = listy
.flatMap { it.asSequence() }
.associate { it.key to it.value }
println(flattened)
will print out {A=1, B=2, C=3, D=4}
Extract it to an extension function
private fun <K, V> List<Map<K, V>>.group(): Map<K, List<V>> =
asSequence().flatMap { it.asSequence() }.groupBy({ it.key }, { it.value })
Use it like so:
val list = yourListOfMaps
val grouped = list.group()
I know how to do this in RxJava 2.
And I know how RxKotlin helps with similar issues.
But it seems that RxKotlin.Observables doesn't have this helper function for the list overload and I cannot figure it out. How would you do this?
Most static functions in RxJava are extension functions in RxKotlin. This particular function is an extension on Iterable<Observable<T>>. You can call it like this:
listOfObservables.combineLatest { ... }
for RxJava 2 this could be done in this way
val list = Arrays.asList(
remoteRepository.getHospitals(),
remoteRepository.getQuestionCategories(),
remoteRepository.getQuestions(),
)
return Observable.combineLatest(list) {
val hospitals = it[0] as List<Hospital>
val questionCategories = it[1] as List<QuestionCategory>
val questions = it[2] as List<Question>
localRepository.insertHospitals(hospitals)
localRepository.insertQuestionCategories(questionCategories)
localRepository.insertQuestions(questions)
if (hospitals.isNotEmpty())
Constants.STATUS_OK
else
Constants.STATUS_ERROR
}
val list = Arrays.asList(Observable.just(1), Observable.just("2"))
Observable.combineLatest(list, object : FuncN<String>() {
fun call(vararg args: Any): String {
var concat = ""
for (value in args) {
if (value is Int) {
concat += value
} else if (value is String) {
concat += value
}
}
return concat
}
})
Observable.just(1), Observable.just("2") can be replaced with list of observable and login inside call fun will also changed as per requirements.