Kotlin: Find Max Date list of lists - kotlin

In kotlin consider:
data class classA (
field1
field2
....
classBlst:List<classB>
)
data class classB (
field3
field4
...
dateField:String
)
The problem is to loop thru List, find the maximum dateField in classBlst, and return the corresponding field3 for that item. The dateField is a string and may contain nulls and also bad dates so need to try..catch.
I can build a list with all max dates and then do a second iteration on that list to find max. I can do that using iterators but looking for any lambda functional way of doing the same. Sorry new to all this.

Given:
data class ClassA(val list: List<ClassB>)
data class ClassB(val field3: Int, val dateField: String? = null)
You could get the latest date using mapNotNull where you'll format your date and return null if you get an exception.
Then maxBy on the field containing the formatted date in you new object created in the previous mapNotNull
I've choosed to use a Pair since it look like you only want a filter and don't want to keep the newly created object. But if that not the case consider creating a new class containing all the information needed.
val a = ClassA(
listOf(
ClassB(0, "24"),
ClassB(1, "24/03/1987"),
ClassB(2),
ClassB(3, "24/03/2024")
)
)
val res = a.list
.mapNotNull {
val dateFormater = DateTimeFormatter.ofPattern("dd/MM/yyyy")
try {
Pair(it.field3, LocalDate.parse(it.dateField, dateFormater))
} catch (e: Exception) {
null
}
}
.maxBy { it.second }

Related

How can use filter and contains with multi-ArrayList in Kotlin so I only have elements which match the condition?

