Unable to replace string inside a String in Kotlin - kotlin

I am trying to replace a few sub strings inside a string. But my code doesn't seem to work.
val listOfMaleWords = listOf(" him", " he", " his")
val listOfFemaleWords = listOf(" her", " she", " her")
fun modifyIdeaForGender(rawIdea : String, desiredGender : String): String {
var theRawIdea = rawIdea
if (desiredGender == "FEMALE") {
println("desired gender is FEMALE")
listOfMaleWords.forEachIndexed { index, element ->
theRawIdea.replace(element, listOfFemaleWords[index])
}
} else {
println("desired gender is MALE")
listOfFemaleWords.forEachIndexed { index, element ->
theRawIdea.replace(element, listOfMaleWords[index])
}
}
return theRawIdea
}
fun main() {
var sampleString : String = "Tell him, he is special"
println(modifyIdeaForGender(sampleString, "FEMALE"))
}
Expected Output :
"Tell her, she is special"
Current Output :
"Tell him, he is special" // no change
Whats wrong with my code? The current output doesn't replace the string characters at all.

replace returns a new String that you are discarding immediately. It does not mutate theRawIdea itself, so you should assign it back to theRawIdea yourself. For example:
theRawIdea = theRawIdea.replace(element, listOfFemaleWords[index])
Though this would modify theRawIdea as you desire, it wouldn't replace the pronouns correctly. Once it replaces the "him"s with "her"s, it would try to replace the "he"s with "she"s. But note that "he" a substring of "her"! So this would produce:
Tell sher, she is special
This could be fixed by reordering the lists, putting the "he"-"she" pair first, or by using regex, adding \b word boundary anchors around the words:
// note that you should not have spaces before the words if you decide to use \b
val listOfMaleWords = listOf("him", "he", "his")
val listOfFemaleWords = listOf("her", "she", "her")
...
theRawIdea = theRawIdea.replace("\\b$element\\b".toRegex(), listOfFemaleWords[index])
Note that this doesn't account for capitalisation or the fact that changing from female gender pronouns to male ones is inherently broken. Your current code would change all her to him. It would require some more complicated natural language processing to accurately do this task in general.
Taking all that into account, I've rewritten your code with zip:
fun modifyMaleIdeaToFemaleGender(rawIdea : String): String {
var theRawIdea = rawIdea
// if you really want to do the broken female to male case, then this would be
// listOfFemaleWords zip listOfMaleWords
// and the loop below can stay the same
val zipped = listOfMaleWords zip listOfFemaleWords
zipped.forEach { (target, replacement) ->
theRawIdea = theRawIdea.replace("\\b$target\\b".toRegex(), replacement)
}
return theRawIdea
}
You can also use fold to avoid reassigning theRawIdea:
fun modifyIdeaToFemaleGender(rawIdea : String): String {
val zipped = listOfMaleWords zip listOfFemaleWords
return zipped.fold(rawIdea) { acc, (target, replacement) ->
acc.replace("\\b$target\\b".toRegex(), replacement)
}
}

Your code assumes that the replace() method performs an in-place mutation of the string. However, the string with the replaced values are returned by the replace(). So you need to change your code to contain something like:
theRawIdea = theRawIdea.replace(element, listOfFemaleWords[index])
To do this, you will have to use a conventional loop instead of listOfMaleWords.forEachIndexed style looping.

Related

Convert String into list of Pairs: Kotlin

