keeping track of injected variables when using string interpolation in kotlin - kotlin

I'm looking for a way to keep track of variables used when doing a string interpolation without parsing the string. For example, if I have a string:
val expStr = "${var1} some other useless text ${var2}"
I want to be able to identify the order of the variables used, again without parsing the string. In this case [var1, var2] would be an expected output. So far I've thought of defining a class where I pass it all of the variables. Then reference said variables through the class function grab.
val wrapper = ContainerClass(var1, var2)
val expStr = "${wrapper.grab(var1)} some other useless text ${wrapper.grab(var2)}"
inside ContainerClass is a array, each time a variable is referenced it is added to the array and outputted through getReferenceCalls
val whatIWant = wrapper.getReferenceCalls() // [var1, var2]
This works great until I introduce the injection of strings into strings.
val wrapper = ContainerClass(var1, var2, var3)
val snippet = "${wrapper.grab(var1)} some other useless text ${wrapper.grab(var2)}"
val expStr = "${wrapper.grab(var3)} ${snippet}"
val notWhatIWant = wrapper.getReferenceCalls() // [var1, var2, var3]
Here, I want to identify the order of the injected variables in the final expStr ie. [var3, var1, var2]. My question is, is this possible without parsing expStr? I did also think of a not so elegant solution of allowing my class to define any given "snippet" and the class identifies the variables referenced in the snippet. This works but becomes convoluted fast. What I really need is an eligant solution...if it exists.

I have implemented a "ContainerClass" to achieve your goal. I uses String.format instead of string templates so that I don't need prior information of the input.
class StringNode(private val format: String, vararg args : Any) {
private val argv = args
override fun toString() : String = String.format(format,*argv)
fun getFlatArgs() : List<Any> = argv.flatMap {
if(it is StringNode){
it.getFlatArgs()
} else{
listOf(it)
}
}
}
Usage:
fun main(){
val sn1 = StringNode("1:%s 2:%s 3:%s","abc",123,"def")
println(sn1)
println(sn1.getFlatArgs())
val sn2 = StringNode("foo:%s bar:%s","foo",sn1);
println(sn2)
println(sn2.getFlatArgs())
val sn3 = StringNode("sn1:%s, sn2:%s",sn1,sn2);
println(sn3)
println(sn3.getFlatArgs())
}
Output:
1:abc 2:123 3:def
[abc, 123, def]
foo:foo bar:1:abc 2:123 3:def
[foo, abc, 123, def]
sn1:1:abc 2:123 3:def, sn2:foo:foo bar:1:abc 2:123 3:def
[abc, 123, def, foo, abc, 123, def]

val var1 = "abc"
val var2 = "def"
val list = mutableListOf<String>()
val expStr = "${var1.also { list.add(it) }} some other useless text ${var2.also { list.add(it) }}"
println(expStr) // Output: "abc some other useless text def"
println(list) // Output: [abc, def]
Or:
val var1 = "abc"
val var2 = "def"
val list = mutableListOf<String>()
fun String.addTo(list: MutableList<String>) = this.also { list.add(it) }
val expStr = "${var1.addTo(list)} some other useless text ${var2.addTo(list)}"
println(expStr) // Output: "abc some other useless text def"
println(list) // Output: [abc, def]

Related

Kotlin property delegation not working as expected

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.

Kotlin Creating List<List<Map<String, String>>>

