Filter a substring in kotlin - 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
}

Related

How can I make a generic function that works for all subclasses of a collection and at the same time accepts the parameters correctly?

// Generic Function but not work as expecting
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
fun main() {
val originalList: List<String> = readln().split(" ")
val originalSet: Set<String> = originalList.toSet()
val word: String = readln()
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
// Incorrect: Int and String are different types
val dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
}
Is the question about the fact that the following line compiles?
listOf(1, 2, 3).dropElements(word)
If so, then what the compiler is doing is inferring these types:
listOf(1, 2, 3).dropElements<List<Int>, Any>(word)
This is possible because the type parameter in List is covariant, i.e. it is defined as List<out E>. This means that a List<Int> is also a List<Any>.
Doc about generics and variance here.
Your function is working as I would expect.
I think you are expected the integers in dropElements3 to reduce with word, but the problem is that readln() is returning a String, so an integer is not matching the String representation of the same Here is your original code (using kotest library to assert the answers)
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
class ATest {
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
#Test
fun main() {
val originalList: List<String> = listOf("fish","dog","cat","bird")
val originalSet: Set<String> = originalList.toSet()
var word = "cat"
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
dropElements1 shouldBe listOf("fish","dog","bird")
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
dropElements2 shouldBe listOf("fish","dog","bird")
var dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3)
word = "2"
dropElements3 = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3) // integer 2 != "2" no dropped elements
var word2 = 2 // now this is an integer
dropElements3 = listOf(1, 2, 3).dropElements(word2).also(::println)
dropElements3 shouldBe listOf(1, 3)
}
}
The List filter and Set - operations are removing an object based on a equality test on members (and hashcode too for the Set). How can Kotlin/Java know you want to treat integers as like Strings?
The only way you can solve this is to decide how to transform integers into strings (or visa versa). Of course there are multiple string representations of integers - decimal, hexadecimal, and so on...
I think T is a covariant type parameter with the upper bound T: comparable is a great deal!
data class Koko(val name: String) : Comparable<Koko> {
override fun compareTo(other: Koko) = name.compareTo(other.name)
}
inline fun <reified C : Iterable<T>, reified T : Comparable<T>> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> (this.filter { it != word }).toList<T>() as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
fun main() {
val list: List<String> = listOf("apple", "banana", "orange")
val set: Set<String> = list.toSet()
val mutableList: MutableList<String> = list.toMutableList()
val listOfKoko: List<Koko> = List<Koko>(5) { Koko("Name$it") }
val mapOfKoko: Map<Int, String> = list.withIndex().associate { it.index to it.value }
val dropElements1: List<String> = list.dropElements("apple").also(::println)
val dropElements2: Set<String> = set.dropElements("apple").also(::println)
val dropElements3: List<Koko> = listOfKoko.dropElements(Koko("Name1")).also(::println)
val dropElements4: MutableList<String> = mutableList.dropElements("apple").also(::println)
// Incorrect: different types ot not is Iterable
val dropElements5: List<String> = list.dropElements(1).also(::println)
val dropElements6: List<Int> = listOf(1, 2, 3).dropElements("apple").also(::println)
val dropElements7: Map<Int, String> = mapOfKoko.dropElements(Koko("Name1")).also(::println)
}

keeping track of injected variables when using string interpolation in 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]

kotlin string helpers to find index in a string where any element of another string matches first/last etc

C++ has string functions like find_first_of(), find_first_not_of(), find_last_of(),
find_last_not_of(). e.g. if I write
string s {"abcdefghi"};
s.find_last_of("eaio") returns the index of i
s.find_first_of("eaio") returns the index of a
s.find_first_not_of("eaio") returns the index of b
Does Kotlin has any equivalent.
Kotlin doesn't have these exact functions, but they are all special cases of indexOfFirst and indexOfLast:
fun CharSequence.findFirstOf(chars: CharSequence) = indexOfFirst { it in chars }
fun CharSequence.findLastOf(chars: CharSequence) = indexOfLast { it in chars }
fun CharSequence.findFirstNotOf(chars: CharSequence) = indexOfFirst { it !in chars }
fun CharSequence.findLastNotOf(chars: CharSequence) = indexOfLast { it !in chars }
These will return -1 if nothing is found.
Usage:
val s = "abcdefghi"
val chars = "aeiou"
println(s.findFirstOf(chars))
println(s.findFirstNotOf(chars))
println(s.findLastOf(chars))
println(s.findLastNotOf(chars))
Output:
0
1
8
7

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
}

Combining/merging data classes in Kotlin

