Return Type: Calling a function inside Vs outside `println()` - kotlin

I'm new to Kotlin.
I was experimenting with Anonymous functions a little bit until facing different outputs for the same concept using different approaches.
First: Creating my Anonymous function:
var greetingFunction = { playerName: String , numBuildings: Int ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (Copyright $currentYear)\n"
}
Second: Creating a function that takes another function as a parameter:
private fun runSimulation(playerName: String, greetingFunc: (String, Int) -> String){
val numOfBuildings = (1..3).shuffled().last()
println(greetingFunc(playerName, numOfBuildings))
}
A- Regular call for Anonymous function:
println(runSimulation("Ahmed", greetingFunction))
Output:
Adding 3 houses
Welcome to SimVillage, Ahmed! (Copyright 2022)
B- Shorthand call for Anonymous function:
println(runSimulation("Different") { playerName: String , numBuildings: Int ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (Copyright $currentYear)\n"
})
Output:
Adding 2 houses
Welcome to SimVillage, Different! (Copyright 2022)
kotlin.Unit
I tried to remove the println() and calling the runSimulation function directly and the output was:
Output:
Adding 2 houses
Welcome to SimVillage, Different! (Copyright 2022)
What I really want to know is: how in the first place did I get that "kotlin.Unit" print using the Shorthand Syntax?

Kotlin will automatically infer the type of lambda expressions. Since the last line of greetingFunction is a String
var greetingFunction = { playerName: String , numBuildings: Int ->
val currentYear = 2022
println("Adding $numBuildings houses")
// the last line is a string -> Kotlin will infer that the return type is a String
"Welcome to SimVillage, $playerName! (Copyright $currentYear)\n"
}
the inferred type is
var greetingFunction: (String, String) -> String
Return types for block-body functions are not inferred.
If a function does not return a useful value, its return type is Unit. The function
private fun runSimulation(playerName: String, greetingFunc: (String, Int) -> String) {
val numOfBuildings = (1..3).shuffled().last()
println(greetingFunc(playerName, numOfBuildings))
}
will therefore return Unit, so
println(runSimulation("foo") { _, _ -> "bar" })
will print the returned value of runSimulation(), and Unit.toString() is kotlin.Unit
Since runSimulation() will also print to stdout, this is effectively the same as running
println(println("bar"))
First the 'inner' println() will output bar, and then the 'outer' println() will print kotlin.Unit.
bar
kotlin.Unit

Related

Kotlin Generic problem, UNCHECKED_CAST , required:Nothing

