Grouping classes in kotlin - kotlin

I have a class called as Student, with three fields, I need to convert that class into a GroupedStudent. How can this be achieved?
var students: List<Student> = mutableListOf(Student("A", "X", 1), Student("A", "Y", 2), Student("B", "X", 2), Student("B", "Y", 2))
I need to convert the above list to a GroupedStudent List, how can i do that?
They should be grouped by studentName, and have the SubjectMarks as list.
class Student {
var name: String
var subject: String
var marks: Int
}
class GroupedStudent {
var name: String
var subMarks: MutableList<SubjectMarks>
}
class SubjectMarks {
var subject: String
var marks: Int
}

Use the Kotlin stdlib Collections functions:
var students: List<Student> = mutableListOf(Student("A", "X", 1), Student("A", "Y", 2), Student("B", "X", 2), Student("B", "Y", 2))
val groupedMap = students.groupBy { it.name }
val groupedStudents = mutableListOf<GroupedStudent>()
groupedMap.forEach { key, value ->
val groupedStudent = GroupedStudent(key, value.map { SubjectMarks(it.subject, it.marks) }.toMutableList())
groupedStudents.add(groupedStudent)
}
This will result in a List of GroupedStudent, with each GroupedStudent containing a list of students' marks who have that name.

You can group by the student's name and then do two simple mappings:
students.groupBy(Student::name).map { (name, students) ->
GroupedStudent(name, students.map { SubjectMarks(it.subject, it.marks) }.toMutableList())
}

If your Student class is correct, i.e. marks should be Int and not array or list of integers and you really want to convert it to "GroupedStudent List" you can use sortedWith and map methods:
class Student(var name: String, var subject: String, var marks: Int) {
override fun toString(): String = "name: $name, subject: $subject, marks: $marks"
}
class GroupedStudent(var name: String, var subMarks: MutableList<SubjectMarks>) {
override fun toString(): String = "name: $name, subject: $subMarks"
}
class SubjectMarks(var subject: String, var marks: Int) {
override fun toString(): String = "subject name: $subject, marks: $marks"
}
fun main(args: Array<String>) {
val students: List<Student> = mutableListOf(Student("B", "Y", 2),
Student("A", "X", 1),
Student("B", "X", 2),
Student("A", "Y", 2))
val groupedStudents: List<GroupedStudent> = students.sortedWith(compareBy({ it.name }))
.map { student -> GroupedStudent(student.name, mutableListOf(SubjectMarks(student.subject, student.marks))) }
println(groupedStudents)
}
The result:
[name: A, subject: [subject name: X, marks: 1], name: A, subject: [subject name: Y, marks: 2], name: B, subject: [subject name: Y, marks: 2], name: B, subject: [subject name: X, marks: 2]]
BUT if you want to group your Student objects in a map with name as key and list of subject/marks as list of Pairs, you can use groupBy:
class Student(var name: String, var subject: String, var marks: Int) {
override fun toString(): String = "name: $name, subject: $subject, marks: $marks"
}
class GroupedStudent(var name: String, var subMarks: MutableList<SubjectMarks>) {
override fun toString(): String = "name: $name, subject: $subMarks"
}
class SubjectMarks(var subject: String, var marks: Int) {
override fun toString(): String = "subject name: $subject, marks: $marks"
}
fun main(args: Array<String>) {
val students: List<Student> = mutableListOf(Student("B", "Y", 2),
Student("A", "X", 1),
Student("B", "X", 2),
Student("A", "Y", 2))
val groupedStudentsMap = mutableMapOf<String, List<Pair<String, Int>>>()
students.groupBy { it.name }
.forEach({ (key, value) ->
groupedStudentsMap[key] = value.map { element -> Pair(element.subject, element.marks) } })
println(groupedStudentsMap)
}
The result:
{B=[(Y, 2), (X, 2)], A=[(X, 1), (Y, 2)]}

Related

How to serialize/deserialize json with nested field in kotlin?