I have a class called Person
data class Person(
val id: Int,
val name: String
)
data class IDs(
val id : Int,
val active : Boolean )
and an array list that has numbers of ids and another list of Persons
val myStu = listOf<Person>(Person(1, "Name_1"), Person(2, "Name_2"), Person(3, "Name_3"))
var ids = listOf<IDs>(IDs(1,false),IDs(2,true),IDs(3,true))
var newIds = listOf<Int>(2,3,4,6)
First I want to apply two actions to the myStu, first is to have a list that include all the items from myStu that his id matches the id in the IDS and only if the active is true
myStu or the new list will have the values
Person(2, "Name_2"), Person(3, "Name_3"))
Then do action two , I need to add a new item to the new list that their id does not exist in the newIds , in another word we will add a new person Person(4,"None") and (6,"None) , 4 and 6 values come from newIds list
the final output will be :
id= 2 name = "Name_2", id= 3 name = "Name_3", id= 4 name = "None" , id =6 name="None"
I want to write the code with filter , I failed with first step because I don't know how to use contains() with the list inside the filter
val newArr = myStu.filter {
ids.contains(it.id)
}
The "easiest" way of doing that would be to use filter directly, there's no need for contains. If we were to use contains, then we would need to also search for which element contained the id, in order to get the status. We can just do a .any() to do both at the same time.
V1
val activeStu = myStu.filter { person -> ids.any { it.id == person.id && it.active } }
val result = newIds.map { newId ->
activeStu.find { it.id == newId } ?: Person(id = newId, name = "None")
}
Another method, that might work a bit better if we have big lists, would be to first transform the IDs list into a map. That way the second part of our code is a bit more efficient, since there is no search involved.
V2
val idsMap = ids.associate { it.id to it.active }
val activeStu = myStu.filter { idsMap[it.id] ?: false }
//the creation of the result list is the same
Version without creating 2 new lists. This works, but it might be quite ineficient processing wise, and also harder to understand what is going on IMO.
V3
val result = newIds.map { newId ->
//try to find an IDs with the current newId and status as true
when (ids.find { it.id == newId }?.active) {
//if found, then find the corresponding Person
true -> myStu.find { it.id == newId } ?: Person(newId, "None") // if this happens, it means that an IDs with status true existed and no Person had that id. Not sure what you want in this scenario, this version creates one of the "none" persons.
//if not found, then create a new one
else -> Person(newId, "None")
}
}
Note: depending on what version of kotlin you have, you might have to change the when statement to this:
when (ids.find { it.id == newId }?.active == true)
Since I think I remember that null didn't used to be treated as false in old versions (I've run this with version 1.4.20).
Btw, you can also use this version with the idsMap from V2, just replace the when(...) with when(idsMap[newId] or when(idsMap[newId] == true) depending on the kotlin version.

Kotlin filter list by predicate

I am trying to filter a list based on a condition that a property inside the list is an enum type. But I get an error on the filter function. Can anyone tell me how to resolve this error and why it is happening?
Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
My code is below:
data class Person(
val name: String,
val ageInDays: Int,
val currentStatus: List<Status>,
)
data class Status(
val name: String,
val activity: Activity
)
enum class Activity {
COOK,
CLEAN,
SLEEP,
}
fun main() {
var build = listOf(
Person("abc", 3655, listOf(
Status("abcProc1", Activity.COOK),
Status("abcProc2", Activity.CLEAN),
Status("abcProc2", Activity.SLEEP),
)
),
Person("ghi", 500, listOf(
Status("ghiProc", Activity.COOK),
Status("ghiProc", Activity.SLEEP),
)
),
Person("def", 1000,listOf(
Status("defProc", Activity.SLEEP)
)
)
)
println(build.filter { it.currentStatus.contains(Activity.CLEAN) })
}
currentStatus is a List<Status>. The only type of object that could be in the list is a Status (or subtype thereof). So it doesn't make sense to call contains on the list with an argument that is not a Status. An Activity is not a subtype of Status.
Assuming you want to filter your list of Status to only include instances of Status for which the activity property is Activity.CLEAN, you would do it like:
build.filter { it.currentStatus.any { status -> status.activity == Activity.CLEAN } }
or slightly less efficient but possibly clearer logic:
build.filter { it.currentStatus.map(Status::activity).contains(Activity.CLEAN) }

Kotlin iterate a collection and map a certain value to new map

I have a map of collections . I need to get a list of ids from that..
val m1 = mapOf("id" to 1, "name" to "Alice")
val m2 = mapOf("id" to 2, "name" to "Bob")
val m3 = mapOf("id" to 3, "name" to "Tom")
val nameList = listOf(m1, m2, m3)
The result shall be [1, 2, 3]
Assuming you want a list as per the example, not a map as per the title, I would do it like this:
val result = nameList.map {
it.getValue("id").also { id ->
require(id is Int) { "id must be an Int" }
} as Int
}
This has the advantage of handling the following errors cleanly:
The id key is missing: NoSuchElementException: Key id is missing in the map
The id value is not an Int: IllegalArgumentException: id must be an Int
First things first, I believe that if you can, you should use classes instead of maps for storing heterogeneous data like this. So instead of your maps, you can use:
data class Person(val id: Int, val name: String)
val m1 = Person(id = 1, name = "Alice")
val m2 = Person(id = 2, name = "Bob")
val m3 = Person(id = 3, name = "Tom")
val list = listOf(m1, m2, m3)
val idsList = list.map { it.id } // no error handling required, rely on the type system
Now, if you really want to use maps like that, you have several options.
If you're certain the id key will be present and its value will be an Int, you can use the following:
nameList.map { it["id"] as Int }
This will fail with NullPointerException if id is not present in one of the maps or with ClassCastException if it's not an Int.
Normally you should make sure your map matches your contract at creation time, and not when accessing this kind of information.
But if you need to handle errors here for some reason, you can use the following instead:
nameList.map {
(it.getValue("id") as? Int) ?: error("'id' is not an Int")
}
getValue fails on absent keys with NoSuchElementException, and the error() call fails with IllegalStateException. You can also use other kinds of exceptions using throw or require().
If you want to just ignore the entries that don't have a valid integer id, you can use the following:
nameList.mapNotNull { it["id"] as? Int }
If you want to ignore the entries that don't have an id, but fail on those who have a non-integer id, you can use this:
nameList.mapNotNull { map ->
map["id"]?.let { id ->
(id as? Int) ?: error("'id' is not an Int")
}
}
These 2 last examples rely on mapNotNull, which filters the elements out if their mapped value is null.

Why filtering out null map keys doesn't change type to not nullable in kotlin?

I have a list of objects with an optional id as String and I want to make a map out of it.
I want to have the keys of my map as non nullable: so something like this:
data class Foo(
val id: String? = null
val someStuff: String? = null,
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo.filterNot { it.id == null }.associateBy { it.id }
Here bar type is Map<String?, Foo> but not Map<String, Foo>
My workaround is to add a non null asserted call: !!, but it doesn't seem clean.
Is there an easy and safe way to do this?
This looks like something that contracts could help with, but currently a contract expression can't access properties of the class in use.
As a workaround, you could define a 2nd class that has a non-null id, like so
data class Foo(
val id: String? = null,
val someStuff: String? = null
)
data class Foo2(
val id: String,
val someStuff: String? = null
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo
.mapNotNull { if (it.id != null) Foo2(it.id, it.someStuff) else null }
.associateBy { it.id }
There's a six-year-old open feature request for Map.filterNotNullKeys() and a four-year old open feature request for Map.associateByNotNull().
In my opinion, the associateBy { it.id!! } would be cleanest for readability. But you could do it like this:
val bar = foo.mapNotNull { it.id?.run { it.id to it } }.toMap()
As for your actual question, that logic is way too many steps for the compiler to infer. Your last function call to associateBy sees a nullable, so it infers a nullable. For the compiler to figure this out, it would have to step back and see that the List that you call associateBy on happens to have filtered out certain objects in a way that happens to ensure that a certain nullable property won't be null within this specific list, and it's the same property that you are associating with. Now imagine it has to do this for every call to any generic function, and the various lambdas involved could potentially have multiple lines of code. Compile times would skyrocket.

How to use jooq to update table when column names and it's values determined at runtime

I have list of table column names and it's values which will be determined # run time. Right now I am using following way to achieve the feet which requires casting Filed to TableField for every single column name. Is there any better way ?
override fun updateFields(job: Job, jsonObject: JsonObject, handler: Handler<AsyncResult<Job?>>): JobQService {
val updateFieldsDsl = dslContext.update(JOB)
var feildSetDsl: UpdateSetMoreStep<*>? = null
jsonObject.map.keys.forEach { column ->
feildSetDsl = if (feildSetDsl == null) {
updateFieldsDsl.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
} else {
feildSetDsl!!.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
}
}
val queryDsl = feildSetDsl!!.where(JOB.ID.eq(job.id))
jdbcClient.rxUpdateWithParams(queryDsl.sql, JsonArray(queryDsl.bindValues)).subscribeBy(
onSuccess = { handler.handle(Future.succeededFuture(job)) },
onError = { handler.handle(Future.failedFuture(it)) }
)
return this;
}
I'm not sure what you mean by "better" but there is a method UpdateSetStep.set(Map), which seems to be helpful for what you're trying to do. See the javadoc:
UpdateSetMoreStep set(Map<?,?> map)
Set a value for a field in the UPDATE statement.
Keys can either be of type String, Name, or Field.
Values can either be of type <T> or Field<T>. jOOQ will attempt to convert values to their corresponding field's type.