Find item from a list using nested find keyword in kotlin - kotlin

I have an enum class which will hold different states of the app.
enum class State {
STATE_1, STATE_2, STATE_4, READY, UNKNOWN
}
I will have a list that contains these states in some random order. I want to write an algorithm that will return a particular state if other states are not available. For example:
val list = listOf(READY, STATE_2, STATE_1)
return STATE_2
val list = listOf(READY, STATE_1)
return STATE_1
val list = listOf(STATE_2, STATE_1)
return STATE_2
val list = listOf(UNKNOWN, STATE_2)
return STATE_2
I am searching for something that will help me do nested find over a collection in kotlin.
This is what I have achieved till now:
private fun filter(states: List<State>): State {
val currentStates = states.filter {
it != State.UNKNOWN || it != State.READY
}
currentStates.find { it == State.STATE_4 }?.let {
return it
} ?: currentStates.find { it == State.STATE_2 }?.let {
return it
} ?: currentStates.find { it == State.STATE_1 }?.let {
return it
}
}

If you define your enum states in order by what "wins" the filter:
enum class State {
READY, UNKNOWN, STATE_1, STATE_2, STATE_4
}
Then you can pick the one with the highest ordinal:
fun filter(states: List<State>): State =
states.maxBy(State::ordinal) ?: error("Must have at least one state")
This assumes there is at least one State in the provided list. If that's not a safe assumption, you can return a nullable:
fun filter(states: List<State>): State? = states.maxBy(State::ordinal)
If there's some reason you can't rely on defining them in a certain order, you can provide the order as a list (or linked Set to make it fool-proof) in this function:
fun filter(states: List<State>): State {
val order = linkedSetOf(State.READY, State.UNKNOWN, State.STATE_1, State.STATE_2, State.STATE_4)
assert(order.size == State.values().size) // To ensure this function is updated if States are updated.
return states.maxBy { order.indexOf(it) } ?: error("Must have at least one state")
}

I doubt there is much you can optimize. I can only think of using loop over predefined priorities like following
private val PRIORITY = listOf(State.STATE_4, State.STATE_2, State.STATE_1)
private fun filter(states: List<State>): State? {
val currentStates = states.filter {
it != State.UNKNOWN || it != State.READY
}
PRIORITY.forEach { prio ->
currentStates.find { it == prio }?.let {
return it
}
}
return null
}
Note: I'm using nullable State? as return type instead of State for the cases when nothing appropriate is found within a collection.

Related

In Kotlin, how can I test and use a value without computing it twice?

Every so often, I find myself wanting to compute a value for some sort of filter operation, but then wanting to use that value when it's already disappeared into the condition-checking thing.
For instance:
val found = list.firstOrNull { slowConversion(it).isWanted() }
if (found != null) {
something(found, slowConversion(found))
}
or
when {
other_conditions -> other_actions
list.any { it.contains(regex1) } -> something(list.firstOrNull { it.contains(regex1) } ?: "!!??")
}
For the slowConversion() I can work with a sequence mapped to pairs, although the terms first and second kinda confuse things a bit...
val pair = list.asSequence().map { it to slowConversion(it) }.firstOrNull { it.second.isWanted() }
if ( pair != null ) {
something(pair.first, pair.second)
}
or if I only want the conversion,
val converted = list.firstNotNullOfOrNull { slowConversion(it).takeIf { it.isWanted() } }
but the best I can come up with to avoid the when duplication involves moving the action part into the condition part!
fun case(s: List<String>, r: Regex) {
val match = s.firstOrNull { it.contains(r) }?.also { something(it) }
return match != null
}
when {
other_conditions -> other_actions
case(list, regex1) -> true
}
At this point, it seems I should just have a stack of function calls linked together with ||
other_things || case(list, regex1) || case(list, regex2) || catchAll(list)
Is there something better or more concise for either of these?
You can write your first example like this:
for(element in list) {
val result = slowConversion(element)
if(result.isWanted()) {
something(element, result)
break
}
}
This might not look very Kotlin-ish, but I think it's pretty straightforward & easy to understand.
For your second example, you can use the find function:
when {
other_conditions -> other_actions
else -> list.find { it.contains(regex1) }?.let(::something)
}
If you have multiple regexes, just iterate over them,
val regexes = listOf(regex1, regex2, ...)
for(regex in regexes) {
val element = list.find { it.contains(regex1) } ?: continue
something(element)
break
}

Kotlin functional find single element