I am using Json.decodeFromString<User>("json string") (https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md)
model is like
data class User(val id: String, val name: String, val assets: List<Asset>)
data class Asset(val id: String, val sku: String, val name: String)
but input json is like
{
"data": {
"id": "userId",
"name": "userName",
"body": {
"assets": [
{
"data": {
"id": "assetId",
"sku": "assetSku",
"name": "assetName"
}
}
]
}
}
}
How can I parse json with serializer? Seems not able to parse with delegate and surrogate serializers easily.
Your POJO seems to be wrong , every field needs to have name corresponding to json value , or use GSON's SerializedName annotation and Parse sing Gson.
Your User POJO should look something like this,
data class User (
#SerializedName("data") var data : UserData
)
data class UserData(
#SerializedName("id") var id : String,
#SerializedName("name") var name : String,
#SerializedName("body") var body : Body
)
data class Body (
#SerializedName("assets") var assets : List<Assets>
)
data class Assets (
#SerializedName("data") var data : AssetsData
)
data class AssetsData(
#SerializedName("id") var id : String,
#SerializedName("sku") var sku : String,
#SerializedName("name") var name : String
)
for serializing and deserializing
dependencies {
implementation 'com.google.code.gson:gson:2.8.9'
}
val gson = Gson()
val jsonValue = gson.toJson(User)
val jsonToUser = gson.fromJson(jsonValue ,User::class.java)
Read about https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#under-the-hood-experimental
Tried something like this:
#Serializable(with = AssetSerializer::class)
data class Asset(val id: String, val sku: String, val name: String)
#Serializable(with = UserSerializer::class)
data class User(val id: String, val name: String, val assets: List<Asset>)
object AssetSerializer: KSerializer<Asset> {
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("Asset") {
element("data", buildClassSerialDescriptor("data") {
element("id", String.serializer().descriptor)
element("sku", String.serializer().descriptor)
element("name", String.serializer().descriptor)
})
}
override fun serialize(encoder: Encoder, value: Asset) {
require(encoder is JsonEncoder)
encoder.encodeJsonElement(buildJsonObject {
put("data", buildJsonObject {
put("id", value.id)
put("sku", value.sku)
put("name", value.name)
})
})
}
override fun deserialize(decoder: Decoder): Asset {
require(decoder is JsonDecoder)
val root = decoder.decodeJsonElement()
val element = root.jsonObject["data"]!!
return Asset(
id = element.jsonObject["id"]!!.jsonPrimitive.content,
sku = element.jsonObject["sku"]!!.jsonPrimitive.content,
name = element.jsonObject["name"]!!.jsonPrimitive.content,
)
}
}
object UserSerializer: KSerializer<User> {
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("User") {
element("data", buildClassSerialDescriptor("data") {
element("id", String.serializer().descriptor)
element("name", String.serializer().descriptor)
element("body", buildClassSerialDescriptor("body") {
element("assets", ListSerializer(Asset.serializer()).descriptor)
})
})
}
override fun serialize(encoder: Encoder, value: User) {
require(encoder is JsonEncoder)
encoder.encodeJsonElement(buildJsonObject {
put("data", buildJsonObject {
put("id", value.id)
put("name", value.name)
put("body", buildJsonObject {
put("assets", JsonArray(value.assets.map { asset ->
encoder.json.encodeToJsonElement(asset)
}))
})
})
})
}
override fun deserialize(decoder: Decoder): User {
require(decoder is JsonDecoder)
val root = decoder.decodeJsonElement()
val element = root.jsonObject["data"]!!
val assets = element
.jsonObject["body"]!!
.jsonObject["assets"]!!
.jsonArray
.map { asset ->
decoder.json.decodeFromJsonElement(asset)
}
return Asset(
id = element.jsonObject["id"]!!.jsonPrimitive.content,
name = element.jsonObject["name"]!!.jsonPrimitive.content,
assets = assets,
)
}
}

Kotlin modify the default constructor

How would I achieve the following in Kotlin?
public class Person {
String firstName;
String lastName;
public Person(String name, String somethingElse) {
this.firstName = name.substring(0, 5);
this.lastName = name.substring(5, 10) + somethingElse;
}
}
The use case might be a bit weird, but this should be possible in Kotlin right? I have something like the following but then I get Conflicting overloads
data class Person(
val firstName: String,
val lastName: String
) {
constructor(name: String, somethingElse: String) :
this(firstName = name.substring(0, 5), lastName = name.substring(5, 10) + somethingElse)
}
You could have a companion object:
data class Person(
val firstName: String,
val lastName: String) {
companion object {
fun create(name: String, somethingElse: String): Person {
return Person(
name.substring(0, 5),
name.substring(5, 10) + somethingElse
)
}
}
}
fun main() {
val person = Person.create("Long first name", "Last name")
println(person)
}
Yes, it's possible:
data class Person(private val name: String, private val somethingElse: String) {
val firstName: String = name.substring(0, 5)
val lastName: String = name.substring(5, 10) + somethingElse
//You may want to override default "Person(name='$name', somethingElse='$somethingElse')"
override fun toString() = "Person(firstName='$firstName', lastName='$lastName')"
}

Kotlin flatten + map of nested objects

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)

Gson does not use deserialization for generic type

I try to parse this json {"value": [1, "a", "b"]} to triple object. Gson doesn't even call my deserialize.
class TripleDeserializer: JsonDeserializer<Triple<Int, String, String>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Triple<Int, String, String> {
val f = json!!.asJsonArray.get(0).asInt
val s = json.asJsonArray.get(1).asString
val t = json.asJsonArray.get(2).asString
return Triple(f, s, t)
}
}
class SomeClass(val value: Triple<Int, String, String>)
fun another() {
val input = "{\"value\": [1, \"a\", \"b\"] }"
val type = object : TypeToken<Triple<Int, String, String>>() {}.type
val gson = GsonBuilder()
.registerTypeAdapter(type, TripleDeserializer())
.create()
val out = gson.fromJson(input , SomeClass::class.java)
}

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