Is there an easier approach to convert an Intellij IDEA environment variable into a list of Tuples?
My environment variable for Intellij is
GROCERY_LIST=[("egg", "dairy"),("chicken", "meat"),("apple", "fruit")]
The environment variable gets accessed into Kotlin file as String.
val g_list = System.getenv("GROCERY_LIST")
Ideally I'd like to iterate over g_list, first element being ("egg", "dairy") and so on.
And then ("egg", "dairy") is a tuple/pair
I have tried to split g_list by comma that's NOT inside quotes i.e
val splitted_list = g_list.split(",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*\$)".toRegex()).toTypedArray()
this gives me first element as [("egg", second element as "dairy")] and so on.
Also created a data class and tried to map the string into data class using jacksonObjectMapper following this link:
val mapper = jacksonObjectMapper()
val g_list = System.getenv("GROCERY_LIST")
val myList: List<Shopping> = mapper.readValue(g_list)
data class Shopping(val a: String, val b: String)
You can create a regular expression to match all strings in your environmental variable.
Regex::findAll()
Then loop through the strings while creating a list of Shopping objects.
// Raw data set.
val groceryList: String = "[(\"egg\", \"dairy\"),(\"chicken\", \"meat\"),(\"apple\", \"fruit\")]"
// Build regular expression.
val regex = Regex("\"([\\s\\S]+?)\"")
val matchResult = regex.findAll(groceryList)
val iterator = matchResult.iterator()
// Create a List of `Shopping` objects.
var first: String = "";
var second: String = "";
val shoppingList = mutableListOf<Shopping>()
var i = 0;
while (iterator.hasNext()) {
val value = iterator.next().value;
if (i % 2 == 0) {
first = value;
} else {
second = value;
shoppingList.add(Shopping(first, second))
first = ""
second = ""
}
i++
}
// Print Shopping List.
for (s in shoppingList) {
println(s)
}
// Output.
/*
Shopping(a="egg", b="dairy")
Shopping(a="chicken", b="meat")
Shopping(a="apple", b="fruit")
*/
data class Shopping(val a: String, val b: String)
Never a good idea to use regex to match parenthesis.
I would suggest a step-by-step approach:
You could first match the name and the value by
(\w+)=(.*)
There you get the name in group 1 and the value in group 2 without caring about any subsequent = characters that might appear in the value.
If you then want to split the value, I would get rid of start and end parenthesis first by matching by
(?<=\[\().*(?=\)\])
(or simply cut off the first and last two characters of the string, if it is always given it starts with [( and ends in )])
Then get the single list entries from splitting by
\),\(
(take care that the split operation also takes a regex, so you have to escape it)
And for each list entry you could split that simply by
,\s*
or, if you want the quote character to be removed, use a match with
\"(.*)\",\s*\"(.*)\"
where group 1 contains the key (left of equals sign) and group 2 the value (right of equals sign)

how to read mutliple lines of string into one variable using readln() in kotlin?

example:
a variable
val str = readln().replace("[^A-Za-z0-9 ] \\s+".toRegex(),"").trim()
should read multiple lines of input value, input value will be like this
heading
----------
topic1
topic2
or like this
heading
-------
a) topic1
b) topic2
input may contain special characters or tabs or spaces we need to remove them also
I don't know what your Regex is trying to do, but that's not really your question.
How do you know when the user has finished their input - a special word or an empty line?
Assuming an empty line, here's how you can get all the content
println("Enter something:")
var lines = ""
do {
val line = readLine()
lines += "${clean(line)}\n"
} while (!line.isNullOrBlank())
println("User input:\n$lines")
private fun clean(line: String?): String? {
return line?.replace("[^A-Za-z0-9 ] \\s+".toRegex(),"")?.trim()
}

find string within list of strings

validValueType.ValueTypeGroup
["\"is_enabled\": false", "\"value\":\"OUT\""]
failedRecord.record
{"email":"test#gmail.com","source":"web","value":"OUT","reate_date":"2022-09-29T03:42:09.976-05:00","is_undeliverable":false}
fun publishAlert(failedRecord: Record<String>) {
if (validValueType.ValueTypeGroup.contains(failedRecord.record)) {
// do stuff
} else {
// no match do other stuff
}
}
In the list above there are two strings I want to check for when this function receives a record.
The failedRecord.record string does contain what I want "value":"OUT" and it's also within the list above. So why is contains not working here? it keeps bouncing out to the else statement.
You can use any() in the list and pass a predicate:
{x -> searchString.contains(x)}
The searchString.contains() will search x as a substring inside searchString for each x representing the elements in the list
var list = listOf("\"is_enabled\": false", "\"value\":\"OUT\"")
println(list)
println(list::class.qualifiedName)
println() // empty newline
var searchString = "{\"email\":\"test#gmail.com\",\"source\":\"web\",\"value\":\"OUT\",\"create_date\":\"2022-09-29T03:42:09.976-05:00\",\"is_undeliverable\":false}";
println(searchString)
println(searchString::class.qualifiedName)
println() // empty newline
println(list.any{x -> searchString.contains(x)});
Output
["is_enabled": false, "value":"OUT"]
java.util.Arrays.ArrayList
{"email":"test#gmail.com","source":"web","value":"OUT","create_date":"2022-09-29T03:42:09.976-05:00","is_undeliverable":false}
kotlin.String
true

How to get the string in Kotlin to readable format?

I don't know how to get the string in readable format in my app. My code is:
val allergyList = recipeItem.allergies
allergyList.joinToString()
var allergyString: String = ""
for (allergy in allergyList) {
allergyList[1]
allergyString += " ${allergy}"
println(allergy.toString())
}
holder.recipeSearchPageAllergies.text = allergyString
When I print this I get the allergy string memory space?
Result for each one is something like this:
Allergy#4e8f238
How do I 'decode' it into something readable for a human? It should say 'nut allergy'.
you have some options. If you have the control over the Allergy source code,
you could override toString method:
class Allergy(val name: String) {
override fun toString(): String = "Allergy[name=${name}]"
}
println(Allergy("flowers"))
// Allergy[name=flowers]
also, you can make a data class of it. Data class has sane toString by default. It also has a few nice perks, like by default equals/hashCode generation, and deconstruction to components, allowing you to use it in destructing:
data class Allergy(val name: String)
println(Allergy("peanuts"))
// Allergy(name=peanuts)
otherwise, if you can't modify the source of the Allregy, you can make up an extension method for that:
class Allergy(val name: String)
fun Allergy.readable() = "Allergy[name=${name}]"
println(Allergy("cats").readable())
// Allergy[name=cats]
in your case, you could also make an extension method for collections of allergies to have the format you need:
fun Collection<Allergy>.readable() = joinToString { "Allergy[name=${it.name}]" }
println(
listOf(Allergy("cats"), Allergy("peanuts"), Allergy("flowers")).readable()
)
// Allergy[name=cats], Allergy[name=peanuts], Allergy[name=flowers]
// in your case:
holder.recipeSearchPageAllergies.text = recipeItem.allergies.readable()
// or just
holder.recipeSearchPageAllergies.text = recipeItem.allergies.joinToString { "Allergy[name=${it.name}]" }
You can make it simplier:
val allergiesStr = recipeItem.allergies.map { allergy ->
// your allergy name from allergy variable
}.joinToString(separator = " ")

