I have two list of same model class (STUDENT), sample student object structure is given below,
{
"_id": "5a66d78690429a1d897a91ed",
"division": "G",
"standard": "X",
"section": "Secondary",
"lastName": "Sawant",
"middleName": "Sandeep",
"firstName": "Shraddha",
"pin": 12345,
"isEditable": true,
"isTracked": false
}
One list have 3 objects and other 2. lets say, List A has 1, 2, 3 students and List B has 1, 2
So my question is there any inbuilt functions to get the uncommon element by comparing just the id? If not how can i solve this issue.
FYI, following are the two approaches i have made to solve, but failed miserably.
Approach 1.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val consolidated = prefStudents.filter {
prefStudents.any { students: Students -> it._id == students._id }
}
return prefStudents.minus(consolidated)
}
Approach 2.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val consolidatedStudents = studentsList + prefStudents
val distinctStudents = consolidatedStudents.distinctBy{ it._id }
return prefStudents.minus(distinctStudents)
}
Any kind of help will be greatly appreciated.
Thanks
A more Kotlin way to achieve what Ahmed Hegazy posted. The map will contain a list of elements, rather than a key and count.
Using HashMap and Kotlin built-ins. groupBy creates a Map with a key as defined in the Lambda (id in this case), and a List of the items (List for this scenario)
Then filtering out entries that have a list size other than 1.
And finally, converting it to a single List of Students (hence the flatMap call)
val list1 = listOf(Student("1", "name1"), Student("2", "name2"))
val list2 = listOf(Student("1", "name1"), Student("2", "name2"), Student("3", "name2"))
val sum = list1 + list2
return sum.groupBy { it.id }
.filter { it.value.size == 1 }
.flatMap { it.value }
I know that this is an old post but I believe there is a neater and shorter solution. See sample below using Mikezx6r's data whose answer was accepted above.
val list1 = listOf(Student("1", "name1"), Student("2", "name2"))
val list2 = listOf(Student("1", "name1"), Student("2", "name2"), Student("3", "name2"))
val difference = list2.toSet().minus(list1.toSet())
If you have two lists, where element is identified e.g. by some kind of id (item.id), then you can do as below:
fisrtList.filter { it.id !in secondList.map { item -> item.id } }
I assume firstList and secondList contain objects of the same type naturally.
Here's an extension function that basically does what you want. It makes an assumption that the element E knows how to be identified, e.g. by Student._id in your example:
infix fun <E> Collection<E>.symmetricDifference(other: Collection<E>): Set<E> {
val left = this subtract other
val right = other subtract this
return left union right
}
Here's an example of how it could be used:
val disjunctiveUnion: List<Student> = listA symmetricDifference listB
An example test case I'd written for it:
#Test
fun `symmetric difference with one of either set`() {
val left = listOf(1, 2, 3)
val right = listOf(2, 3, 4)
val result = left symmetricDifference right
assertEquals(setOf(1, 4), result)
}
This is the solution using a HashMap, the code could be better, but I'm very new to kotlin
fun getDistinctStudents(studentsList: List<Student>, prefStudents: List<Student>): List<Student> {
val studentsOccurrences = HashMap<Student, Int>()
val consolidatedStudents = studentsList + prefStudents
for (student in consolidatedStudents) {
val numberOfOccurrences = studentsOccurrences[student]
studentsOccurrences.put(student, if(numberOfOccurrences == null) 1 else numberOfOccurrences + 1)
}
return consolidatedStudents.filter { student -> studentsOccurrences[student] == 1 }
}
Your student class should be a data class or at least overrides hashcode and equals to be used as a key.
Until someone comes up with a neater and shorter solution, here's a working one that I think is easy enough to read:
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val studentsIds = studentsList.map { it._id } // [ 1, 2, 3 ]
val prefStudentIds = prefStudents.map { it._id } // [ 1, 2 ]
val commonIds = studentsIds.intersect(prefStudentIds) // [ 1, 2 ]
val allStudents = studentsList + prefStudents // [ Student1, Student2, Student3, Student1, Student2 ]
return allStudents.filter { it._id !in commonIds } // [ Student3 ]
}
If you have a very large amount of students (hundreds), consider using sequences for the various steps, and perhaps filtering before concatenating the last two lists could help too:
val filteredStudents = studentsList.filter { it._id !in commonIds }
val filteredPrefStudents = prefStudents.filter { it._id !in commonIds }
return filteredStudents + filteredPrefStudents
Edit: see this answer instead.
Finally after some searching on Kotlin docs i have the solution. the function i was looking for was filterNot
Here is the complete solution which i tried.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
return prefStudents.filterNot { prefStudent ->
studentsList.any {
prefStudent._id == it._id
}
}
}
Which returned the uncommon elements.
On mobile right now so I can’t test it but this might work for what you need.
Using subtract from stdlib https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/subtract.html
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents:
List<Students>): List<Students> {
return prefStudents.subtract(studentList) + studentList.subtract(prefStudents)
}
Related
There are two lists:
val listA:List<Amodel>
val listB:List<Int>
data class Amodel(val id:Int,var isUsed:Boolean=false)
Need to update listA isUsed =true based on the int id of the listB
What I'm doing now:
listB.foreach(){
//here i'm updating but it's taking too much time w.r.t time efficiency.
}
any GOOD solution.
Note list size is 500k+ for simulation
This might be slightly faster when the lists are huge:
val lookup = listB.associateWith { true }
listA.forEach { it.isUsed = lookup[it.id] ?: false }
Possibly this is even faster, I'm not sure. Because it then only sets isUsed in the case it needs to be true:
val lookup = listB.associateWith { true }
listA.forEach { lookup[it.id]?.run { it.isUsed = true } }
data class Amodel(val id: Int, var isUsed: Boolean = false)
val listA: List<Amodel> = listOf(
Amodel(1),
Amodel(2),
Amodel(3),
Amodel(4),
Amodel(5)
)
val listB: List<Int> = listOf(2, 4, 5)
listA.forEach { if (it.id in listB) it.isUsed = true }
// Alternative to set all items, not only from false to true:
listA.forEach { it.isUsed = it.id in listB }
println(listA)
Output:
[
Amodel(id=1, isUsed=false),
Amodel(id=2, isUsed=true),
Amodel(id=3, isUsed=false),
Amodel(id=4, isUsed=true),
Amodel(id=5, isUsed=true)
]
How about our good buddy Set?
val usedIds = listB.toSet()
listA.forEach { it.isUsed = it.id in usedIds }
If you just need to know if a thing is present in a collection, that's basically what a set's for! Might shave a bit of time off vs a map since you don't need to store any value data
data class Amodel(val id:Int,var isUsed:Boolean=false)
val listA = listOf(
Amodel(3),
Amodel(2),
Amodel(3),
Amodel(8),
Amodel(0)
)
val listB = listOf(2, 0, 5)
listA.forEach { aModel ->
// here we check listB have element of listA or not
if (listB.contains(aModel.id)){
aModel.isUsed = true
}
}
println(listA)
**RESULT:-**
[
Amodel(id=3, isUsed=false),
Amodel(id=2, isUsed=true),
Amodel(id=3, isUsed=false),
Amodel(id=8, isUsed=false),
Amodel(id=0, isUsed=true)
]
I have an API (https://www.thecocktaildb.com/api.php) I want to load all lists one by one. There's a request where I can find all categories and the only difference beetween lists in the filter in URL.request function What should i use to achieve such functionality? The sketch of how it should be. Maybe Paging library can be useful? Please help!
I believe that you can use Paging 3 for that.
I'll assume that you will start this by having all the categories in a list.
class DrinkSource(
private val categories: List<String>
) : PagingSource<String, Drink>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Drink> {
val result = requestFromAPI(params.key ?: categories[0])
val index = categories.indexOf(params.key)
val previous = if (index == 0) null else categories[index - 1]
val next = if (index == categories.size - 1) null else categories[index + 1]
return LoadResult.Page(result, prevKey = previous, nextKey = next)
}
private suspend fun requestFromAPI(category: String): List<Drink> {
// replace this with an API call
return listOf(Drink(1, ""))
}
}
From what i saw of your API there were no pagination in the category query, so this solution would work.
In my code, I create a mutable list and add elements from a model:
var lista: MutableList<ExpenseItem> = mutableListOf()
...
class ExpenseItem (val name: String, val word: String, val flavour: String)
...
val currentExpense = ExpenseItem("Sergio", "Aguacate", "Duro")
val currentExpense1 = ExpenseItem("amaya", "fresas", "pan")
val currentExpense2 = ExpenseItem("emma", "limon", "agua")
lista.add(currentExpense)
lista.add(currentExpense1)
lista.add(currentExpense2)
Now I am looking for a way to remove elements knowing, for example, the ´name´ field
I have tried the filters, remove, drop, etc for the list. I've also tried "when", but I think I'm not finding the correct syntax or way to do it,
I really appreciate the help.
It sounds like the method you want is
lista.removeAll { it.name == nameToRemove }
If you intend to modiify the actual list, then you'll want removeAll.
lista.removeAll {
it.name == "nameToRemove"
}
If you don't want to modify the original list, then filter can you get a new list without those elements.
val newList = lista.filter{
it.name != "nameToRemove"
}
Below shows a complete explanation of the behavior
var list: MutableList<String> = mutableListOf("1","2", "3")
//Shows all items
list.forEach {
println(it)
}
//Makes a new list with all items that are not equal to 1
val newList = list.filter {
it != "1"
}
newList.forEach {
println(it)
}
//Original list is untouched
list.forEach {
println(it)
}
//Modifies this list to remove all items that are 1
list.removeAll {
it == "1"
}
list.forEach {
println(it)
}
I want to write functions that perform filters on a list of my data class/object. I can filter the classes but it seem like I need to create a new list every time a want to filter again, is there any way around this?
I create multiple functions and extension function but it does not work
My data class and custom list
data class Product(
var name: String,
var category: Category,
var price: Double,
var rating: Double
)
object ProductList {
var productList = listOf(
Product("Shopping Bag", Category.HOME, 11.75, 3.9),
Product("Gold Earrings", Category.JEWELRY, 38.99, 4.2),
Product("Golf Clubs", Category.SPORTS, 20.75, 4.1),
Product("iPad", Category.ELECTRONICS, 180.75, 3.9),
Product("MacBook Pro", Category.ELECTRONICS, 1200.85, 4.6),
Product("Basketball Net", Category.SPORTS, 8.75, 3.5),
Product("Lipstick", Category.WOMENS, 19.75, 4.1),
Product("Dumbells", Category.HOME, 12.99, 4.8),
Product("Gym Shoes", Category.MENS, 69.89, 3.9),
Product("Coffee Mug", Category.HOME, 6.75, 3.9),
Product("Reading Glasses", Category.MENS, 14.99, 2.8),
Product("Nail Polish", Category.WOMENS, 8.50, 3.4),
Product("Football Cleats", Category.SPORTS, 58.99, 3.9)
)
}
My two filtering functions
fun filterByCategory(category: Category): List<Product> {
return productList.filter { it.category == category }
}
fun filterByRating(productList: List<Product>,rating: Double): List<Product> {
return productList.filter { it.rating >= rating }
}
My sorting functions
fun sortByPriceLowToHigh(productList: List<Product>) {
val sortedByPrice = productList.sortedBy { it.price }
for (i in sortedByPrice) {
println("${i.name}: ${i.price}")
}
}
fun sortByPriceHighToLow(productList: List<Product>) {
val sortedByPrice = productList.sortedByDescending { it.price }
for (i in sortedByPrice) {
println("${i.name}: ${i.price}")
}
}
My function calls in main function
fun main(args: Array<String>) {
val selectedCategory = filterByCategory(Category.HOME)
filterByRating(selectedCategory, 4.0)
println("Sorted by Price Low to High")
sortByPriceLowToHigh(selectedCategory)
println("")
println("Sorted by Price High to Low")
sortByPriceHighToLow(selectedCategory)
}
I want my output to filter this list by category(which it does) then filter the list again by rating (which it does not)
This is my output:
Sorted by Price Low to High
Coffee Mug: 6.75
Shopping Bag: 11.75
Dumbells: 12.99
Sorted by Price High to Low
Dumbells: 12.99
Shopping Bag: 11.75
Coffee Mug: 6.75
The output I would like is:
Sorted by Price Low to High
Dumbells: 12.99
Sorted by Price High to Low
Dumbells: 12.99
Because dumbbells is the only Product with a rating higher than 4.0 that is CATEGORY.HOME.
I know I can just call filter on the list twice with a new predicate but I want to use function so I can make these calls in multiple places.
As m0skit0 said: you should use the return values of these functions like filter and sortedBy.
Kotlin's collection API is using a functional approach where you are not modifying the values you pass to function but rather return the results.
I suggest you to use extension functions in this case. Chaining them looks nicer:
fun List<Product>.filterByCategory(category: Category) = this.filter { it.category == category }
fun List<Product>.filterByRating(rating: Double) = this.filter { it.rating >= rating }
fun List<Product>.sortByPriceLowToHigh() =
this.sortedBy { it.price }
.forEach { println("${it.name}: ${it.price}") }
fun List<Product>.sortByPriceHighToLow() =
this.sortedByDescending { it.price }
.forEach { println("${it.name}: ${it.price}") }
fun main() {
val filteredProducts = ProductList.productList
.filterByCategory(Category.HOME)
.filterByRating(4.0)
println("Sorted by Price Low to High")
val lowToHighProducts = filteredProducts.sortByPriceLowToHigh()
println("")
println("Sorted by Price High to Low")
val highToLowProducts = filteredProducts.sortByPriceHighToLow()
}
I have a dataClass, which contains a unique code of item, code of the parent and two lists - categories and subcategories.
data class MyItem (
var code: String,
var name: String,*
var data: String,
var count: Int,
var parent: String,
var categories: MutableList<MyItem>,
var subcategories: MutableList<MyItem>
)
I've got from server 3 different items list. And structure that I want to get is:
- listOfTopLevelItems
--- listOfMiddleLevelItems
----- listOfBottomLevelItems
where every topLevelItem contains a list of middleLevelItems and every middle level items contains a list of bottom level items. For that i used code below
for (topItem in topLevelItems) {
for (middleItem in middleLevelItems) {
if (topItem.code == middleItem.parent) {
val middleResultItem = middleItem
for (bottomItem in bottomLevelItems) {
if (middleItem.code == bottomItem.parent) {
middleResultItem.subcategories.add(bottomItem)
}
}
topItem.categories.add(middleResultItem)
}
}
result.add(topItem)
}
But the problem is if i will have a lots of items on bottom level, than it will be a lot of iterations. Is there is another way to solve this?
So what you have is a DAG of depth 3. I am going to make some other adjustments other than just solving your iteration problem.
First, I think the structure of your data classes is a bit redundant for describing a graph of objects. You do not need the category and subcategory fields in my opinion. Stripping out the irrelevant fields, this is what mine would look like:
data class MyItem(
var code: String,
var parent: String? = null,
var categories: MutableList<MyItem> = mutableListOf()
){
val subcategories: List<MyItem>
get() = categories.flatMap { it.categories }
}
A root/top item will be any item where the parent is null. And then its categories are its immediate children, and its sub categories are its grandchildren. I have provided a property here which will take care of grandchildren if you really want that accessor, and it means if you add something to a child, the parents grandchildren will be updated automatically :D.
Now for version 1 of creating the object graph. This keeps things in line with your apparent structure of knowing which ones are roots, children and grand children. But this is not needed as you will see in version 2.
fun main() {
val topItems = listOf(MyItem("1"), MyItem("2"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val topByID = topItems.map { it.code to it }.toMap()
val middleByID = middleItems.map { it.code to it }.toMap()
bottomItems.forEach { middleByID[it.parent]?.categories?.add(it) }
middleItems.forEach { topByID[it.parent]?.categories?.add(it) }
println(topItems)
println(topItems[0].subcategories)
}
But really, all you need to know for building an obect graph is parent child relationship, and they can all just be in a big collection. Then you can rebuild your object graph like this:
fun main() {
val topItems = listOf(MyItem("1", "*"), MyItem("2", "*"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val allItems = topItems + middleItems + bottomItems
val allItemsByID = allItems.map { it.code to it }.toMap()
allItems.forEach {
allItemsByID[it.parent]?.categories?.add(it)
}
println(topItems)
println(topItems[0].subcategories)
}
This is my favorite approach :D