Convert String to Map Kotlin - kotlin

I´m trying to convert this Kotlin string below to map for removing duplicates names and also remove all the email entry.
var str: String = "name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
My code generates a error:
val array = log_dump.split(";")
var map = emptyMap<String, String>()
for (a in array) {
map = a.split(",").associate {
val (left, right) = it.split("=")
left to right.toString()
}
}
println(map)

As Karsten Gabriel said you got an error is because of empty string and also you are overriding users
I understand your question like you want to remove email fields and make data distinct by user.name.
If you want the end result to be string you can do it without maps
val log_dump: String =
"name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val commaRegex = Regex("\\s*,\\s*")
val semicolonRegex = Regex("\\s*;\\s*")
val sanitizedLogDump = log_dump.split(semicolonRegex).asSequence()
.mapNotNull { userString ->
var name: String? = null
val filteredUserFieldString = userString.split(commaRegex) // split by "," and also omit spaces
.filter { fieldString -> // filter field strings not to include email
val keyVal = fieldString.split("=")
// check if array contains exactly 2 items
if (keyVal.size == 2) {
// look for name
if (keyVal[0] == "name") {
name = keyVal[1]
}
// omit email fields
keyVal[0] != "email" // return#filter
} else {
false // return#filter
}
}
.joinToString(separator = ", ") // join field back to string
// omit fieldString without name and add ; to the end of fieldString
if (name == null) null else Pair(name, "$filteredUserFieldString;") // return#mapNotNull
}
.distinctBy { it.first } // distinct by name
.joinToString(separator = " ") { it.second }
println(sanitizedLogDump)
However, if you still want the end result to be map
val log_dump: String =
"name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val commaRegex = Regex("\\s*,\\s*")
val semicolonRegex = Regex("\\s*;\\s*")
val usersMap = log_dump.split(semicolonRegex).asSequence()
.mapNotNull { userString ->
var name: String? = null
val userFieldsMap = userString.split(commaRegex) // split by "," and also omit spaces
.mapNotNull { fieldString -> // filter field strings not to include email and map it to pairs
val keyVal = fieldString.split("=")
// check if array contains exactly 2 items
if (keyVal.size == 2) {
// look for name
if (keyVal[0] == "name") {
name = keyVal[1]
}
// omit email fields
if (keyVal[0] != "email") keyVal[0] to keyVal[1] else null // return#filter
} else {
null // return#filter
}
}
// omit fieldsMap without name
if (name == null) null else Pair(name, userFieldsMap) // return#mapNotNull
}
.toMap()

here is how you can do it, first change the log_dump to str, since you want to split that, and second thing, you have to remove the last ";" since you don't need that, for me I just check if it is the null or empty string then continue meaning skip this.
fun main() {
var str: String = "name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val array = str.split(";")
var map = emptyMap<String, String>()
for (a in array) {
if(a == ""){
continue
}
map = a.split(",").associate {
val (left, right) = it.split("=")
left to right.toString()
}
}
println(map)
}
Here is how you get all users.
fun main() {
var str: String = "name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val array = str.split(";")
var map = emptyMap<String, String>()
var allUsers = mutableMapOf<Int, MutableList<Pair<String, String>>>()
var count: Int = 0;
for (a in array) {
count += 1
if(a == ""){
continue
}
map = a.split(",").associate {
val (left, right) = it.split("=")
allUsers.getOrPut(count) { mutableListOf() }.add(Pair(left.toString(), right.toString() ))
left to right.toString()
}
}
println(allUsers)
// get the first user info
println(allUsers[1])
}
I am using an online compiler here so let me know if anything goes wrong.
// Output: { name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com}

The uniqueness constraint is not well defined, do you want to keep the first, last something else? Apart from uniqueness though here is a solution (online demo here) :
data class User(val name: String, val userName: String, val email: String, val id: String)
fun String.cleanSplit(token: String) = this.split(token).map { it.trim() }.filter { it.isNotEmpty() }
fun String.asUserRows() = cleanSplit(";")
fun String.asUser(): User = let {
val attributeMap = this.cleanSplit(",").map { it.cleanSplit("=") }.associate { it[0] to it[1] }
User(attributeMap["name"]!!, attributeMap["username"]!!, attributeMap["email"]!!, attributeMap["id"]!!)
}
fun main() {
val str: String =
"name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val users: List<User> = str.asUserRows().map { it.asUser() }.also { println(it) }
// at this point you can enforce uniqueness on users
// for example this code will have list of users with unique name keeping the last one
users.associateBy { it.name }.values.also { println(it) }
}
}