I am relatively new to Kotlin and I try to overcome a special case.
I am filtering a books store and want to verify that the length of the obtained list is exactly one unit shorter than the original one. Further I need to verify that the discarded element is under a specific state. Here is my example:
fun BookStoreVerified(bookStore: BookStore): Boolean {
val specialChapter = bookStore.stores
.flatMap { it.books }
.flatMap { it.chapters }.filter { it != null && it.state == Chapter.SPECIAL }
val total = bookStore.stores
.flatMap { it.books }
.flatMap { it.chapters }
.filterNotNull()
val finalChapters = book.stores
.flatMap { it.books }
.flatMap { it.chapters }
.filter { it != null && it.state.isCorrect }
return (finalChapters.size + specialChapterFigure.size == total.size) && (specialChapter.size == 1)
}
My question is if there is a smarter way to compute the above operation. I would like to know if ander a scope like filter, map can we make reference to the previous object? ( get the length of the original list for instance ?)
You have Books where each Book contains a list of Chapters. You want to partition chapters from all the books according to some criteria.
With this in mind the partition function can be useful:
data class Chapter(val state: String)
data class Book(val chapters: List<Chapter>? = null)
fun main() {
val books = listOf(
Book(),
Book(chapters = listOf(Chapter("a"), Chapter("SPECIAL"))),
Book(chapters = listOf(Chapter("c"), Chapter("d")))
)
val (specialChs, regularChs) = books
.flatMap { it.chapters ?: emptyList() }
.partition { it.state == "SPECIAL" }
println(specialChs) // [Chapter(state=SPECIAL)]
println(regularChs) // [Chapter(state=a), Chapter(state=c), Chapter(state=d)]
}
Now that you have specialChs and regularChs, you can check whatever invariants you want.
For example:
check(specialChs.size == 1 && specialChs.first().state ==
"SPECIAL")
Edit: It is possible to abstract away the existence of null chapters inside a Book:
data class Book(val chapters: List<Chapter>? = null) {
val safeChapters: List<Chapter>
get() = chapters ?: emptyList()
}
then in your code you can flatMap { it.safeChapters } instead of .flatMap { it.chapters ?: emptyList() }

Optimizing a for loop to add items to a map

Kotlin 1.5.42
I have the following data class and I need to filter out level 2 and level 3 values. Then I use a for..loop to loop through the level2 and check that the level3 has a parentId that matches the level2 value
Which is added to a list. When that level3 loop has finished I add to map with the key being the level2 object and the value being the list of level3.
i.e.
Map<TopsProductFilterItem, List<TopsProductFilterItem>>
I was looking for a better solution that is more concise using kotlin and looking if associateBy, or assoicateWith would help.
data class TopsProductFilterItem(
val value: String = "",
val catalogSearchCustomAttribute: CatalogSearchCustomAttribute = CatalogSearchCustomAttribute(),
)
And the following data class that has the levels
data class CatalogSearchCustomAttribute(
val level: Int = 0,
val parentId: Int = 0)
As there can be many levels assigned to his class I am only interested in level 2 and level 3. So I have filtered them out as below.
private fun createMapOfLevelCategories(listOfTopsProductFilterItem: List<TopsProductFilterItem>) {
val listOfLevel2 = listOfTopsProductFilterItem.filter { topsProductFilterItem ->
topsProductFilterItem.catalogSearchCustomAttribute.level == 2
}
val listOfLevel3 = listOfTopsProductFilterItem.filter { topsProductFilterItem ->
topsProductFilterItem.catalogSearchCustomAttribute.level == 3
}
val mapOfCategoryLevel2 = mutableMapOf<TopsProductFilterItem, List<TopsProductFilterItem>>()
listOfLevel2.forEach { categoryLevel2 ->
/* Find the parent id in the level 3 and from the level 2 value */
val listOfCategoryLevel3 = mutableListOf<TopsProductFilterItem>()
listOfLevel3.forEach { categoryLevel3 ->
if(categoryLevel2.value.toInt() == categoryLevel3.catalogSearchCustomAttribute.parentId) {
/* found a matching parent ID and value */
listOfCategoryLevel3.add(categoryLevel3)
}
}
mapOfCategoryLevel2.put(categoryLevel2, listOfCategoryLevel3)
}
}
You can do listOfLevel2.associateWith { ... }. associatedWith creates map with the keys being the same as the iterable you called it on, but allows you to specify how you want each key's associated value to be transformed. In this case, we want the value to be all the items in the level 3 list that have a parentId equal to the key's value.toInt().
listOfLevel2.associateWith { level2 ->
listOfLevel3.filter { level3 ->
level2.value.toInt() == level3.catalogSearchCustomAttribute.parentId
}
}
Even better, you can do a groupBy on the level 3 parent Ids first, then you don't have to loop over the the listOfLevel3 over and over again in associatedWith:
listOfLevel3.groupBy { it.catalogSearchCustomAttribute.parentId }.let { parentIdGroups ->
listOfLevel2.associateWith { level2 ->
parentIdGroups[level2.value.toInt()] ?: emptyList()
}
}
It may be a matter of taste regarding code readability, but I would do it like this:
private fun createMapOfLevelCategories(listOfTopsProductFilterItem: List<TopsProductFilterItem>): Map<TopsProductFilterItem, List<TopsProductFilterItem>> {
// create map: parentId -> (parent, children)
val resultById = listOfTopsProductFilterItem
.filter { it.catalogSearchCustomAttribute.level == 2 }
.associate {
it.value.toInt() to Pair(it, mutableListOf<TopsProductFilterItem>())
}
// associate children to parents
listOfTopsProductFilterItem
.filter { it.catalogSearchCustomAttribute.level == 3 }
.forEach {
resultById.getValue(it.catalogSearchCustomAttribute.parentId).second += it
}
return resultById.values.toMap()
}
I wouldn't say it is trivial to read and understand, but at least for me it is cleaner than your implementation. It should be also a little more performant, because we iterate over level3 items only once, not once per level2 item.
There is a balance between concise and readable that is subjective. This works for me:
fun mapOfLevel2Categories(itemList: List<TopsProductFilterItem>): Map<TopsProductFilterItem, List<TopsProductFilterItem>> {
fun List<TopsProductFilterItem>.withParent(item: TopsProductFilterItem) =
this.filter { it.catalogSearchCustomAttribute.parentId == item.value.toInt() }
val (level2items, level3items) = itemList
.filter { it.catalogSearchCustomAttribute.level in (2..3) }
.partition { it.catalogSearchCustomAttribute.level == 2 }
return level2items.associateWith { level3items.withParent(it) }
}
By the way, having the string TopsProductFilterItem.value as the id is odd. I am guessing that this is due to simplifying the actual implementation for SO.
This can be achieved in one statement-chain using associateWith.
fun createMapOfLevelCategories(
listOfTopsProductFilterItem: List<TopsProductFilterItem>,
): Map<TopsProductFilterItem, List<TopsProductFilterItem>> {
val upperLevel = 2
val lowerLevel = 3
return listOfTopsProductFilterItem
// find all upper-level items
.filter { it.catalogSearchCustomAttribute.level == upperLevel }
// find all children for the upper-level item
.associateWith { upperLevelItem ->
// search the item list for children of the upperLevelItem...
listOfTopsProductFilterItem
// find all lower-level items
.filter { it.catalogSearchCustomAttribute.level == lowerLevel }
// find all lower-level items with a parent in the upper-level
.filter { it.catalogSearchCustomAttribute.parentId == upperLevelItem.value.toInt() }
}
}
If searching filtering through the list repetitively is expensive, then use groupBy to create a Map. Then all children for a given parentId can be easily fetched.
fun createMapOfLevelCategories(
listOfTopsProductFilterItem: List<TopsProductFilterItem>,
): Map<TopsProductFilterItem, List<TopsProductFilterItem>> {
val upperLevel = 2
val lowerLevel = 3
// group all lower-level Items by parentId
val mapParentIdToChildItems: Map<Int, List<TopsProductFilterItem>> =
listOfTopsProductFilterItem
.filter { it.catalogSearchCustomAttribute.level == lowerLevel }
.groupBy { it.catalogSearchCustomAttribute.parentId }
return listOfTopsProductFilterItem
// find all upper-level items
.filter { it.catalogSearchCustomAttribute.level == upperLevel }
// find all children for the upper-level item
.associateWith { upperLevelItem ->
mapParentIdToChildItems[upperLevelItem.value.toInt()] ?: listOf()
}
}
Assumptions
I've made two assumptions,
TopsProductFilterItem.value is a unique identifier,
The relationship is one parent has many children, not many-to-many.