I am trying to return List<List<Map<String, String>>> from a function in kotlin. I'm new to kotlin.
Edit1
Here's how I am attempting to to this
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
The problem with the above variable is, I am unable to figure out how to insert data into this variable. I tried with this:
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
val b = mutableListOf(mutableMapOf<String, String>())
val c = mutableMapOf<String, String>()
c.put("c", "n")
b.add(c)
a.add(b)
This is giving me:
[[{}], [{}, {c=n}]]
What I want is [[{c=n}]]
Can someone tell me how I can insert data into it?
The end goal I am trying to achieve is to store data in the form of List<List<Map<String, String>>>
EDIT 2
The function for which I am trying to write this dat structure:
fun processReport(file: Scanner): MutableList<List<Map<String, String>>> {
val result = mutableListOf<List<Map<String, String>>>()
val columnNames = file.nextLine().split(",")
while (file.hasNext()) {
val record = mutableListOf<Map<String, String>>()
val rowValues = file.nextLine()
.replace(",(?=[^\"]*\"[^\"]*(?:\"[^\"]*\"[^\"]*)*$)".toRegex(), "")
.split(",")
for (i in rowValues.indices) {
record.add(mapOf(columnNames[i] to rowValues[i]))
print(columnNames[i] + " : " + rowValues[i] + " ")
}
result.add(record)
}
return result
}
You don't need to use mutable data structures. You can define it like this:
fun main() {
val a = listOf(listOf(mapOf("c" to "n")))
println(a)
}
Output:
[[{c=n}]]
If you wanted to use mutable data structures and add the data later, you could do it like this:
fun main() {
val map = mutableMapOf<String, String>()
val innerList = mutableListOf<Map<String, String>>()
val outerList = mutableListOf<List<Map<String, String>>>()
map["c"] = "n"
innerList.add(map)
outerList.add(innerList)
println(outerList)
}
The output is the same, although the lists and maps are mutable.
In response to the 2nd edit. Ah, you're parsing a CSV. You shouldn't try to do that yourself, but you should use a library. Here's an example using Apache Commons CSV
fun processReport(file: File): List<List<Map<String, String>>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map {
it.toMap().entries.map { (k, v) -> mapOf(k to v) }
}
}
For the following CSV:
foo,bar,baz
a,b,c
1,2,3
It produces:
[[{foo=a}, {bar=b}, {baz=c}], [{foo=1}, {bar=2}, {baz=3}]]
Note that you can simplify it further if you're happy returning a list of maps:
fun processReport(file: File): List<Map<String, String>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map { it.toMap() }
}
Output:
[{foo=a, bar=b, baz=c}, {foo=1, bar=2, baz=3}]
I'm using Charset.defaultCharset() here, but you should change it to whatever character set the CSV is in.

Kotlin spread operator behaviour on chars array

I have been using Kotlin for some time now, but I just found out that when I would like to use spread operator on the array of chars and pass it to the split function, it does not work.
fun main() {
val strings = arrayOf("one", "two")
val stringSplit = "".split("one", "two")
val stringsSplit = "".split(*strings)
val chars = arrayOf('1', '2')
val charSplit = "".split('1', '2')
val charsSplit = "".split(*chars) // this is not possible
}
produces following error (same during the build and same in the official try kotlin repl)
Am I doing something wrong?
This happens because in Kotlin Array<Char> is equal to Character[] in Java, not to char[] in Java.
To use the spread operator on an array of characters and pass it to a vararg Char parameter, you need to use CharArray which is equal to char[] in Java.
fun main() {
val strings = arrayOf("one", "two")
val stringSplit = "".split("one", "two")
val stringsSplit = "".split(*strings)
val chars = charArrayOf('1', '2')
val charSplit = "".split('1', '2')
val charsSplit = "".split(*chars) // this is not possible
}

Map Key Values to Dataclass in Kotlin

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()...

Filter a substring in kotlin

In kotlin I'd like to filter a string and return a substring of only valid characters. Say we have valid characters,
valid = listOf('A', 'B', 'C')
How can I define a fcn in kotlin in the most succinct way to filter a string and only retain valid characters? For example,
'ABCDEBCA' --> 'ABCBCA'
'AEDC' --> 'AC'
Having trouble finding a canonical way to do this without resorting to using an array of string.
import kotlin.text.filter
class Test(){
val VALID = listOf("A", "B", "C")
fun filterString(expression: String): String{
expression.filter(x --> !VALID.contains(x)) #Doesn't work
}
}
The filter docs doesn't show any examples specifically for spring manipulation.
val VALID = setOf('A', 'B', 'C') // lookup in a set is O(1), whereas it's O(n) in a list. The set must contain Chars, not Strings
val expression = "ABCDEFEDCBA"
val filtered = expression.filter { VALID.contains(it) }
println(filtered)
// ABCCBA
Or
val VALID = setOf('A', 'B', 'C')
fun filterString(expression: String) = expression.filter { it in VALID }
fun main(args: Array<String>) {
val expression = "ABCDEFEDCBA"
val filtered = filterString(expression)
println(filtered)
// ABCCBA
}
In case you have a long set of chars you could join them in a String and convert it to a Set:
val VALID = "ABC".toSet()
fun filterString(expression: String) = expression.filter { it in VALID }
fun main(args: Array<String>) {
val expression = "ABCDEFEDCBA"
val filtered = filterString(expression)
println(filtered)
// ABCCBA
}