There are a few small mistakes in your code:
You get an error because the split also yields an empty string which has to be filtered out.
You overwrite your result map on each iteration, instead of saving the complete result.
You should trim your result keys to remove unwanted leading or trailing spaces (e.g. to obtain username instead of username).
var str: String = "name=John Trust, username=john3, email=john3#gmail.com, id=434453; name=Hannah Smith, username=hsmith, email=hsm#test.com, id=23312; name=Hannah Smith, username=hsmith, id=3223423, email=hsm#test.com;"
val array = str.split(";").filterNot { it.isEmpty() } // 1. filter out non-empty strings
var map = array.map { // 2. use map instead of a for-loop
it.split(",").associate {
val (left, right) = it.split("=")
left.trim() to right.toString() // 3. trim keys
}
}

Related

Remove character or word from string in kotlin

Hey I want to remove character/word or sentence from string.
For example
val string = "Hey 123"
or
val string = "Hey How are you 123"
or
val string = "Hey!! How are you 123"
and output
string = 123
If you only want the digits:
val result = string.filter { it.isDigit() }
Alternatively if you want to omit letters (and maybe also whitespace):
val result = string.filter { !it.isLetter() }
val result = string.filter { !it.isLetter() && !it.isWhitespace() }

Kotlin: Find the first line of a file that matches a regex, and return captured values

i want get text from a file by using regEx and want save the file with a new name (using the results of the regEx-Find).
My Problem is that i cant get/return the correct genearated (in this example xyz maur) out of the function readFileLineByLineUsingForEachLine(fileName: String) the new newFileName which was generated (sucessfully as expected) in the function.
Line 1 of Source:
start {"Name":"xyz","Civ":"maur","Team":0}
My Prototype:
fun main() {
val f = "./commands.txt";
var newFileName = readFileLineByLineUsingForEachLine(f)
print(newFileName.)
val source = Paths.get(f)
val target = Paths.get("/home/x/snap/0ad/199/.local/share/0ad/replays/0.0.24/2021-03-14_0016/" + newFileName)
// try {
// val move = Files.move(
// source,
// target
// )
// } catch (e: IOException) {
// e.printStackTrace()
// }
};
fun readFileLineByLineUsingForEachLine(fileName: String) // https://www.baeldung.com/kotlin/read-file
= File(fileName).forEachLine lit#{
// "Name":"Cleisthenes"
val regexString = """
"Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0
""".trim()
var regex = Regex(regexString)
var matched = regex.find(it)?.groupValues
val Name = matched?.get(1)
val Civ = matched?.get(2)
if (Name != null)
println(Name)
if (Civ != null)
println(Civ)
val newFileName = "$Name $Civ"
return#lit
}
Because you want to stop processing as soon as you find a match, I don't think forEachLine is the best choice. Instead you can use useLines, and combine it with first to stop processing once you get a match:
val regex = Regex(""""Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0""")
fun readFileLineByLineUsingForEachLine(fileName: String) =
File(fileName).useLines { lines ->
val (name, civ) = lines
.map { regex.find(it) }
.filterNotNull()
.first()
.destructured
"$name $civ"
}
For the example you provided, this returns the string "xyz" "maur".
that's just a very little modification of the correct, helpful answer from Adam here https://stackoverflow.com/a/66654710/2891692
fun readFileLineByLineUsingForEachLine2(fileName: String) =
File(fileName).useLines { lines ->
val (name, civ) = lines
.map {
val regexString = """
"Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0
""".trim()
var regex = Regex(regexString)
regex.find(it)
}
.filterNotNull()
.first()
.destructured
"$name $civ"
}

How can I remove the char 'a' from a list?

I have this code :
fun main(args:Array<String>){
var a = "eat, banana, one"
var a1 = a.split(",").toMutableList()
a1.sortBy { it.toCharArray().count { it == 'a' } }
var a2 = a1.associateWith { word -> word.count { char -> char == 'a' } }
a2.keys.filterNot { c -> "a".contains(c)}
}
Actually, I want to remove the "a" in the word that I have using this line :
a2.keys.filterNot { c -> "a".contains(c)} but it does not work.
How could I do to remove all the a in a2 ?
Thank you very much !
In order to remove all a characters from your keys, you can replace them with an empty string:
a2.mapKeys { it.key.replace("a", "")}
you can map the keys to a new map and replace the a with an empty String in the keys. You then need to use the new created map as result:
fun main(args:Array<String>){
val a = "eat, banana, one"
val a1 = a.split(",").toMutableList()
a1.sortBy { it.toCharArray().count { it == 'a' } }
val a2 = a1.associateWith { word -> word.count { char -> char == 'a' } }
val result = a2.mapKeys { it.key.replace("a", "")}
println(result) // prints { one=0, et=1, bnn=3}
}

