Invoke Methods dynamically using Method.invoke() with array that has the Parameters - kotlin

I have below code which was supposed to call 2 methods concatenation(1, 2, mapOf("S" to "M")) and subtraction(5, 3):
private fun concatenation(a: Int, b: Int, c: Map<String, String>): String {
return a.plus(b).toString().plus(c.values.toString())
}
private fun subtraction(a: Int, b: Int): Int {
return a - b
}
#Test
fun test() {
val methods = listOf(
::concatenation,
::subtraction
)
val arguments = listOf(
arrayOf(1, 2, mapOf("S" to "M")),
arrayOf(5, 3)
)
val result = methods.zip(arguments).map { (method, args) ->
{
if (method.parameters.size == args.size) {
method.invoke(*args) //this line does not work
} else {
println("The number of arguments does not match the number of parameters in the target method")
}
}
}
println(result)
}
But I have error in method.invoke() because each method has different number of arguments.
How do I do this in Kotlin in an elegant manner?

The below code works if I change method.invoke() to method.call(). And remove private keyword for function or use the method.isAccessible = true.
fun concatenation(a: Int, b: Int, c: Map<String, String>): String {
return a.plus(b).toString().plus(c.values.joinToString())
}
fun subtraction(a: Int, b: Int): Int {
return a - b
}
#Test
fun test() {
val methods = listOf(
::concatenation,
::subtraction
)
val arguments = listOf(
arrayOf(1, 2, mapOf("S" to "M")),
arrayOf(5, 3)
)
val result = methods.zip(arguments).map { (method, args) ->
if (method.parameters.size == args.size ||
(method.parameters.size - 1 <= args.size && method.parameters.last().isVararg)
) {
//method.isAccessible = true
method.call(*args)
} else {
println("The number of arguments does not match the number of parameters in the target method")
}
}
println(result)
}

Related

How can I improve the verbose code when I use enum in Kotlin?

