map a nested list to another nested list in Kotlin - 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)
}
}

Related

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)

How to map list of Triple into data class with nested list in kotlin

Sample
val listTriple = listOf<Triple<Int, Int, String>>()
data class Sample(val parentId: Int, val listItem : List<Item>)
data class Item(val id: Int, val name: String)
how to map listTriple into listOf Sample in kotlin in the best way
You can express that really concise by specifying groupBys valueTransform lambda:
val samples = listTriple.groupBy({ it.first }, { Item(it.second, it.third) }).map {
Sample(it.key, it.value)
}
But as EpicPandaForce mentioned in the comments it would be better to create a dedidacted class instead of using Triple. Having to refer to properties by first, second, third makes it hard to read.
Of course I could've just destructuring syntax here as well, but that doesn't solve the problem of not having a dedicated class.
Try this:
val samples = list
.groupBy { it.first }
.map {
val items = it.value.map { Item(it.second, it.third) }
Sample(it.key, items)
}
Assuming that the first Int of your triple is the parentId and the second one is the id, you would do this:
val listOfSample = listTriple.map {(parentId, id, name) ->
Sample(parentId, listOf(Item(id, name)))
}
If they are in the other order, just change the order in the '.map{}'
You can do groupBy for the secondary question of how to concatenate all lists based on the same parent id
val listOfSample = listTriple.map {(parentId, id, name) ->
Sample(parentId, listOf(Item(id, name)))
}.groupBy { it.parentId }

Sort list in kotlin by variable property given as parameter

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 })

Is there a way to generate kotlin dsl using data

We are using kotlin dsl to as a user friendly builder to take input and generate data. Is there a way to do the opposite of that ?
ie, convert existing data into dsl ?
Can this kotlin representation be converted to dsl ?
val person = Person("John", 25)
val person = person {
name = "John"
age = 25
}
Unless you're really crazy about { and some commas, below is an absolutely valid Kotlin code:
data class Person(
val name: String,
val age: Int
)
val person = Person(
name = "John",
age = 25
)
I seems really close to what you want and comes out-of-the-box.
Of course, you can achieve the syntax you want by writing some extra code, like:
import kotlin.properties.Delegates
data class Person(
val name: String,
val age: Int
)
class PersonDSL{
lateinit var name: String
var age: Int by Delegates.notNull<Int>()
fun toPerson(): Person = Person(this.name, this.age)
}
fun person(config: PersonDSL.() -> Unit): Person{
val dsl = PersonDSL()
dsl.config()
return dsl.toPerson()
}
fun main(){
val person = person {
name = "John"
age = 25
}
println(person) // Person(name=John, age=25)
}
But why do that?

Re-use mapping code for immutable data class in Kotlin

Updated: added some clarifications from the comments
I would like to use the same 'mapping' code for the primary constructor and copy() method of an immutable data class. How can I do this without creating an empty object first, and then using copy() on it?
The issue with how it is now is that if I add a new attribute with default value to Employee and EmployeeForm it would be easy to only add it in one of the two mapping functions and forget about the other (toEmployeeNotReusable / copyEmployee).
These are the data classes I'd like to map between:
#Entity
data class Employee(
val firstName: String,
val lastName: String,
val jobType: Int,
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
private val _absences: MutableSet<Absence> = mutableSetOf(),
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0 // prevents #Joffrey's answer from working
) {
init {
_absences.forEach { it.employee = this }
}
val absences get() = _absences.toSet()
fun addAbsence(newAbsence: Absence) {
newAbsence.employee = this
_absences += newAbsence
}
#Entity
#Table(name = "absence")
data class Absence(
// ... omitted fields
) {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
lateinit var employee: Employee
}
}
data class EmployeeForm(
var firstName: String = "",
var lastName: String = "",
var jobType: Int = 0
) {
// not reusable
fun toEmployeeNotReusable(): Employee {
return Employee(firstName, lastName, jobType)
}
// works but hacky
fun toEmployee(): Employee {
return copyEmployee(Employee("", "", 0))
}
fun copyEmployee(employee: Employee): Employee {
return employee.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
}
While mutability would be fine, in my case, I'd be interested to know how this would be possible.
One way to avoid listing the attributes 4 times would be to declare Employee as an interface instead, and use the "mutable" version, the form, as the only data class implementing it. You would have the "read-only" view using the interface, but you would technically only use the mutable instance behind the scenes.
This would follow what Kotlin designers have done for List vs MutableList.
interface Employee {
val firstName: String
val lastName: String
val jobType: Int
}
data class EmployeeForm(
override var firstName: String = "",
override var lastName: String = "",
override var jobType: Int = 0
): Employee {
fun toEmployee(): Employee = this.copy()
fun copyEmployee(employee: Employee): Employee = this.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
However, this implies that the form has all fields of an employee, which you probably don't want.
Also, I would personally prefer what you had done in the beginning, listing twice the field would not be a problem, just write tests for your functions, and when you want to add functionality, you'll add tests for that functionality anyway.
You should be able to do this using reflection: check list of properties in Employee and EmployeeForm, call the constructor by the matching names (using callBy to handle default parameters). The drawback, of course, is that you won't get compile-time errors if any properties are missing (but for this case, any test would probably fail and tell you about the problem).
Approximate and untested (don't forget to add the kotlin-reflect dependency):
inline fun <reified T> copy(x: Any): T {
val construct = T::class.primaryConstructor
val props = x::class.memberProperties.associate {
// assumes all properties on x are valid params for the constructor
Pair(construct.findParameterByName(it.name)!!,
it.call(x))
}
return construct.callBy(props)
}
// in EmployeeForm
fun toEmployee() = copy<Employee>(this)
You can make an equivalent which is compile-time checked with Scala macros, but I don't think it's possible in Kotlin.