sort an array by providing field name dynamically - kotlin

I have a data class with about 20 fields:
data class Entry {
entryType: string,
order: int
....
}
now an array is created with the class
val entries = listOf<Entry>(...);
basically, I want to sort the array but user can specify any field they want to sort by. My current attempt appears to be repetitive.
fun sortByOrder(){
entries.sortedWith {f: Entry, s: Entry ->
if (f.order != s.order) {
f.order - s.order
} else {
f.entryType.compareTo(s.entryType)
}
}
}
I couldn't find away of having one method like below that can do it dynamically:
fun sortEntries(fieldName: string, fieldType: string, backUpField: string){
if (fieldType == 'number'){
entries.sortedWith {f: Entry, s: Entry ->
if (f[fieldName] != s[fieldName]) {
f[fieldName] - s[fieldName]
} else {
f[backUpField].compareTo(s[backUpField])
}
}
}
}
how can I achieve something like the above?

Related

Kotlin mapping a list that has a list inside

I am trying to map a list of items, that has another list inside which should be mapped into the same type of items. however, i end up having List<List> and can not flatten.
TropicalFruit(
val someStuff: String
)
Fruits(
val type: String,
val tropicalFruits: List<TropicalFruit>
)
FruitUiModel(
...
)
val listOfFruits: List<Fruit>
listOfFruits.map { fruit ->
if (fruit.type == "tropical") {
tropicalFruits.map {
FruitUiModel(it.someStuff)
}
} else {
FruitUiModel(fruit.type)
}
}
As a result, I want to achieve a List instead of List
Any being both FruitUiModel and List
So the list ends up being something like listOf(fruitUiModel, fruitUiModel, listOfFruitUiModels)
and i need to flatten the listOfFruitUiModels
You have to use flatMap instead of map:
listOfFruits.flatMap { fruit ->
if (fruit.type == "tropical") {
fruit.tropicalFruits.map { FruitUiModel(it.someStuff) }
} else {
FruitUiModel(fruit.type)
}
}

Kotlin sort one List with key and Enum with key and order