The are many items in a enum class , I found there ae many verbose code in the function fun getLevel.
How can I improve it?
Code A
enum class ELevel(private val labelId: Int, val alarmed: Boolean){
Normal(R.string.Normal,false),
Leaves(R.string.Leaves,false),
Whispering(R.string.Whispering,false),
Quiet(R.string.Quiet,false),
Refrigerator(R.string.Refrigerator,false),
Electric(R.string.Electric,false),
Washing(R.string.Washing,true),
Alarm(R.string.Alarm,true),
Subway(R.string.Subway ,true),
Factory(R.string.Factory,true),
Car(R.string.Car,true),
Ambulance(R.string.Ambulance,true);
fun getLabel(mContext: Context) = mContext.getString(labelId)
companion object {
fun getLevel(soundValue: Double): ELevel {
var temp = Normal
val i = soundValue.toInt()
if (i in 1..10) {
temp = Normal
}
if (i in 11..20) {
temp = Leaves
}
...
if (i in 101..110) {
temp = Car
}
if (i in 111..120) {
temp = Ambulance
}
return temp
}
}
In the same way that you have associates a labelId and alarmed flag with each of the enum constants, you can add an additional maxSoundLevel property:
enum class ELevel(
private val labelId: Int,
val alarmed: Boolean,
val maxSoundLevel: Int,
){
Normal(R.string.Normal,false, 10),
Leaves(R.string.Leaves,false, 20),
...
}
Then you can do:
companion object {
fun getLevel(soundValue: Double): ELevel =
// assuming the max sound levels are in ascending order
values().firstOrNull { soundValue.toInt() <= it.maxSoundLevel }
// if there is no match, throw exception. You can also just return the nullable ELevel
?: throw IllegalArgumentException("Unknown sound")
}
One way could be to create an abstract val range and define it for each enum. After that you could simply check and get the first enum that have your soundValue in his range.
enum class ELevel(private val labelId: Int, val alarmed: Boolean) {
Normal(R.string.Normal, false) {
override val range: IntRange = 1..10
},
Leaves(R.string.Leaves, false) {
override val range: IntRange = 11..20
},
Whispering(R.string.Whispering, false) {
override val range: IntRange = ...
},
Quiet(R.string.Quiet, false) {
override val range: IntRange = ...
},
Refrigerator(R.string.Refrigerator, false) {
override val range: IntRange = ...
},
Electric(R.string.Electric, false){
override val range: IntRange = ...
},
Washing(R.string.Washing, true){
override val range: IntRange = ...
},
Alarm(R.string.Alarm, true){
override val range: IntRange = ...
},
Subway(R.string.Subway, true){
override val range: IntRange = ...
},
Factory(R.string.Factory, true){
override val range: IntRange = ...
},
Car(R.string.Car, true) {
override val range: IntRange = 101..110
},
Ambulance(R.string.Ambulance, true) {
override val range: IntRange = 111..120
};
fun getLabel(mContext: Context) = mContext.getString(labelId)
abstract val range: IntRange
companion object {
fun getLevel(soundValue: Double): ELevel =
values().first { it.range.contains(soundValue.toInt()) }
}
}
If the IDs of the Strings are exact matches for the enum value names, you can also use identifier look-up to shorten your code a little bit more.
enum class ELevel(
val alarmed: Boolean,
private val maxSoundLevel: Int,
){
Normal(false, 10),
Leaves(false, 20),
//...
;
private var labelId = 0
fun getLabel(context: Context){
if (labelId == 0) {
labelId = context.resources.getIdentifier(name, "id", context.packageName)
}
return context.getString(labelId)
}
}

Data class toString name clash property / construction arg

Newby in Kotlin, I'm trying to implement a simple data class with constraint validation in fields.
This works great, but my solution is suboptimal since it exposes the private variables names defined in the class' definition in the toString representation, where I would prefer to have the properties:
data class MutablePointKt(private val _x: Int = 0, private val _y: Int = 0) {
private fun validatePositiveOrZero(some:Int) {
Validate.isTrue(some >= 0, "negative coordinate provided: $some")
}
var x: Int = 0
get() {
println(" > getting x: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
var y: Int = 0
get() {
println(" > getting y: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
init {
this.x = _x;
this.y = _y;
}
}
println(MutablePointKt(1, 2)) // prints "MutablePointKt(_x=1, _y=2)". how to print "MutablePointKt(x=1, y=2)" ?
Thank you !
EDIT:
I have a solution with
override fun toString(): String = ToStringBuilder.reflectionToString(this, KotlinToStringStyle()) and
class KotlinToStringStyle : ToStringStyle() {
private fun isFiltered(s: String?) = s?.startsWith("_") == true
override fun appendFieldStart(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldStart(buffer, fieldName)
}
override fun appendDetail(buffer: StringBuffer?, fieldName: String?, any: Any) {
if (!isFiltered(fieldName))
super.appendDetail(buffer, fieldName, any)
}
override fun appendFieldEnd(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldEnd(buffer, fieldName)
}
}
... but this is rather overkill, I would prefer a concise solution aka "the Kotlin way"

Is it possible to pass an argument into a sequence function?

I'm looking for a way to pass an argument into a Kotlin sequence function similar to how it works in JS:
function *gen () {
console.log(yield) // prints 1
console.log(yield) // prints 2
}
const it = gen()
it.next() // first iteration will execute the first yield and pause
it.next(1) // we pass 1 to the first yield which will be printed
it.next(2) // we pass 2 to the second yield which will be printed
Something like this in Kotlin:
fun main() {
val it = gen().iterator()
// Iterator#next() doesn't expect an argument
it.next(1)
it.next(2)
}
fun gen() = sequence {
println(yield(null)) // Would print 1
println(yield(null)) // Would print 2
}
Kotlin Sequences do not support passing arguments to each yield, but you have at least 2 ways to implement needed behaviour:
Using actors:
class NextQuery<A, T>(val arg: A, val next: CompletableDeferred<T> = CompletableDeferred())
fun test() = runBlocking {
val actor = GlobalScope.actor<NextQuery<String, Int>> {
for (nextQuery in channel) {
nextQuery.next.complete(nextQuery.arg.length)
}
}
val query1 = NextQuery<String, Int>("12345")
actor.send(query1)
println(query1.next.await())
val query2 = NextQuery<String, Int>("1234")
actor.send(query2)
println(query2.next.await())
}
Using channels:
class ArgSequenceScope<out A, in T>(
private val argChannel: ReceiveChannel<A>,
private val nextChannel: SendChannel<T>
) {
suspend fun yield(next: T) {
nextChannel.send(next)
}
suspend fun arg(): A = argChannel.receive()
}
class ArgSequence<in A, out T>(
private val argChannel: SendChannel<A>,
private val nextChannel: ReceiveChannel<T>
) {
suspend fun next(arg: A): T {
argChannel.send(arg)
return nextChannel.receive()
}
}
fun <A, T> sequenceWithArg(block: suspend ArgSequenceScope<A, T>.() -> Unit): ArgSequence<A, T> {
val argChannel = Channel<A>()
val nextChannel = Channel<T>()
val argSequenceScope = ArgSequenceScope(argChannel, nextChannel)
GlobalScope.launch {
argSequenceScope.block()
argChannel.close()
nextChannel.close()
}
return ArgSequence(argChannel, nextChannel)
}
fun test() {
val sequence = sequenceWithArg<String, Int> {
yield(arg().length)
yield(arg().length)
}
runBlocking {
println(sequence.next("12345"))
println(sequence.next("1234"))
}
}

Handling lists of two different types with same code using functional programming in kotlin

I have two lists with different types list1 and list2 . I have a method which does the same operation on the lists.
I'm using lambdas where I cannot access the property as (it.prop1) if I'm using List of type Any.
Is there any solution to avoid this issue with lambdas?
val list1: List<Student> = ..
val list2: List<Teacher> = ..
list1.filter {
school.contains(it.prop1) }
.forEach {
total += it.prop2.toLong()
}
list2.filter {
school.contains(it.prop1) }
.forEach {
total += it.prop2.toLong()
}
Thanks.
Try this:
object Test {
private fun isContains(school: Set<Int>, any: Any) = when (any) {
is Student -> school.contains(any.prop1)
is Teacher -> school.contains(any.prop1)
else -> false
}
private fun value(any: Any) = when (any) {
is Student -> any.prop2
is Teacher -> any.prop2
else -> throw NoWhenBranchMatchedException("any should be Student or Teacher")
}
#JvmStatic
fun main(args: Array<String>) {
val school = setOf(1, 2)
val list = listOf(Student(1, 1), Student(2, 2), Student(3, 3), Teacher(1, 1), Teacher(2, 2), Teacher(3, 3))
val total = list.filter {
isContains(school, it)
}.map {
value(it)
}.sum()
println("Total: $total")
}
private class Student(val prop1: Int, val prop2: Int)
private class Teacher(val prop1: Int, val prop2: Int)
}
You may use Type Checks and Casts
class Student(val prop1:Int, val prop2:Int)
class Teacher(val prop1:Int, val prop2:Int)
val list : List<Any> = listOf(Student(1,1),Student(2,2),Student(3,3),Teacher(1,1),Teacher(2,2),Teacher(3,3))
var total : Long = 0
val school : Array<Int> = arrayOf(1,2)
list.filter{
if(it is Student)
{
school.contains((it as Student).prop1)
}
else if(it is Teacher)
{
school.contains((it as Teacher).prop1)
}
else
{
false
}
}.forEach{
if(it is Student)
{
total += (it as Student).prop2.toLong()
}
else if(it is Teacher)
{
total += (it as Teacher).prop2.toLong()
}
}
println(total) //print 6 in this example
This is ugly tough. It is better to make Student and Teacher either inherit a common superclass or implement a common interface
As far as I know you can't. You can take advantage of common interface.
For example:
interface Human{
val age: Int
}
class Student(override val age: Int): Human
class Teacher(override val age: Int, val salary: Double):Human
fun x(){
val list1: List<Student> = ...
val list2: List<Teacher> = ...
val school: List<Human> = ...
val result = school
.filter { it is Student }
.sumBy { it.age}
val result2 = school
.filter { it is Teacher }
.sumBy { it.age }
}

Invoking Action by reference in Kotlin

I've a Map of (key, value) where the value is a predefined function.
I want to iterate the input param in the Mp and check where the key is matching with the input parameter, then invoke the equivalent function, something like this
My code required to be something like below:
fun fn1: Unit { // using Unit is optional
println("Hi there!")
}
fun fn2 {
println("Hi again!")
}
fun MainFun(x: int){
val map: HashMap<Int, String> = hashMapOf(1 to fn1, 2 to fn2)
for ((key, value) in map) {
// if key = x then run/invoke the function mapped with x, for example if x = 1 then invoke fn1
}
}
Notes: I read something like below, but could not know how to us them:
inline fun <K, V> Map<out K, V>.filter(
predicate: (Entry<K, V>) -> Boolean
): Map<K, V> (source)
val russianNames = arrayOf("Maksim", "Artem", "Sophia", "Maria", "Maksim")
val selectedName = russianNames
.filter { it.startsWith("m", ignoreCase = true) }
.sortedBy { it.length }
.firstOrNull()
Hi I hope this would help you.
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: IntArray){
val map = hashMapOf(
1 to ::fn1,
2 to ::fn2)
map.filterKeys { it == args[0] } // filters the map by comparing the first int arg passed and the key
.map { it.value.invoke() } // invoke the function that passed the filter.
}
If the keyis RegEx then map.filterKeys { Regex(it).matches(x) } can be used, below full example of it Try Kotlin:
data class Person(val name: String,
val age: Int? = null)
val persons = listOf(Person("Alice"),
Person("Bob", age = 23))
fun old() {
val oldest = persons.maxBy { it.age ?: 0 }
println("The oldest is: $oldest")
}
fun young() {
val youngest = persons.minBy { it.age ?: 0 }
println("The youngest is: $youngest")
}
fun selection(x: String) {
val map = mapOf(
"old|big" to ::old,
"new|young" to ::young)
map.filterKeys { Regex(it).matches(x) }
.map { it.value.invoke() }
}
fun main(args: Array<String>) {
selection("new")
}
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: Array<Int>){
val map = hashMapOf(1 to ::fn1, 2 to ::fn2)
map.forEach { key, function -> function.invoke() }
}
This will do the work but your code does not even have the correct syntax. You should learn the basic first.