#file:Suppress("UNCHECKED_CAST")
data class Element<T>(
val key: String,
val valueOne: T,
val valueTwo: T,
val comparator: Comparator<T>,
val comparatorValue: CompareResult
)
enum class CompareResult(
val value: Int
) {
LESS(-1),
EQUAL(0),
GREATER_THAN(1)
}
fun <T> matchesComparison(list:Collection<Element<T>>): Pair<Boolean, List<String>> {
val failedComparisons = mutableListOf<String>()
for (element in list) {
val compareValue = element.comparator.compare(element.valueOne, element.valueTwo)
if (element.comparatorValue.value != compareValue) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
val elementsToCompare = listOf(
Element("number", 1, 2, intComparator, CompareResult.LESS),
Element("first name", "a", "a", stringComparator, CompareResult.EQUAL),
Element("last name", "a", "b", stringComparator, CompareResult.EQUAL)
)
matchesComparison(elementsToCompare).second.joinToString(", ","Failed elements: \"","\"")
I often get faced with comparing two different object properties with the same values.
As an example object A has props number,firstname,lastname. What i want to do is create a list have and have a function which goes over these Elements and returns which props have failed the comparison. I've managed to use generics for both the object and the matchesComparison function which returns the failed comparisons. The problem begins when i want to pass this list which is of type Collection<Element<out Any>> to this function is i get a type missmatch. instead of using unchecked casts to force the Comparator to be of type Any i would like to do this
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
The result value that of the script above should be Failed elements: "last name"
I tried changing the signature of the function to out any but then the comparator.compare method has both params as of type Nothing. I really want to avoid unsing unchecked casts.
matchesComparison() doesn't need to be generic in this case. It doesn't really care what is the type of the whole input collection, so we can simply use * here.
Then we have another problem. The compiler isn't smart enough to notice that while we perform operations on a single element, all its properties are of matching types. As a result, it doesn't allow to use element.comparator on element.valueOne and element.valueTwo. To fix this problem, we simply need to create a separate function which works on a single Element, so it understand the type for all properties is the same:
fun matchesComparison(list:Collection<Element<*>>): Pair<Boolean, List<String>> {
fun <T> Element<T>.matches() = comparatorValue.value == comparator.compare(valueOne, valueTwo)
val failedComparisons = mutableListOf<String>()
for (element in list) {
if (!element.matches()) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
Also, I believe such matches() function should be actually a member function of Element. It seems strange that while Element is pretty independent and it contains everything that is needed to perform a comparison, it still requires to use external code for this. If it would have a matches() function then we wouldn't need to care about its T. matches() would work with any Element.

Unable to replace string inside a String in 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.

How can I loop over an array of sets/maps with varying data types

I want to make it so that I keep my code dry and create 3 (or more, or less) buttons with somewhat the same structure. So I create a list of objects to loop over and put the data inside the object to use in several places in the AppButton.
I might think a bit too Pythonic, because that's my main language and I only recently started using Kotlin. What I normally do in Python:
app_buttons = [
dict(
text="....",
icon="....",
uri_string="....",
),
...
]
I've tried something similar in Kotlin with mapOf:
val appButtons = arrayOf(
mapOf(
"title" to getString(R.string.app_btn_example1),
"icon" to R.drawable.ic_some_icon_1_64,
"uriString" to "myapp://example1",
),
...
)
and then loop over them and getting from the map:
for (entry in appButtons) {
buttons.add(
AppButton(
entry.get("text"),
entry.get("icon"),
) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(entry.get("uriString"))).apply {
val name = getString(R.string.saved_account_key)
putExtra(name, sharedPref.getString(name, null))
}
startActivity(intent)
}
)
}
But then I get Type mismatch. Required String. Found {Comparable & java.io.Serializable}?. I don't know what types to put where...
Ok different approach, using setOf and destructuring:
val appButtons = arrayOf(
setOf(
getString(R.string.app_btn_example1),
R.drawable.ic_some_icon_1_64,
"myapp://example1",
),
...
)
for ((text, icon, uriString) in appButtons) {
buttons.add(
AppButton(
text,
icon
) {
...
}
)
}
But now I get the following:
Destructuring declaration initializer of type Set<{Comparable<*> & java.io.Serializable}> must have a 'component1()' function
Destructuring declaration initializer of type Set<{Comparable<*> & java.io.Serializable}> must have a 'component2()' function
Destructuring declaration initializer of type Set<{Comparable<*> & java.io.Serializable}> must have a 'component3()' function
How do I make this work? How do I create a basic list of objects and loop over them with the correct types? It feels so simple in Python. I'm clearly missing something.
Rather than using maps, you should create a data class. For example:
data class ButtonModel(
val title: String,
val icon: Int,
val uriString: String,
)
You can then create the array like this:
val appButtons = arrayOf(
ButtonModel(
title = getString(R.string.app_btn_example1),
icon = R.drawable.ic_some_icon_1_64,
uriString = "myapp://example1",
),
...
)
Or without the parameter labels if you prefer:
val appButtons = arrayOf(
ButtonModel(
getString(R.string.app_btn_example1),
R.drawable.ic_some_icon_1_64,
"myapp://example1",
),
...
)
Then, rather than getting them with get or [], you can just use the dot syntax:
buttons.add(
AppButton(
entry.text,
entry.icon,
) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(entry.uriString)).apply {
val name = getString(R.string.saved_account_key)
putExtra(name, sharedPref.getString(name, null))
}
startActivity(intent)
}
)

Kotlin - creating map with 3 arrays using fold not working