How to find last node that satisfies where predicate in singly linked list?

write a method "lastWhere" that accepts a function called "where" of type (T) -> Boolean. The method returns the last element of type T to which the "where" function applies. If no matching element is found, null is returned.
call the method "lastwhere" on the linked list below. Find the last game that is more than 10 euros.
So far I've got this Code going for me.
I assume the only important piece of Code I need to edit is the "fun lastWhere" for task number 1)
the second task wants me to implement a way on the main function to find the last Game that is cheaper than 10 Euros.
class LinkedList<T> {
data class Node<T>(val data: T, var next: Node<T>?)
private var first: Node<T>? = null
override fun toString(): String = first?.toString() ?: "-"
fun isEmpty() = first == null
fun addLast(data: T) {
if (first == null) {
first = Node(data, first)
return
}
var runPointer = first
while (runPointer?.next != null) {
runPointer = runPointer.next
}
runPointer?.next = Node(data, null)
}
fun lastWhere (where: (T) -> Boolean): T? { // "where" function needs to be implemented
if (isEmpty()) return null
else {
var runPointer = first
while (runPointer?.next != null ) {
runPointer = runPointer.next
}
return runPointer?.data
}
}
}
data class Game(val title: String, val price: Double)
fun main() {
val list = LinkedList<Game>()
list.addLast(Game("Minecraft", 9.99))
list.addLast(Game("Overwatch", 29.99))
list.addLast(Game("Mario Kart", 59.99))
list.addLast(Game("World of Warcraft", 19.99))
var test = list.lastWhere ({it.price >= 10.00}) // This is probably wrong too, since I haven't got task 1) working
println (test)
}
Would appreciate any help!
Since you only store a reference to first node, you don't have any choice but to start at first and iterate. you will also have to keep a reference to last item that satisfied the where predicate, and keep updating this reference with every iteration.
fun lastWhere (where: (T) -> Boolean): T? {
var runPointer = first
var item: T? = null // init item to null, if nothing is found we return null
while (runPointer != null ) {
// For every node, execute the where function and if it returns true
// then update the return value
if(where(runPointer.data)) { item = runPointer.data }
runPointer = runPointer.next
}
return item
}

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