I receive data from Request information as list data (List) below code. That data has a "key" parameter by which I want to sort it.
data class ApplianceSetting(
#SerializedName("key") val key: String,
#SerializedName("value") var value: Any,
(...)
I have the required order in the SettingsUtilEnum and want to sort items by that.
After that, I can convert the list using map{} the data and use the function of Enum getSettingByMode() and get the list of Enum values. Then I will sort them and convert them again to List.
But that sounds too inefficient. Is there a better way.
enum class SettingsUtilEnum(
var settingKey: String,
override val order: Int = 99,
var settingName: String = "",
) : AbstractOrderEnum {
FIRST_MODE("first.mode", 0),
SECOND_MODE("second.mode", 1),
(...)
UNKNOWN_MODE("", 99);
companion object {
#JvmStatic
fun getSettingByMode(settingKey: String): SettingsUtilEnum? {
return values().find { it.settingKey == settingKey }
}
k
private fun initDataObserver() {
(activity as FavouriteActivity).viewModel.applianceSettings.observe(activity as FavouriteActivity
) { data ->
(controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
val adapter = (controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
// public final var data: List<ApplianceSetting>
// old code:
// data.settings
adapter.data = sortAndGetControlModes(data)
adapter.notifyDataSetChanged()
}
}
// TODO: sortAndGetControlModes
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.map {
getSettingByMode(it.key)
?: UNKNOWN_MODE.apply {
// If in future new modes are added -> put them as tail
settingKey = it.key
}
}.sortedBy { it.order }
// error i need to return again List<ApplianceSetting>
If you want to compare keys with theirs ASCII values you can just use sortBy { it.key }
If you want to expand possibilities of comparison you can use function sortedWith with passing custom comparator as argument.
Comparator used to compare its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
Example:
You can use it like that if you want to sort by integer value of key parameter:
data.settings.sortedWith { a, b ->
when {
a.key.toInt() < b.key.toInt() -> -1
a.key.toInt() > b.key.toInt() -> 1
else -> 0
}
}
I fixed it using sortedBy and as comparator I am using received value (order) from getSettingByMode(), if item is not found (null) I give him order value of 99 and put it on tail position:
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.sortedBy {
getSettingByMode(it.key)?.order ?:99
}

Finding an item in a list of lists that can contain many levels

kotlin 1.4.72
I have the following class that contains a list. However, the list will contain another list and could be 3 or 4 levels deep.
I am populating the data structure like this. And have a method to find a item from one of the children.
data class Producer(
val id: Int,
val children: List<Producer> = emptyList(),
) {
fun createProducer(src: Producer): Producer {
return Producer(
id = src.id,
children = src.children.map {
createProducer(it)
}
)
}
fun findProducerByIDorNull(id: Int): Producer? {
val producer = children.firstOrNull {
it.id == id
}
return producer
}
}
Currently I am using firstOrNull. However, that will only find the item in the 1st level. If the item is at a 3 level it will return null.
Just wondering if there is a better way to do this.
Many thanks for any suggestions,
You could make findProducerByIDOrNull recursive. Something like:
fun findProducerByIDorNull(id: Int): Producer? {
if (this.id == id) {
return this
}
return children.asSequence()
.mapNotNull { it.findProducerByIDorNull(id) }
.firstOrNull()
}

How do i filter and copy values to a list of objects based on another list in Kotlin

I am trying to filter a list of objects based on a certain condition from a second list and then update/copy certain values from the second list to the already filtered list.
I tried this:
val filteredList = firstObjectList.stream()
.filter { first ->
secondObjectList.stream()
.anyMatch { second ->
second.sharedId == first.shareId
}
}.toList()
filteredList.map { filtered ->
secondObjectList.forEach { so ->
if(filtered.shareId == so.shareId){
val asset= Assets()
asset.address = so.address
asset.assetValue = so.assetValue
filtered.asset = asset
}
}
}
return filteredList
here are the objects:
Class firstObject(
val shareId: Int,
var asset : Asset? = null)
Class secondObject(
val shareId: Int,
var asset: Assets)
Class Assets(
val address: String,
val assetValue: Double)
This works but obviously not very efficient and Java based. How can I improve and write this in idiomatic kotlin? as i don’t seem to be able to chain operators correctly. Thanks in Advance.
val map = secondObjectList.associateBy { it.shareId }
val filteredList = firstObjectList
.filter { it.shareId in map }
.onEach { fo ->
fo.asset = map.getValue(fo.shareId).asset.let { Assets(it.address, it.assetValue) }
}

How to filter a list of objects based on a dynamically created collection of predicates

I am working on a searching/filtering functionality where a user should be able to filter a list of events to fit a pattern which he will make in runtime.
I made a function filter which is looping over all the constraints the user has set, and then filtering the result.
My problem is that I am copying the list many times, and I was wondering if there is a way where I can do this kind of complex filtering in a more declarative (kotlin-) way without side effects.
fun filter(query: Filter, eventsIn: List<Event>): List<Event> {
var events = eventsIn
query.filters.forEach { filter ->
if (filter.key is EventFiltersListStuff){
events = when (filter.key as EventFiltersListStuff) {
PLACE -> events.filter { event -> (filter.value as List<*>).contains(event.location.place) }
AREA -> events.filter { event -> (filter.value as List<*>).contains(event.location.area) }
CATEGORY -> events.filter { event -> (filter.value as List<*>).any{it in event.category} }
GENRE -> events.filter { event -> (filter.value as List<*>).contains(event.genre) }
}
} else {
events = when (filter.key as EventFilters) {
TITLE -> events.filter { event -> event.title.contains(filter.value as String, true) }
PRICELT -> events.filter { event -> event.price <= filter.value as Int }
PRICEGT -> events.filter { event -> event.price >= filter.value as Int }
TIMELT -> events.filter { event -> event.time <= filter.value as Int }
TIMEGT -> events.filter { event -> event.time >= filter.value as Int }
}
}
}
return events
}
The model looks like this
data class Event(
val title: String,
val genre: String,
val image: String,
val link: String,
val category: List<String>,
val price: Int,
val text: String,
val tickets: String,
var time: Long,
val location: Location
)
I have two enums one is for inclusive filtering, where a user can filter a list based on multiple instances of the attribute in focus.
The other one is non inclusive, and will just remove all entities which is not matching the query.
enum class EventFiltersListStuff(val str: String, ) : FilterType {
PLACE("place"),
AREA("area"),
CATEGORY("category"),
GENRE("genre");
override fun str(): String = str
}
enum class EventFilters(val str: String, ) : FilterType {
PRICELT("priceLT"),
PRICEGT("priceGT"),
TIMELT("timestampLT"),
TIMEGT("timestampGT"),
TITLE("title");
override fun str(): String = str
}
The next code block is less relevant, but I'll include is for transparency, Because it is used in the function which is the core of my question.
interface FilterType {
fun str(): String
}
class Filter private constructor(val filters: Map<FilterType, Any>) {
class Builder {
private var filters: MutableMap<FilterType, Any> = mutableMapOf()
fun filters(key: FilterType, value: Any) = apply {
this.filters[key] = when (this.filters[key]) {
is List<*> -> (this.filters[key] as List<*>) + listOf(value)
is Comparable<*> -> listOf(this.filters[key], value)
else -> value
}
}
fun build(): Filter {
return Filter(filters)
}
}
}
I am also using the Filter to generate a filter in a GraphQL query, this is the reason I am having strings in the enums.
Simple and efficient solution would be just to filter each event with conjunction of all filters.
typealias SingleFilter = Map.Entry<FilterType, Any> // you may want to remodel it as a sealed class, more on that later
fun SingleFilter.isMatching(event: Event): Boolean = ...
fun List<Event>.applyFilters(filters: List<SingleFilter>) = filter { event -> filters.all { it.isMatching(event) }
Your model could be greatly improved by remodeling your filters as a sealed class, replacing instance checks and enums with polymorphism. This will have a huge benefit of being type-safe. You can create a hierarchy with two groups for your two filtering cases. You can define mapping between filter names and classes in different part of your code, decoupling serialization from filter logic.