Is there a way to merge kotlin data classes without specifying all the properties?
data class MyDataClass(val prop1: String, val prop2: Int, ...//many props)
with a function with the following signature:
fun merge(left: MyDataClass, right: MyDataClass): MyDataClass
where this function checks each property on both classes and where they are different uses the left parameter to create a new MyDataClass.
Is this possible possible using kotlin-reflect, or some other means?
EDIT: more clarity
Here is a better description of what i want to be able to do
data class Bob(
val name: String?,
val age: Int?,
val remoteId: String?,
val id: String)
#Test
fun bob(){
val original = Bob(id = "local_id", name = null, age = null, remoteId = null)
val withName = original.copy(name = "Ben")
val withAge = original.copy(age = 1)
val withRemoteId = original.copy(remoteId = "remote_id")
//TODO: merge without accessing all properties
// val result =
assertThat(result).isEqualTo(Bob(id = "local_id", name = "Ben", age=1, remoteId = "remote_id"))
}
If you want to copy values from the right when values in the left are null then you can do the following:
inline infix fun <reified T : Any> T.merge(other: T): T {
val propertiesByName = T::class.declaredMemberProperties.associateBy { it.name }
val primaryConstructor = T::class.primaryConstructor
?: throw IllegalArgumentException("merge type must have a primary constructor")
val args = primaryConstructor.parameters.associateWith { parameter ->
val property = propertiesByName[parameter.name]
?: throw IllegalStateException("no declared member property found with name '${parameter.name}'")
(property.get(this) ?: property.get(other))
}
return primaryConstructor.callBy(args)
}
Usage:
data class MyDataClass(val prop1: String?, val prop2: Int?)
val a = MyDataClass(null, 1)
val b = MyDataClass("b", 2)
val c = a merge b // MyDataClass(prop1=b, prop2=1)
A class-specific way to combine data classes when we can define the fields we want to combine would be:
data class SomeData(val dataA: Int?, val dataB: String?, val dataC: Boolean?) {
fun combine(newData: SomeData): SomeData {
//Let values of new data replace corresponding values of this instance, otherwise fall back on the current values.
return this.copy(dataA = newData.dataA ?: dataA,
dataB = newData.dataB ?: dataB,
dataC = newData.dataC ?: dataC)
}
}
#mfulton26's solution merges properties that are part of primary constructor only. I have extended that to support all properties
inline infix fun <reified T : Any> T.merge(other: T): T {
val nameToProperty = T::class.declaredMemberProperties.associateBy { it.name }
val primaryConstructor = T::class.primaryConstructor!!
val args = primaryConstructor.parameters.associate { parameter ->
val property = nameToProperty[parameter.name]!!
parameter to (property.get(other) ?: property.get(this))
}
val mergedObject = primaryConstructor.callBy(args)
nameToProperty.values.forEach { it ->
run {
val property = it as KMutableProperty<*>
val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
property.javaSetter!!.invoke(mergedObject, value)
}
}
return mergedObject
}
Your requirements are exactly the same as copying the left value:
fun merge(left: MyDataClass, right: MyDataClass) = left.copy()
Perhaps one of use isn't properly understanding the other. Please elaborate if this isn't what you want.
Note that since right isn't used, you could make it a vararg and "merge" as many as you like :)
fun merge(left: MyDataClass, vararg right: MyDataClass) = left.copy()
val totallyNewData = merge(data1, data2, data3, data4, ...)
EDIT
Classes in Kotlin don't keep track of their deltas. Think of what you get as you're going through this process. After the first change you have
current = Bob("Ben", null, null, "local_id")
next = Bob(null, 1, null, "local_id")
How is it supposed to know that you want next to apply the change to age but not name? If you're just updating based on nullability,
#mfulton has a good answer. Otherwise you need to provide the information yourself.
infix fun <T : Any> T.merge(mapping: KProperty1<T, *>.() -> Any?): T {
//data class always has primary constructor ---v
val constructor = this::class.primaryConstructor!!
//calculate the property order
val order = constructor.parameters.mapIndexed { index, it -> it.name to index }
.associate { it };
// merge properties
#Suppress("UNCHECKED_CAST")
val merged = (this::class as KClass<T>).declaredMemberProperties
.sortedWith(compareBy{ order[it.name]})
.map { it.mapping() }
.toTypedArray()
return constructor.call(*merged);
}
Edit
infix fun <T : Any> T.merge(right: T): T {
val left = this;
return left merge mapping# {
// v--- implement your own merge strategy
return#mapping this.get(left) ?: this.get(right);
};
}
Example
val original = Bob(id = "local_id", name = null, age = null, remoteId = null)
val withName = original.copy(name = "Ben")
val withAge = original.copy(age = 1)
val withRemoteId = original.copy(remoteId = "remote_id")
val result = withName merge withAge merge withRemoteId;