sortedBy parameter as a variable - sorting

I have a method which needs to sort a collection in many possible ways. Instead of calling myCollection.sortedBy multiple times, I'd like to have the lambda which I'd pass to sortedBy in a variable, and later I'd pass that variable to one single call to sortedBy. However, I can't figure out the type which that lambda variable should have. Keep in mind that the "sorted-by fields" may have different types (but they're all Comparable, obviously).
Here's a simplified version of my problem:
data class Person(val name: String, val age: Int)
enum class SortBy {
NAME, AGE
}
fun main(args: Array<String>) {
val people = listOf(Person("John", 30), Person("Mary", 20))
val sort = SortBy.NAME
val comparator = when (sort) {
SortBy.NAME -> { p: Person -> p.name }
SortBy.AGE -> { p: Person -> p.age }
}
// the line below won't compile
people.sortedBy(comparator).forEach(::println)
}
Any ideas?

The problem is that the two lambdas have very different types, so the type of the variable comparator is simply inferred to be Any, which is first common supertype. There is no other type (other than Any?) which this variable can have if you need to be able to assign either of the lambdas to it.
Then, depending on what you ended up with (either a (Person) -> Int or a (Person) -> String), the sortedBy method would have to somehow infer its type parameter, which again is impossible while handling both cases.
However, there's a solution. You can create Comparator<Person> instances in the different when branches instead which wrap the specific types of whatever the Person instances are compared by, with the compareBy function:
val comparator: Comparator<Person> = when (sort) {
SortBy.NAME -> compareBy { it.name }
SortBy.AGE -> compareBy { it.age }
}
And then use the sortedWith method that takes a Comparator instead of a selector lambda:
people.sortedWith(comparator).forEach(::println)

You could simply use property references
val people = listOf(Person("John", 30), Person("Mary", 20))
val sort = Person::age
people.sortedBy(sort).forEach(::println)

Related

Kotlin. ArrayList, how to move element to first position

I have a list of Lessons. Here is my Lessons class:
data class Lessons(
val id: Long,
val name: String,
val time: Long,
val key: String
)
I need to move the element to the beginning of the list, whose key field has a value "priority".
Here is my code:
val priorityLesson = lessons.find { it.key == "priority" }
if (priorityLesson != null) {
lessons.remove(priorityLesson)
lessons.add(0, priorityLesson)
}
Everything is working but I do not like this solution, perhaps there is a more efficient way to perform this algorithm. In addition, it comes to me to convert the list to mutable, and I would like to leave it immutable.
Please help me.
One way is to call partition() to split the list into a list of priority lesson(s), and a list of non-priority lessons; you can then rejoin them:
val sorted = lessons.partition{ it.key == "priority" }
.let{ it.first + it.second }
As well as handling the case of exactly one priority lesson, that will cope if there are none or several. And it preserves the order of priority lessons, and the order of non-priority lessons.
(That will take a little more memory than modifying the list in-place; but it scales the same — both are 𝒪(n). It's also easier to understand and harder to get wrong!)
First, I would call your class Lesson rather than Lessons as it represents a single lesson. Your choice of the variable name lessons is good for your list of lessons.
You can use a mutable list and move the item to the top:
val priorityLessonIndex = lessons.indexOf { it.key == "priority" }
if (priorityLessonIndex != -1)
lessons[0] = lessons[priorityLessonIndex]
.also { lessons[priorityLessonIndex] = lessons[0] }
Or you can use an immutable list:
val priorityLesson = lessons.firstOrNull { it.key == "priority" }
val newList =
if (priorityLesson != null)
listOf(priorityLesson) + (lessons - priorityLesson)
else
lessons
A possibly more efficient way, which avoids creation of intermediate lists:
val newList = buildList(lessons.size) {
lessons.filterTo(this) { it.key == "priority" }
lessons.filterTo(this) { it.key != "priority" }
}

Spring Data JPA filtering by best match

I want to implement filtering for multiple fields of an entity which is ordered by best match. By best match I mean that the more of the filtered fields match the higher in the order the result is listed. I want this to work dynamically, so I can add more filters later on.
I have been looking for a solution for a long time now and I didn't find an elegant way to do this with JPA.
My approach is to concatenate all my predicates with or and then order them by how many of the fields match. This is done by dynamically creating a CASE statement for each possible combination of the filters (this is a powerset and leads to a lot of CASE statements). Then I give every subset a rank (= size of the subset) and then I sort by the rank in descending order. This way subsets with more elements (= filters) are ranked higher.
From a few tests I can see that I already takes up to 10s for 4 filters, so that can't be a good solution.
Here is my code:
private fun orderByBestMatch(): Specification<User?> {
return Specification<User?> { root: Root<User?>, query: CriteriaQuery<*>, builder: CriteriaBuilder ->
val benefit = getExpressionForNestedClass<String>(root, "benefit")
val umbrellaTerm = getExpressionForNestedClass<String>(root, "umbrellaTerm")
val specialization = getExpressionForNestedClass<String>(root, "specialization")
val salaryExpectation = root.get<Number>("salaryExpectation")
val matcher: CriteriaBuilder.Case<Int> = builder.selectCase()
for (set in powerSetOfsearchedFields()) {
if(set.isNotEmpty()) {
var predicate: Predicate? = when(set.elementAt(0).key) {
"umbrellaTerm" -> builder.like(umbrellaTerm, set.elementAt(0).value.toString())
"specialization" -> builder.like(specialization, set.elementAt(0).value.toString())
"benefit" -> builder.like(benefit, set.elementAt(0).value.toString())
"salaryExpectation" -> builder.equal(salaryExpectation, set.elementAt(0).value.toString())
else -> null
}
for (i in 1 until set.size) {
predicate = when(set.elementAt(1).key) {
"umbrellaTerm" -> builder.and(predicate, builder.like(umbrellaTerm, set.elementAt(1).value.toString()))
"specialization" -> builder.and(predicate, builder.like(specialization, set.elementAt(1).value.toString()))
"benefit" -> builder.and(predicate, builder.like(benefit, set.elementAt(1).value.toString()))
"salaryExpectation" -> builder.and(predicate, builder.equal(salaryExpectation, set.elementAt(1).value.toString()))
else -> null
}
}
matcher.`when`(predicate, set.size)
}
}
matcher.otherwise(0)
query.orderBy(builder.desc(matcher))
query.distinct(true)
builder.isTrue(builder.literal(true))// just here for the function to have a return value
// result?.toPredicate(root, query, builder)
}
}
This function is used in a Builder class I implemented and is appended to the Specification with an and when building the Specification.
The Specification is then passed to UserRepository.findall().
Is there a better way (maybe even an out of the box way) to implement this behaviour?
Thanks in advance

How to sort an array of object in kotlin with custom order?

I have an array of object User(val name: String, val role: String). Role can be: leader, student, teacher, parent. And should be sorted in that order. I've read some article about Comparator but haven't figure out how they work. Can you please add some explanation too?
You can instantiate a HashMap for indicating roles priority:
private val roles: HashMap<String, Int> = hashMapOf(
"Leader" to 0,
"Student" to 1,
"Teacher" to 2,
"Parent" to 3
)
fun sortUsers(users: ArrayList<User>): ArrayList<User> {
val comparator = Comparator { o1: User, o2: User ->
return#Comparator roles[o1.role]!! - roles[o2.role]!!
}
val copy = arrayListOf<User>().apply { addAll(users) }
copy.sortWith(comparator)
return copy
}
In the comparator, if the result of the subtraction is negative, it will sort it in the order [o1, o2]. Otherwise, [o2, o1]. So in this case, it will sort your list in an ascending manner (since we have the highest priority as 0 - Leader as indicated in the HashMap).
For example, if we have o1 as Teacher and o2 as Leader,
the result of the comparator will be: 2 - 0 = 2 which is a positive integer. Hence, it is sorted as [o2 (Leader), o1 (Teacher)].
Reversing the roles however yields the opposite result: 0 - 2 = -2 and hence it will be ordered [o1 (Teacher), o2 (Leader)].
You can verify this result with:
fun main(args: Array<String>) {
val users = arrayListOf(
User("john", "Student"),
User("tim", "Teacher"),
User("nice", "Student"),
User("hey", "Leader"),
User("you", "Parent")
)
println(sortUsers(users))
}
Which prints out: [User(name=hey, role=Leader), User(name=john, role=Student), User(name=nice, role=Student), User(name=tim, role=Teacher), User(name=you, role=Parent)]
I believe the simplest way to solve your problem is to use enum classes, since they already implement the Comparable interface. You can't use String directly since it would use the String compareTo method, which sorts them alphabetically.
Essentially you would have to do something like this:
enum class Role { LEADER, STUDENT, TEACHER, PARENT }
data class User(val name: String, val role: Role)
fun main(args: Array<String>) {
val trumpino = User("trumpino", Role.STUDENT)
val obamino = User("barack-obamino", Role.PARENT)
val bushino = User("george-bushino", Role.TEACHER)
val kennedino = User("kennedino", Role.LEADER)
val mlist = listOf<User>(trumpino, obamino, bushino, kennedino)
val result = mlist.sortedBy { it.role }
println(result)
}
But that would require you to change other pieces of your code if you're already using strings. You could possibly add an extension method to parse the string into an enum or use the valueOf method which is explained in this other answer.
The comparator interface is meant to be used with the functional constructs from the kotlin library, in constrast with the comparable interface which is meant to represent the inherent ordering from your class (if it has one). If you need to order your data in a way that's different from its normal ordering (which would be defined by the comparable interface), you use comparator with some ordering method like sortedWith.
With comparator you could do something like, which specify a complex ordering in a simple manner:
mlist.sortedWith(compareByDescending { it.role }.thenBy { it.name })
In contrast to the comparable interface:
class User(val name: String, val role: Role) : Comparable {
override operator fun compareTo(other: User) : Int {
return when {
other.role > this.role -> 1
other.role < this.role -> -1
other.name > this.name -> 1
other.name < this.name -> -1
else -> 0
}
}
}
They both act in the same way in the sense that the value they return is meant to represent the ordering of between this and other, as explained by the kotlin documentation of compareTo:
Compares this object with the specified object for order. Returns zero if this object is equal to the specified other object, a negative number if it's less than other, or a positive number if it's greater than other.
But since comparator is a functional construct, it has a diferent interface from its OOP counterpart as it's meant to be used with lambda expressions.
abstract fun compare(a: T, b: T): Int
But it acts in the same way, as explained in the java documentation for Comparator
Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
its so simple if you use kotlin, you can use sort method. for example
you have list like
val listOfUser = listOf<User>(
User("raka", "teacher"),
User("adi", "student"),
User("steve", "student"),
User("mark", "student"),
User("jack", "operator")
)
if you want to sort, you just access your variable and use sort method. like this
listOfUser.sortedBy {it.role} // if you want to sort ASCENDING by roles
listOfUser.sortedByDescending { it.role } // if you want to sort DESC by roles
Let your array be
val listOfUser = listOf<User>(
User("Ben", "Teacher"),
User("Bheem", "Student"),
User("Steve", "Student"),
User("Fora", "Student"),
User("Jacker", "Operator")
)
To sort this array you can use sortedWith method and a comparator of your object whose return type is also list is as shown below
val sortedUsersList = listOfUser.sortedWith(compareBy { it.role })
If you use sortBy or sortByDescending methods their return will be Unit but not list.

Sort a vector with a comparator which changes its behavior dynamically

I have a vector of a custom struct and a list of attributes to use for ordering that vector in descending priority. For example:
struct TheStruct {
artist: String,
title: String,
date: String,
}
let order_vec: Vec<String> = vec!["artist".to_string(),"title".to_string(),"date".to_string()];
let item_vec: Vec<TheStruct> = Vec::new();
I want the vector to be ordered as given by order_vec. In this example, it should first be ordered by the artists name, when this is equal it should be ordered by the title. I do not want to hard-code this ordering as order_vec changes dynamically.
I found Vec::sort_by which takes a compare function. How do I dynamically generate that function? Is there a way to do this without sort_by?
How do I dynamically generate that function
You don't. You have a specific closure that has dynamic behavior inside of it.
Here, we have a list of sorts to apply. When we need to compare two items, we iterate through the list. We use Ordering::then_with to only apply the comparison when the previous comparison was Equal:
use std::cmp::Ordering;
#[derive(Debug, Copy, Clone)]
enum Field {
Artist,
Title,
Date,
}
struct TheStruct {
artist: String,
title: String,
date: String,
}
fn main() {
let mut items: Vec<TheStruct> = vec![];
use Field::*;
let orders = vec![Artist, Title];
items.sort_by(|a, b| {
orders.iter().fold(Ordering::Equal, |acc, &field| {
acc.then_with(|| {
match field {
Artist => a.artist.cmp(&b.artist),
Title => a.title.cmp(&b.title),
Date => a.date.cmp(&b.date),
}
})
})
})
}
I used an enum for the fields because I didn't want to deal with what to do when one of the sorts is an unknown field.

Java 8 stream compare two objects and run a function on them

I've a a stream which I want to partition into smaller parts based on matching Id and then apply some proccessing logic on each of the part/element.
class BigRequest{
String bId;
List<Parts> parts;
//getters and setter here
}
Class Parts{
String pId;
String partId;
//getters and setter here
}
I want to segregate and create a list of Parts of size 10 when the partId of different parts are same.
How to use the filter or reduce or groupingBy function to compare the two elements and put them to a list?
I've tried filter like below, doesn't take p1 variable:
big.stream().filter( p -> p.getPartId() == p1.getPartId()) //error
Tried groupingBy like this
big.stream().collect(Collectors.groupingBy(Parts::getPartId) //error
I want to iterate over the filtered/reduced list a another and call another function called abc(). How can I do it using Java Streams?
pseudo:
big.getParts().stream.
//dividing logic logic
for(i < parts.size)
abc(p)
Thanks
You might use something like this:
Map<String,List<Parts>> commonId = big.getParts().
stream().
collect(
Collectors.groupingBy(
Parts::getPartId,
Collectors.mapping(
Function.identity(),
Collectors.toList()
)
)
);
and after it, you will just need to iterate over the map and apply your function.
commonId.entrySet().stream().map(entry -> apply(entry))...
Updated
We can omit Collectors.mapping(Function.identity(),Collectors.toList()) part, since it is a default behaviour of groupingBy
Map<String,List<Parts>> commonId = big.getParts().
stream().
collect(
Collectors.groupingBy(
Parts::getPartId
)
);

Resources