I have two JsonNode objects which are arrays returned from API calls and look like this
exportedNodeArray:
[
{"key":"111", "value":"aaa"},
{"key":"222", "value":"bbb"},
{"key":"333", "value":"ccc"}
]
localNodeArray
[
{"key":"999", "value":"aaa"},
{"key":"888", "value":"bbb"},
{"key":"777", "value":"ccc"}
]
The required output is a Map of any keys which correspond to the same values in each array. The values are guaranteed to be unique within an array.
"111"="999"
"222"="888"
"333"="777"
This function returns the correct result, but seems like a very in-elegant way to do it.
fun mapIds(exportedNodeArray: JsonNode, localNodeArray: JsonNode) : MutableMap<String, String?> {
val localMap = mutableMapOf<String, String>()
localNodeArray.forEach {
localMap[it["value"].asText()] = it["key"].asText()
}
val idMap = mutableMapOf<String, String?>()
exportedNodeArray.forEach {
idMap[it["key"].asText()] = localMap[it["value"].asText()]
}
return idMap
}
I am new to Kotlin, and would like to understand a more functional approach. Especially if there is a way to access elements of a JsonNode by attribute value, and accomplish this in a single loop or map call.
If you define one more function, it could look like this:
fun mapIds(exportedNodeArray: JsonNode, localNodeArray: JsonNode) : Map<String, String> {
val localConverted = convert(localNodeArray)
return convert(exportedNodeArray)
.filterKeys(localConverted::containsKey)
.map { it.value to localConverted.getValue(it.key) }
.toMap()
}
fun convert(node: JsonNode): Map<String, String> = node.associate {
it["value"].asText() to it["key"].asText()
}
Related
In TypeScript I can create a mapped type, along the lines of
interface IConfig {
[s : string]: number
}
is this possible in Kotlin?
I wanna be able to do something like
data class ConfigDataClass(val volume : number) : IConfig
Then later I can loop through all the data class members of a data class that satisfies IConfig and know that they are numbers
There's a few options that are close to TypeScript's Mapped Types
The basic option is just to use a Map<String, Int>. The keys will always be strings, and the values will always be integers. This matches your first definition of IConfig.
fun main() {
val iConfigMap = mapOf(
"blah" to 123,
)
println(iConfigMap)
// {blah=123}
}
What about if you want a distinct type for ConfigDataClass? We can use delegation to easily make ConfigDataClass a map, but without having to re-implement lots of code.
class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map
// ^ delegate the implementation of Map to map
IConfig can now be used exactly like a Map<String, Int>, but because it's a distinct type, we can easily write specific functions for it
data class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map {
// add a helper constructor to emulate mapOf(...)
constructor(vararg pairs : Pair<String, Int>) : this(pairs.toMap())
// an example function that's only for ConfigDataClass
fun toStringUppercaseKeys() : ConfigDataClass =
ConfigDataClass(map.mapKeys { (key, _) -> key.uppercase() })
}
fun main() {
val iConfig = ConfigDataClass(
"blah" to 123,
)
// I can call Map.get(...) and .size,
// even though ConfigDataClassdoesn't implement them
println(iConfig["blah"]) // 123
println(iConfig.size) // 1
println(iConfig.toStringUppercaseKeys()) // ConfigDataClass(map={BLAH=123})
}
Finally, we can also easily add a specific named field - volume. Kotlin has a really a niche feature to allow properties to be delegated to values in a map.
data class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map {
constructor(vararg pairs : Pair<String, Int>) : this(pairs.toMap())
val volume: Int by map // finds the value of "volume" in the map
}
fun main() {
val iConfig = ConfigDataClass(
"volume" to 11,
)
println(iConfig.volume) // 11
}
Note that if there's no key "volume" in the map, you'll get a nasty exception!
fun main() {
val iConfig = ConfigDataClass(
"blah" to 123,
)
println(iConfig.volume)
}
// Exception in thread "main" java.util.NoSuchElementException: Key volume is missing in the map.
If you want your data to be mutable, you can instead delegate to a MutableMap, and change val volume to var volume.
I have my own converter from Strings to List
object TypeConverter {
fun stringToListLong(text: String): List<Long> {
val listLong = mutableListOf<Long>()
val listString = text.split(",").map { it.trim() }
listString.forEach {
listLong.add(it.toLong())
}
return listLong
}
}
Then when I try to use it like below it shows the error(Unresolved reference: add)
val someString = "something"
var ids = TypeConverter.stringToListLong(someString)
ids.add(some long value)
Why?
You're returning a List<>, so ids is a List<>, therefore it does not have mutation operations. Make stringToListLong return MutableList<Long>.
how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...
I have a
val map = Map<String,String>
map.put("Nurseiyt","android")
I want to get a value by subString like:
map["Nurs"] should return "android"
is it possible?
Use kotlin.Collections, there are methods like filter.
Two things - it's better to use regular expression. So, you can even get better control what will be returned. And the second one, there can be more than one elements matched to that regex. So that's why I return list.
fun <T> substringKey(map: Map<String, T>, regex: Regex): List<T> {
return map.filter { it.key.contains(regex) }
.map { it.value }
}
If you want to use that notation you need to create your own map and override proper operator. What's worth to notice, you cannot return list of values then. So, in this case I just return first found value.
class SubstringMap<V> : HashMap<String, V>() {
override operator fun get(key: String): V? {
return this.entries.first { it.key.contains(key) }.value
}
}
fun main() {
val map = SubstringMap<String>()
map["Nurseiyt"] = "android"
println(map["Nurs"]) // "android"
}
And as the last thing - in kotlin you can create your own operator, like withKeyPart. This would be much better than overriding default operator (because I wouldn't expect that [] operator will work in different way than usual.
infix fun <V> Map<String, V>.withKeyPart(keyPart: String): List<V> {
return this.filter { it.key.contains(keyPart) }
.map { it.value }
}
and then call it like this:
fun main() {
val map = HashMap<String, String>()
map withKeyPart "KeyPart" // infix notation
map.withKeyPart("KeyPart") // standard call
}
Filtering the map, as per other answers, is simple and straightforward, but it doesn't scale well; it takes time proportional to the size of the map, so if the map could grow big, it could get very slow.
If you're always going to be searching for a leading substring, i.e. the start of a map key, then a better general solution is a data structure called a trie. This lets you search efficiently, with just one lookup per character.
Of course, writing one from scratch may not be justified for your project. But there are third-party implementations you could use, such as this one in Apache Commons. Or see the answers to this question.
write top level function like this
fun HashMap<String, String>.getContainskeyValue(search: String): String?
{
var returnList = ArrayList<String?>()
this.keys.filter { it.contains(search) }.map {
returnList.add(this[it])
}
return returnList.first()
//if you want all keys 'contains' values just return list
/* Ex
map.put("Nurseiyt", "android")
map.put("Nurseiyt1", "androidone")
map.put("Nurseirt2", "andrrroidtwo")
val isContainsdata = map.getContainskeyValue("N")
println(" result " + containsdata)
output :result [andrrroidtwo, android, androidone]
*/
}
then call like this
val map = HashMap<String, String>()
map.put("Nurseiyt", "android")
val containsdata = map.getContainskeyValue("Nurs")
println(" result " + containsdata)
output
android
After performing a parallelStream() on a List, I end up with a List<Map<String, Set<String>. I want to unify this into a Map<String, Set<String>> (which will only keep uniques across the List of Maps).
I am unfamiliar with the collect and reduce functions, so don't have anything to go ahead with.
Existing code:
private val TYPES = listOf("string", "integer")
private fun getLinesOfEachTypeAcrossMultipleFiles(files: List<File>): Map<String, Set<String>> {
return files
.parallelStream()
.map { file ->
TYPES.associate {
it to getRelevantTypeLinesFromFile(file)
}
}
// Converted into a Stream<String, Set<String>>
// .reduce() / collect() ?
}
private fun getRelevantTypeLinesFromFile(it: File): Set<String> {
// Sample code
return setOf()
}
If you're looking for an equivalent Java code, you can stream all the entries using flatMap and then collect them as a Map with a merge function as :
Map<String, Set<String>> some(List<Map<String, Set<String>>> listOfMap) {
return listOfMap.stream()
.flatMap(a -> a.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(s1, s2) -> {
s1.addAll(s2);
return s1;
}));
}
I figured out and implemented a Kotlin-specific solution of using the fold operator (instead of reduce or collect):
private val TYPES = listOf("string", "integer")
private fun getLinesOfEachTypeAcrossMultipleFiles(files: List<File>): Map<String, Set<String>> {
return files
.map { file ->
TYPES.associate { it to getRelevantTypeLinesFromFile(file) }
}
.fold(mutableMapOf<String, MutableSet<String>>()) { acc, map ->
acc.apply {
map.forEach { key, value ->
acc.getOrPut(key) { mutableSetOf() }.addAll(value)
}
}
}
}
private fun getRelevantTypeLinesFromFile(it: File): Set<String> {
// Sample code
return setOf()
}
A benefit of using fold is that we don't need to change the type of the data from Map to MutableMap and Set to MutableSet.