Sort list in kotlin by variable property given as parameter - kotlin

I know it's possible to sort a list of person by a property using following syntax:
persons = persons.sortedBy { person: Person-> person.name }
Now i want to write a sort function which accepts the property by which i want the list to be sorted by as an parameter.
In JavaScript i would write something like this:
fun sort(property: String) {
persons = persons.sortedBy { person: Person-> person[property]}
}
But i cannot write "person[property]" in Kotlin.
So how can i achieve such a sorting function in Kotlin?

Now i want to write a sort function which accepts the property by which i want the list to be sorted by as an parameter.
Let's hope that by "the property", you can settle for something like a function reference:
data class Person(val name: String, val age: Int)
var people = listOf(Person("Jane Doe", 25), Person("John Smith", 24), Person("Jen Jones", 37))
fun <R: Comparable<R>> sort(selector: (Person) -> R) {
people = people.sortedBy(selector)
}
fun main() {
println(people)
sort(Person::name)
println(people)
}
I could also use sort(Person::age) if I wanted to sort by age.
If your pseudocode is more literal, and you want a String parameter for the property name, you should start by asking yourself "why?". But, if you feel that you have a legitimate reason to use a String, one approach would be:
data class Person(val name: String, val age: Int)
var people = listOf(Person("Jane Doe", 25), Person("John Smith", 24), Person("Jen Jones", 37))
fun sort(property: String) {
people = when(property) {
"name" -> people.sortedBy(Person::name)
"age" -> people.sortedBy(Person::age)
else -> throw IllegalArgumentException("and this is why you should not be doing this")
}
}
fun main() {
println(people)
sort("name")
println(people)
}

class CustomObject(val customProperty: String, val value:Int)
val list = ArrayList<CustomObject>()
list.add(CustomObject("Z",5))
list.add(CustomObject("A",4))
list.add(CustomObject("B",1))
list.add(CustomObject("X",6))
list.add(CustomObject("Aa",7))
var alphabeticallySortedList = list.sortedWith(compareBy { it.customProperty })
var numberWiseSortedList = list.sortedWith(compareBy { it.value })

Related

kotlin data class constructors not getting picked up

I am creating a data class in kotlin as such
data class User(val name: String, val age: Int)
{
constructor(name: String, age: Int, size: String): this(name, age) {
}
}
In my main function, I can access the objects as such:
fun main(){
val x = User("foo", 5, "M")
println(x.name)
println(x.age)
println(x.size) // does not work
}
My problem is that I can't get access to size.
What I am trying to do is, create a data class where top level params are the common items that will be accessed, and in the constructors, have additional params that fit certain situations. The purpose is so that I can do something like
// something along the lines of
if (!haveSize()){
val person = User("foo", 5, "M")
} else {
val person = User("foo", 5)
}
}
Any ideas?
In Kotlin you do not need separate constructors for defining optional constructor params. You can define them all in a single constructor with default values or make them nullable, like this:
data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
val x = User("foo", 5, "L")
val y = User("foo", 5)
println(x.size) // "L" from call site
println(y.size) // "M" from default param
}
You can not access size variable, because this is from secondary construct, but we have alternative variant.
data class User(var name: String, var age: Int) {
var size: String
init {
size = "size"
}
constructor(name: String, age: Int, size: String) : this(name, age) {
this.size = size
}
}
In short, you want to have one property that can be one of a limited number of options. This could be solved using generics, or sealed inheritance.
Generics
Here I've added an interface, MountDetails, with a generic parameter, T. There's a single property, val c, which is of type T.
data class User(
val mountOptions: MountOptions,
val mountDetails: MountDetails<*>,
)
data class MountOptions(
val a: String,
val b: String
)
interface MountDetails<T : Any> {
val c: T
}
data class MountOneDetails(override val c: Int) : MountDetails<Int>
data class MountTwoDetails(override val c: String) : MountDetails<String>
Because the implementations MountDetails (MountOneDetails and MountTwoDetails) specify the type of T to be Int or String, val c can always be accessed.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
println(user.mountDetails)
}
fun main() {
val mt = MountOptions("foo", "bar")
val mountOneDetails = MountOneDetails(111)
anotherCaller(User(mt, mountOneDetails))
val mountTwoDetails = MountTwoDetails("mount two")
anotherCaller(User(mt, mountTwoDetails))
}
Output:
foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)
Generics have downsides though. If there are lots of generic parameters it's messy, and it can be difficult at runtime to determine the type of classes thanks to type-erasure.
Sealed inheritance
Since you only have a limited number of mount details, a much neater solution is sealed classes and interfaces.
data class User(val mountOptions: MountOptions)
sealed interface MountOptions {
val a: String
val b: String
}
data class MountOneOptions(
override val a: String,
override val b: String,
val integerData: Int,
) : MountOptions
data class MountTwoOptions(
override val a: String,
override val b: String,
val stringData: String,
) : MountOptions
The benefit here is that there's fewer classes, and the typings are more specific. It's also easy to add or remove an additional mount details, and any exhaustive when statements will cause a compiler error.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
// use an exhaustive when to determine the actual type
when (user.mountOptions) {
is MountOneOptions -> println(user.mountOptions.integerData)
is MountTwoOptions -> println(user.mountOptions.stringData)
// no need for an 'else' branch
}
}
fun main() {
val mountOne = MountOneOptions("foo", "bar", 111)
anotherCaller(User(mountOne))
val mountTwo = MountTwoOptions("foo", "bar", "mount two")
anotherCaller(User(mountTwo))
}
Output:
foo
bar
111
foo
bar
mount two
This is really the "default values" answer provided by Hubert Grzeskowiak adjusted to your example:
data class OneDetails(val c: Int)
data class TwoDetails(val c: String)
data class MountOptions(val a: String, val b: String)
data class User(
val mountOptions: MountOptions,
val detailsOne: OneDetails? = null,
val detailsTwo: TwoDetails? = null
)
fun main() {
fun anotherCaller(user: User) = println(user)
val mt = MountOptions("foo", "bar")
val one = OneDetails(1)
val two = TwoDetails("2")
val switch = "0"
when (switch) {
"0" -> anotherCaller(User(mt))
"1" -> anotherCaller(User(mt, detailsOne = one))
"2" -> anotherCaller(User(mt, detailsTwo = two))
"12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
else -> throw IllegalArgumentException(switch)
}
}