I have an array of customers, each customer has properties id, uuid and subCustomer and other properties that I am not interested in. I would like to do one iteration, where I would create 3 arrays where one would hold ids, other uuids and third subcustomers only.
I have tried to achieve this by using fold function like this:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
acc["ids"]?.plus(customer["id"])
acc["uuids"]?.plus(customer["uuid"])
acc["subCustomers"]?.plus(customer["subCustomer"])
}
With this code I get an error in editor:
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
List<Any?>?
I have tried this as well:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer["id"]),
"uuids" to acc["uuids"]?.plus(customer["uuid"]),
"subCustomers" to acc["subCustomers"]?.plus(customer["subCustomer"])
)
}
But, I get this errors:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<Any?>?
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
Map<String, List<Any?>?>
Write two data classes for your data. One for your customers, and one for the three lists that you want:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class CustomerDataLists(
val ids: MutableList<String> = mutableListOf(),
val uuids: MutableList<UUID> = mutableListOf(),
val subCustomers: MutableList<String> = mutableListOf(),
)
Then, just use a simple for loop to add the data in:
val dataLists = CustomerDataLists()
for (customer in customers) {
dataLists.ids.add(customer.id)
dataLists.uuids.add(customer.uuid)
dataLists.subCustomers.add(customer.subCustomer)
}
// now dataLists is filled with customers' data
#Sweeper's answer is nice. I believe in any case it's worth using data classes instead of maps for this kind of use case.
Since you don't really have any interactions between the 3 lists in the fold, you could also build those lists independently (but it's 3 iterations of course here):
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class AggregatedCustomers(
val ids: List<String>,
val uuids: List<UUID>,
val subCustomers: List<String>,
)
val customers: List<Customer> = TODO("get that list from somewhere")
val aggregated = AggregatedCustomers(
ids = customers.map { it.id }
uuids = customers.map { it.uuid }
subCustomers = customers.map { it.subCustomer }
)
This answer contiains 3 parts:
A better way to solve such problem;
Why the original code doesn't work;
Other problems need to pay attention.
1. A better way to solve such problem
Let's assume that the Consumer mentioned looks like this:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
It's really not necessary to use function fold in such occasion. For loop or extension function forEach is merely enough:
val customers: List<Customer> = listOf(
Customer("1", UUID.randomUUID(), "sub-1"),
Customer("2", UUID.randomUUID(), "sub-2"),
Customer("3", UUID.randomUUID(), "sub-3"),
)
val ids = mutableListOf<String>() // pay attention. use `mutableListOf` instead of `listOf()`
val uuids = mutableListOf<UUID>()
val subConsumers = mutableListOf<String>()
customers.forEach {
ids += it.id
uuids += it.uuid
subConsumers += it.subCustomer
}
2. Why the original code doesn't work
The proposed two pieces of code are in the same pattern:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
) { acc, customer ->
// ... do something with acc and customer
}
We should first make it clear that the last statement in the fold scope is the expression to be accumulated. It's like an acc_n <combine> customer -> acc_(n+1), for each customer in customers each time, where <combine> is where we write our logic. So the first proposed piece of code doesn't work because you might not be aware that something should be returned while writing:
customers.fold(...){ acc, customer ->
acc["ids"]?.plus(customer.id)
acc["uuids"]?.plus(customer.uuid)
acc["subCustomers"]?.plus(customer.subCustomer)
}
In fact, the last statement acc["subCustomers"]?.plus(...) is an expression with type List<Any>?, kotlin regard it as your "acc_(n+1)", but you propose mapOf("ids" to ...) as acc_0, which has type Map<String, ...>: not the same type as List<Any>?. And that's why you got the first error:
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
List<{Comparable{String & UUID}> & java.io.Serializable}>?
We'll talk about generic types later.
Let's move on the second piece of code. A map is proposed as the last expression in the scope of fold, which is also a map:
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id),
"uuids" to acc["uuids"]?.plus(customer.uuid),
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)
)
}
The simpliest way to eliminate error is using !! expression (not suggested!):
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id)!!,
"uuids" to acc["uuids"]?.plus(customer.uuid)!!,
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)!!
)
}
The reason is that kotlin cannot assert acc["ids"] is not null, that's why you use ?. for a null-safe method invoke. However such invoke make the return type nullable:
val cus: Customer? = Customer("1", UUID.randomUUID(), "sub-1") // cus has type Customer? : nullable
val id1: String = cus?.id // [compile error] Type mismatch. [Required: String] [Found: String?]
val id2: String? = cus?.id // OK
val id3: String = cus?.id!! // If `cus?.id` is null, throw NPE.
You've declare acc_0 (in bracket after fold) in type Map<String, List<T>> implicitly (we will talk about T later). Just know that T is not a nullable type), but a map with type Map<String, List<T>?> was found as acc_(n+1). Types mismatch and the error was shown:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<{Comparable{String & UUID}> & java.io.Serializable}>?
3. Other problem need to pay attention
An important problem is: What's the type of acc_0?
// acc_0:
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
Of course type of each expression on the left of to is String, and List<T> is the type of each expression on the right of it. so it must be Map<String, List<T>>. What about T? Kotlin try to find the nearest ancessor of String and UUID, and find them both implements Comparable<?> and Serializable, so that's what you see in the error. That's the type of T:
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
This may lead to some unwanted experience:
val map = mapOf(
"listA" to mutableListOf("233"),
"listB" to mutableListOf(UUID.randomUUID())
)
val listA = map["A"]!! // MutableList<out {Comparable{String & UUID}> & java.io.Serializable}!>
// generic type "collapse" into `Nothing` for no type can implement both Comparable<String> and Comparable<UUID>
listA.add(Any()) // Type mismatch. [Required: Nothing] [Found: Any]
So try not to put lists with different generic type into one map.
Another problem is, when you try to invoke acc["ids"]?.plus(customer.id), you are actually invoking such method (from kotlin _Collections.kt)
public operator fun <T> Collection<T>.plus(element: T): List<T> {
val result = ArrayList<T>(size + 1)
result.addAll(this)
result.add(element)
return result
}
A new list is created each time you invoke the method! Try use mutableListOf() in replace of listOf() for collections that you want to make changes, and use "+=" (or ?.plusAsign() as null-safe version) operator instead. This may leads to some other problem with the original code (which is too complex to explain why), but for the code in part 1: A better way to solve such problem, the += is actually invoking:
public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) {
this.add(element)
}
which just add value to list without create new ones.

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 = " ")