I have a list of objects that looks like:
data class Classroom(
val id: Int,
val name: String,
val students: List<Student>
)
data class Student(
val id: Int,
val name: String,
val age: Int,
val gpa: Long,
)
I wanna have a hash map that maps Student.name -> Student of all the students that are in all of the classes (regardless of which student belongs to each class).
My input is a List of Classroom
How can i achieve that elegantly?
val studentMap: Map<String, Student> = classrooms
.flatMap { it.students }
.map { student -> student.name to student }
.toMap()
Use flatMap to extract all the students
map them to name/student pairs
Convert to a map
Here is the the setup and test code, that you should have provided in the question.
fun generateClassrooms() : List<Classroom> {
val classANames = listOf("foo", "bar", "baz")
val classBNames = listOf("baz", "asdf", "ghjk")
val classCNames = listOf("asdf", "ghjk", "bar", "qwerty")
val student = Student(0, "name", 0, 0)
val classAStudents = classANames.map { student.copy(name = it) }
val classBStudents = classBNames.map { student.copy(name = it) }
val classCStudents = classCNames.map { student.copy(name = it) }
val classroom = Classroom(0, "classroom", emptyList())
val classA = classroom.copy(students = classAStudents)
val classB = classroom.copy(students = classBStudents)
val classC = classroom.copy(students = classCStudents)
return listOf(classA, classB, classC)
}
fun main() {
val classrooms = generateClassrooms()
val studentMap: Map<String, Student> = classrooms
.flatMap { it.students }
.map { student -> student.name to student }
.toMap()
for (student in studentMap) println(student)
}
Running this produces:
foo=Student(id=0, name=foo, age=0, gpa=0)
bar=Student(id=0, name=bar, age=0, gpa=0)
baz=Student(id=0, name=baz, age=0, gpa=0)
asdf=Student(id=0, name=asdf, age=0, gpa=0)
ghjk=Student(id=0, name=ghjk, age=0, gpa=0)
qwerty=Student(id=0, name=qwerty, age=0, gpa=0)
Related
I have an mutable list in kotlin that looks like this:
val groceries = listOf(
Food("orange", "fruit"),
Food("potato", "vegetable"),
Food("banana", "fruit"),
Food("apple", "fruit"),
Food("cucumber", "vegetable"),
Food("salad", "vegetable")
)
i have defined a sealed class so that i can group this data:
sealed class RecyclerItem {
data class Food(val name: String): RecyclerItem()
data class Section(val title: String): RecyclerItem()
}
Where section would be things like "vegetable" or "fruit"
and food would be like "banana" or "apple"
etc etc
to group my data into a single list i have written this code using groupby:
val sectionedGroceries: List<RecyclerItem> = groceries
.groupBy { it.category }
.flatMap { (category, foods) ->
listOf<RecyclerItem>(RecyclerItem.Section(category)) + foods.map { RecyclerItem.Food(it.name) }
}
in the end if i print the list it looks like this:
[Section(title=fruit), Food(name=orange), Food(name=banana), Food(name=apple), Section(title=vegetable), Food(name=potato), Food(name=cucumber), Food(name=salad)]
but now my issue is if there is ONLY ONE Section in the list i want to remove it such that the list would have only Food in it. how can i do this most efficiently?
update sorry here is the full code:
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val groceries = listOf(
Food("orange", "fruit"),
Food("potato", "vegetable"),
Food("banana", "fruit"),
Food("apple", "fruit"),
Food("cucumber", "vegetable"),
Food("salad", "vegetable")
)
val sectionedGroceries: List<RecyclerItem> = groceries
.groupBy { it.category }
.flatMap { (category, foods) ->
listOf<RecyclerItem>(RecyclerItem.Section(category)) + foods }
Log.v("TAG","$sectionedGroceries")
}
}
sealed class RecyclerItem {
data class Food(val name: String, val category: String): RecyclerItem()
data class Section(val title: String): RecyclerItem()
}
val sectionedGroceries: List<RecyclerItem> = groceries
.groupBy { it.category }
.let {
if (it.size == 1) it.values.first()
else it.flatMap { (category, foods) ->
listOf(RecyclerItem.Section(category)) + foods
}
}
I have a mutableLIst:
var books = mutableListOf<Book>()
model "Book" is:
data class Book(val title: String, val id: Int)
My code is:
button2.setOnClickListener{
val delFragment = DelFragment()
val booksforDel = Bundle()
booksforDel.putStringArrayList("books", books as ArrayList<String>)
delFragment.setArguments(booksforDel)
val manager = supportFragmentManager
delFragment.show(manager,"Delete Book")
}
in Fragment I try to get data:
val booksForDelete = getArguments()?.getStringArrayList("books")!!
And get Error:
java.lang.ArrayStoreException: source[0] of type com.example.http_example.model.Book cannot be stored in destination array of type java.lang.String[]
How send a data from mutableList "books" to Bundle in DialogFragment?
You can implement Parcelable interface:
data class Book(val title: String, val id: Int) : Parcelable {
constructor(source: Parcel) : this(
source.readString()!!,
source.readInt()
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(title)
writeInt(id)
}
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Book> = object : Parcelable.Creator<Book> {
override fun createFromParcel(source: Parcel): Book = Book(source)
override fun newArray(size: Int): Array<Book?> = arrayOfNulls(size)
}
}
}
And use it like the following:
var books = mutableListOf<Book>()
val booksforDel = Bundle()
booksforDel.putParcelableArray("books", books.toTypedArray())
Ann to retrieve books in a Fragment:
val booksForDelete = arguments?.getParcelableArray("books")
How can I do a recursive / deep merge of two data classes in Kotlin? Something like this:
import kotlin.reflect.*
import kotlin.reflect.full.*
data class Address(
val street: String? = null,
val zip: String? = null
)
data class User(
val name: String? = null,
val age: Int? = null,
val address: Address? = null
)
inline 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]!!
val type = property.returnType.classifier as KClass<*>
if (type.isData) {
parameter to this.merge(other) //inline function can't be recursive
} else {
parameter to (property.get(other) ?: property.get(this))
}
}
return primaryConstructor.callBy(args)
}
val u1 = User(name = "Tiina", address = Address(street = "Hämeenkatu"))
val u2 = User(age = 23, address = Address(zip = "33100"))
u1.merge(u2)
// expected: User(age = 23, name= "Tiina", address = Address(zip = "33100", street = "Hämeenkatu")
related: Combining/merging data classes in Kotlin
There were several problems in the posted code,
unnecessary reification and inlining
when type isData was detected instead of merging the values of the property merge on this with the other was called, so it became endless recursion.
get cannot be used on KProperty1<out T, Any?> because of the variance
some non-idiomatic stuff which works, but can be made better
Here's the fixed version. For production I would've added some checks and error messages, but this should work for "happy path" and hopefully give you the base to build on:
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.primaryConstructor
data class Address(
val street: String? = null,
val zip: String? = null
)
data class User(
val name: String? = null,
val age: Int? = null,
val address: Address? = null,
val map: Map<String, Int>? = null
)
fun <T> mergeData(property: KProperty1<out T, Any?>, left: T, right: T): Any? {
val leftValue = property.getter.call(left)
val rightValue = property.getter.call(right)
return rightValue?.let {
if ((property.returnType.classifier as KClass<*>).isSubclassOf(Map::class)) (leftValue as? Map<*, *>)?.plus(it as Map<*, *>)
else leftValue?.merge(it)
} ?: rightValue ?: leftValue
}
fun <T> lastNonNull(property: KProperty1<out T, Any?>, left: T, right: T) =
property.getter.call(right) ?: property.getter.call(left)
fun <T : Any> T.merge(other: T): T {
val nameToProperty = this::class.declaredMemberProperties.associateBy { it.name }
val primaryConstructor = this::class.primaryConstructor!!
val args: Map<KParameter, Any?> = primaryConstructor.parameters.associateWith { parameter ->
val property = nameToProperty[parameter.name]!!
val type = property.returnType.classifier as KClass<*>
when {
type.isData || type.isSubclassOf(Map::class) -> mergeData(property, this, other)
else -> lastNonNull(property, this, other)
}
}
return primaryConstructor.callBy(args)
}
// verification
val u1 = User(name = "Tiina", address = Address(street = "Hämeenkatu"), map = mapOf("a" to 1))
val u2 = User(age = 23, address = Address(zip = "33100"), map = mapOf("b" to 2))
check(
u1.merge(u2) == User(
age = 23,
name = "Tiina",
address = Address(zip = "33100", street = "Hämeenkatu"),
map = mapOf("a" to 1,"b" to 2)
)
) {
"doesn't work"
}
println("Works!")
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 }
}
This is a simple data structure House which contains a (nullable) list of Person :
data class Person(val name: String,
val militaryYear: Int? = null,
val firstPregnantYear: Int? = null)
data class House(val persons: List<Person>?)
Person maybe male or female. Male has militaryYear value , Female has firstPregnantYear value.
I want to write a DSL to build the House object , like this :
val house = house {
person("John") {
militaryYear = 2003
}
person("Anne") {
firstPregnantYear = 2010
}
}
This is what I have done :
data class Person(val name: String,
val militaryYear: Int? = null,
val firstPregnantYear: Int? = null) {
class Builder(private val name: String) {
var militaryYear : Int? = null
var firstPregnantYear : Int? = null
fun build(): Person = Person(name, militaryYear, firstPregnantYear)
}
}
fun person(name: String , block: Person.Builder.() -> Unit) = Person.Builder(name).apply(block).build()
data class House(val persons: List<Person>?) {
class Builder {
var persons = mutableListOf<Person>()
fun person(name: String , block: Person.Builder.() -> Unit) {
persons.add(Person.Builder(name).apply(block).build())
}
fun build(): House = House(persons = persons.takeIf { it.isNotEmpty() })
}
}
It seems works , but it doesn't forbid both militaryYear & firstPregnantYear , for example :
val house = house {
person("John") {
militaryYear = 2003
}
person("Anne") {
firstPregnantYear = 2010
}
person("Alien") {
militaryYear = 2005
firstPregnantYear = 2009
}
}
println(house)
The result :
House(persons=[
Person(name=John, militaryYear=2003, firstPregnantYear=null)
, Person(name=Anne, militaryYear=null, firstPregnantYear=2010)
, Person(name=Alien, militaryYear=2005, firstPregnantYear=2009)
])
The Alien has militaryYear & firstPregnantYear , which is incorrect.
So , I add another MaleBuilder and FemaleBuilder :
class MaleBuilder(private val name: String) {
var militaryYear = 0
fun build(): Person = Person(name, militaryYear, null)
}
class FemaleBuilder(private val name: String) {
var firstPregnantYear = 0
fun build() : Person = Person(name , null , firstPregnantYear)
}
fun male(name: String , block: Person.MaleBuilder.() -> Unit) = Person.MaleBuilder(name).apply(block).build()
fun female(name: String , block: Person.FemaleBuilder.() -> Unit) = Person.FemaleBuilder(name).apply(block).build()
And the DSL becomes :
val house = house {
male("John") {
militaryYear = 2003
}
female("Anne") {
firstPregnantYear = 2010
}
}
println(house)
OK , here is the problem , male("John") and female("Anne") are not inserted into persons . It outputs :
House(persons=null)
I think the problem comes from House's person() function :
fun person(name: String , block: Person.Builder.() -> Unit) {
persons.add(Person.Builder(name).apply(block).build())
}
The defined receiver is Person.Builder , but it is MaleBuilder and FemaleBuilder in the code.
How to solve this problem ?
Full code : https://pastebin.com/1Q6D8rzx
------- updated -------
It can be solved by introducing House.Builder.male() and House.Builder.female() functions :
fun male(name: String , block: Person.MaleBuilder.() -> Unit) {
persons.add(Person.MaleBuilder(name).apply(block).build())
}
fun female(name: String , block: Person.FemaleBuilder.() -> Unit) {
persons.add(Person.FemaleBuilder(name).apply(block).build())
}
But I think it is not a good solution. In real world example , there maybe a lot of properties , and a lot of restrictions. Is there a way to use one function to receive it ?