map a nested list to another nested list in Kotlin

I have a nested list of People : List<List<People.>>, where People has two attribute int age, String name.
I want to map it to a nested list of Student, Student also has two attribute int age, String name.
So the output is List<List<Student.>>.I have looked at examples of mapping a List to another, something like this:
fun List<People>.convert(): List<Student>{
return this.map {
Student(
age = this.age,
name = this.name
)
}
}
How to do it with a nested list? Thanks in advance.
map is one of Kotlin's collection transformation operations. That link explains how it works.
Let's fix your List<People>.convert() function first. Here's one way to write it:
data class Person(val name: String, val age: Int)
data class Student(val name: String, val age: Int)
fun List<Person>.convert(): List<Student> {
return this.map { person ->
Student(
age = person.age,
name = person.name,
)
}
}
Note that inside the mapping function, this does not refer to anything, which is why your original code doesn't compile.
Since the mapping function we're passing to map has only one parameter, we can skip declaring the parameter, and refer to the argument by the special name it instead, like this:
fun List<Person>.convert(): List<Student> {
return this.map { // it is Person
Student(
age = it.age,
name = it.name,
)
}
}
Then, to convert a List<List<Person>> to a List<List<Student>> we could write:
val listsOfPeople: List<List<Person>> = listOf(
listOf(Person("Alice", 27)),
listOf(Person("Bob", 23), Person("Clarissa", 44))
)
val listsOfStudents: List<List<Student>> = listsOfPeople.map { // it is List<Person>
it.convert()
}
Or, if you decide you don't need the convert function, you could write it like this:
val listsOfStudents: List<List<Student>> =
listsOfPeople.map { // it is List<Person>
it.map { // it is Person
Student(it.name, it.age)
}
}

Why class member property reflection in Kotlin?