Kotlin String substitution not working when string is read from file

I have written a code that reads a text file. The text files contain placeholders which I would like to replace. The substitution does not work this way and the string is printed with the placeholders. Here is the code that I have written for this:
class TestSub(val sub: Sub) {
fun create() = template()
fun template() = Files.newBufferedReader(ClassPathResource(templateId.location).file.toPath()).readText()
}
data class Sub(val name: String, val age: Int)
Here is the main function that tries to print the final string:
fun main(args: Array<String>) {
val sub = Sub("Prashant", 32)
println(TestSub(sub).create())
}
However, when, instead of reading a file, I use a String, the following code works (Replacing fun template())
fun template() = "<h1>Hello ${sub.name}. Your age is ${sub.age}</h1>"
Is there a way to make string Substitution work when reading the content of a file?
Kotlin does not support String templates from files. I.e. code like "some variable: $variable" gets compiled to "some variable: " + variable. String templates are handled at compile time, which means it does not work with text loaded from files, or if you do something else to get the String escaped into a raw form. Either way, it would, as danielspaniol mentioned, be a security threat.
That leaves three options:
String.format(str)
MessageFormat.format(str)
Creating a custom engine
I don't know what your file contains, but if it's the String you used in the template function, change it to:
<h1>Hello {0}. Your age is {1,integer}</h1>
This is for MessageFormat, which is my personal preference. If you use String.format, use %s instead, and the other appropriate formats.
Now, use that in MessageFormat.format:
val result = MessageFormat.format(theString, name, age);
Note that if you use MessageFormat, you'll need to escape ' as ''. See this.
String substitution using ${...} is part of the string literals syntax and works roughly like this
val a = 1
val b = "abc ${a} def" // gets translated to something like val b = "abc " + a + " def"
So there is no way for this to work when you load from a text file. This would also be a huge security risk as it would allow for arbitrary code execution.
However I assume that Kotlin has something like a sprintf function where you can have placeholders like %s in your string and you can replace them with values
Take a look here. It looks like the easiest way is to use String.format
You are looking for something similar to Kotlin String templates for raw Strings, where placeholders like $var or ${var} are substituted by values, but this functionality needs to be available at runtime (for text read from files).
Methods like String.format(str) or MessageFormat.format(str) use other formats than the notation with the dollar prefix of Kotlin String templates. For "Kotlin-like" placeholder substitution you could use the function below (which I developed for similar reasons). It supports placeholders as $var or ${var} as well as dollar escaping by ${'$'}
/**
* Returns a String in which placeholders (e.g. $var or ${var}) are replaced by the specified values.
* This function can be used for resolving templates at RUNTIME (e.g. for templates read from files).
*
* Example:
* "\$var1\${var2}".resolve(mapOf("var1" to "VAL1", "var2" to "VAL2"))
* returns VAL1VAL2
*/
fun String.resolve(values: Map<String, String>): String {
val result = StringBuilder()
val matcherSimple = "\\$([a-zA-Z_][a-zA-Z_0-9]*)" // simple placeholder e.g. $var
val matcherWithBraces = "\\$\\{([a-zA-Z_][a-zA-Z_0-9]*)}" // placeholder within braces e.g. ${var}
// match a placeholder (like $var or ${var}) or ${'$'} (escaped dollar)
val allMatches = Regex("$matcherSimple|$matcherWithBraces|\\\$\\{'(\\\$)'}").findAll(this)
var position = 0
allMatches.forEach {
val range = it.range
val placeholder = this.substring(range)
val variableName = it.groups.filterNotNull()[1].value
val newText =
if ("\${'\$'}" == placeholder) "$"
else values[variableName] ?: throw IllegalArgumentException("Could not resolve placeholder $placeholder")
result.append(this.substring(position, range.start)).append(newText)
position = range.last + 1
}
result.append(this.substring(position))
return result.toString()
}
String templates only work for compile-time Sting literals, while what u read from a file is generated at runtime.
What u need is a template engine, which can render templates with variables or models at runtime.
For simple cases, String.format or MessageFormat.format in Java would work.
And for complex cases, check thymeleaf, velocity and so on.