Kotlin replace multiple words in string

How to replace many parts of a string with something else in Kotlin using .replace()
For example, we can do it only by replacing one word
fun main(args: Array<String>) {
var w_text = "welcome his name is John"
println("${w_text.replace("his","here")}")
}
and the result will be " welcome here name is John " .
finally we need the result be " welcome here name is alles "
by replacing his to here and john to alles using .replace()
You can do it using multiple consecutive calls to replace():
w_text.replace("his", "here").replace("john", "alles")
You could write an extension that overloads String::replace:
fun String.replace(vararg replacements: Pair<String, String>): String {
var result = this
replacements.forEach { (l, r) -> result = result.replace(l, r) }
return result
}
fun main(args: Array<String>) {
val sentence = "welcome his name is John"
sentence.replace("his" to "here", "John" to "alles")
}
If you have many of those replacement rules, then create a mapping of them and call the replace method in a loop:
val map = mapOf("his" to "here", "john" to "alles", ...)
val sentence = "welcome his name is John"
var result = sentence
map.forEach { t, u -> result = result.replace(t, u) }
println(result)
For the ones interested in replacing a map of values in a text:
private fun replaceText(text: String, keys: Map<String, String>): String =
val replaced = map.entries.fold(text) { acc, (key, value) -> acc.replace(key, value) }
Here is a one liner:
fun String.replace(vararg pairs: Pair<String, String>): String =
pairs.fold(this) { acc, (old, new) -> acc.replace(old, new, ignoreCase = true) }
Test:
#Test fun rep() {
val input = "welcome his name is John"
val output = input.replace("his" to "her", "john" to "alles")
println(output)
output shouldBeEqualTo "welcome her name is alles"
}
Similar to other responses but using Kotlin extension and overloading String::replace to accept a map of oldValue to newValue.
fun String.replace(mapping: Map<String, String>): String {
var str = this
mapping.forEach { str = str.replace(it.key, it.value) }
return str
}
Usage:
val mapping = mapOf("his" to "here", "John" to "alles")
"his dad is John".replace(mapping) // here dad is alles
The issue with just using replace without any regex is:
Let's say I want to replace the occurrence of "here" with "there" inside the string "Where is my bag? Your bag is here." As you can imagine the result will be "Wthere is my bag? Your bag is there." which will not be correct. The solution is to use a regex like given below.
var str = "Where is my bag? Your bag is here."
val replacements = setOf("\\bhere\\b" to "there",
"\\bjohn\\b" to "alles")
replacements.forEach {
str = str.replace(Regex(it.first), it.second)
}

Check for null in map function in Kotlin

I'm new to Kotlin and I want to map an object (ProductVisibility) base on another one (fmpProduct). Some object can't be converted so I need to skip them on some condition.
I wanted to know if there's a better way to do this than what I did with the filter and the "!!" I feel that it's hacked. Am I missing something ?
val newCSProductVisibility = fmpProducts
.filter { parentIdGroupedByCode.containsKey(it.id) }
.filter { ProductType.fromCode(it.type) != null } //voir si on accumule les erreus dans une variable à montrer
.map {
val type = ProductType.fromCode(it.type)!! //Null already filtered
val userGroupIds = type.productAvailabilityUserGroup.map { it.id }.joinToString(",")
val b2bGroupIds = type.b2bUserGroup.map { it.id }.joinToString { "," }
val b2bDescHide = !type.b2bUserGroup.isEmpty()
val parentId = parentIdGroupedByCode[it.id]!! //Null already filtered
CSProductDao.ProductVisibility(parentId, userGroupIds, b2bGroupIds, b2bDescHide)
}
edit: updated the map access like comment suggested
Use mapNotNull() to avoid the filter()s and do everything in the mapNotNull() block, then the automatic typecast to non-null type works.
Example:
fun f() {
val list = listOf<MyClass>()
val v = list.mapNotNull {
if (it.type == null) return#mapNotNull null
val type = productTypeFromCode(it.type)
if (type == null) return#mapNotNull null
else MyClass2(type) // type is automatically casted to type!! here
}
}
fun productTypeFromCode(code: String): String? {
return null
}
class MyClass(val type: String?, val id: String)
class MyClass2(val type: String)