In 'Kotlin in Action', it says "if a memberProperty refers to the age property of the Person class, memberProperty.get(person) is a way to dynamically get the value of person.age" with code(10.2.1 The Kotlin Reflection API):
class Peron(val name: String, val age: Int)
>> val person = Person("Alice", 29)
>> val memberProperty = Person::age
>> println(memberProperty.get(person))
29
I can't understand why this example refers to "dynamically" getting the value of property. It just works when I run this code:
println(person.age)
Is there any other case or example of member property reflection?
For example, say you want to write a function which prints all the properties of an object along with their values, this is how you can do that:
inline fun <reified T: Any> T.printProperties() {
T::class.memberProperties.forEach { property ->
println("${property.name} = ${property.get(this)}") // You can't use `this.property` here
}
}
Usage:
data class Person(val firstName: String, val lastName: String)
data class Student(val graduate: Boolean, val rollNumber: Int)
fun main() {
val person = Person("Johnny", "Depp")
val student = Student(false, 12345)
person.printProperties()
student.printProperties()
}
Output:
firstName = Johnny
lastName = Depp
graduate = false
rollNumber = 12345

Kotlin DSL automatically add/generate to MutableList

I've been struggling making DSL to work like this. I'd like to add items inside the lambda to the mutableList inside the persons. Can anybody help with this?
persons {
Person("name")
Person("name second")
}
the expected result after the lambda executed, all those item will be put inside the mutableList like this:
mutableListOf(Person("name"), Person("name second"))
Assuming that Person is a:
data class Person(val name: String)
Then the line Person("name") does nothing - it just desclares an unused instance. Person("name second") does the same (generally speaking, as it is the last line in lambda, it implicitly returned as the result of lambda expsession and theoretically, it could be handled later; anyway, that DSL syntax won't be working in general case).
You need not just declare instances, but also add them to list. So you need to declare some auxilary function (person for instance, to be close to desired syntax) which will do this under the hood:
class Persons {
val delegate: MutableList<Person> = mutableListOf()
fun person(name: String, block: Person.() -> Unit = {}) {
delegate.add(Person(name).also(block))
}
}
fun persons(block: Persons.() -> Unit) = Persons().also(block).delegate.toList() //mutation was needed only during construction, afterwards consider preserving immutability
Usage:
val persons: List<Person> = persons {
person("name")
person("name second")
}
Not quite exactly as you have but should be able to use something like
data class Person(var name: String? = null)
class Persons : ArrayList<Person>() {
fun person(block: Person.() -> Unit) {
val person = Person().apply(block)
add(person)
}
}
fun persons(block : Persons.() -> Unit): Persons = Persons().apply(block)
fun main() {
val personList = persons {
person {
name = "John"
}
person {
name = "Jane"
}
}
println(personList)
}
(This could be expanded then to use some kind of builder pattern to allow use of immutable vals in the data class)

What is the easiest way to print a Kotlin data class as compilable code?

I'd like to be able to turn an instance of a fairly simple Kotlin data class into a String that could be copy and pasted into a Kotlin file and would compile.
For example, given these data classes:
data class Parent(val name: String, val age: Int, val children: Set<Child>)
data class Child(val name: String, val age: Int)
I would like a function from any data class to String such that:
toCompilableString(
Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))
)
would return
"""Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))"""
Does such a thing exist?
We can override the behaviour of toString to output in the desired format:
fun main() {
var amy = Child(name="Amy",age=4)
var bob = Child(name="Bob",age=7)
var joe = Parent(name="Joe", age=34, children=setOf(amy, bob))
print(joe) // outputs "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))"
}
data class Parent(val name: String, val age: Int, val children: Set<Child>) {
override fun toString() = "Parent(\"$name\", $age, setOf(${children.joinToString()})"
}
data class Child(val name: String, val age: Int) {
override fun toString() = "Child(\"$name\", $age)"
}
With the help of joinToString(), this will output in the format "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))".
If you really love pain, there are reflection tools made specially for such things. I made a small function that will generate what you need:
fun dataClassToString(instance: Any) {
val sb = StringBuilder()
sb.append("data class ${instance::class.qualifiedName} (")
var prefix = ""
instance::class.memberProperties.forEach{
sb.append(prefix)
prefix = ","
sb.append("${it.name} = ${it.getter.call(instance)}")
}
sb.append(")")
println(sb.toString())
}
The only problem with this function is that for your parent class it generates the following:
data class Parent (age = 34,children = [Child(name=Amy, age=4), Child(name=Bob, age=7)],name = Joe)
Internally set is represented as array, however if you know for sure that you will have only sets or arrays, you can easily check what type it is and append that type when creating the set. You can also check if this is a data class and append it instead of hardcoded string.