How does Comparator.comparing() function work? - java-8

For the code below:
ForComparatorDemo object1 = new ForComparatorDemo("object-1",5);
ForComparatorDemo object2 = new ForComparatorDemo("object-2",4);
ForComparatorDemo object3 = new ForComparatorDemo("object-3",3);
ForComparatorDemo object4 = new ForComparatorDemo("object-4",4);
List<ForComparatorDemo> objectList = new ArrayList<>();
objectList.add(object1);
objectList.add(object2);
objectList.add(object3);
objectList.add(object4);
Comparator<ForComparatorDemo> comparer = Comparator.comparing(ForComparatorDemo::getAge);
objectList.sort(comparer);
objectList.forEach(object -> System.out.println(object.getName() + " " + object.getAge()));
I got this output(which is correct):
object-3 3
object-2 4
object-4 4
object-1 5
The question is how did that comparing function actually work?
After digging into documentation I found this code for Coamparator.comparing(..) function :
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
Could you explain me how is this function getting these two values (c1 and c2) and how that return statement actually works?

Method comparing() does not compare things.
It returns new Comparator, which is described as lamda. This is possible, as Comparator interface is FunctionalInterface.
So, this code
(Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
is equivalent of:
new Comparator<T>() {
int compare(T c1, T c2) {
return keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
}
So c1 and c2 are names of arguments.
Items are compared by key, which is extracted by keyExtractor Function.
Actual object are passed into compare() method from places, where comparator are actually used. Usually, these are different sorting methods where all collection values are iterated through via loop or iterator and compared to each other on to some outer value. For example, you could check Arrays.mergeSort.

Related

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.

Java 8 Stream Collect Sets

I have two maps, namely
Map<String, Set<String>> courseTeacherMap = {course1: [teacher1, teacher2], ...}
Map<String, Set<String>> teacherStudentMap = {teacher1: [student1, student2], ...}
And I defined a class courseStudentPair that has a very simple structure
public class courseStudentPair{
String studentName; // Taken from teacherStudentMap
String courseName; // Taken from courseTeacherMap
}
And my goal is to get a Set<courseStudentPair> out of the two maps. As long as a teacher A is teaching a course C, every student that is in the value set of key A in teacherStudentMap is considered to be a student taking C.
For example, given
Map<String, Set<String>> courseTeacherMap = {c1: [t1], c2:[t2], c3:[t1, t2]}
Map<String, Set<String>> teacherStudentMap = {t1: [s1], t2:[s1, s2]}
The result should be *(student, course) denotes a courseStudentPair object in the example below*
Set<courseStudentPair> result = [(c1, s1), (c2, s1), (c2, s2), (c3, s1), (c3, s2)]
It's quite straightforward to do it with for loops, but I am learning the stream function in Java 8 and this seems quite complicated to me. You can assume the courseStudentPair class has constructor or builder defined.
In the same spirit, you can generate each combination of (course, teacher) and then lookup for the students associated with this teacher. This may generate duplicates (for instance (c3, s1)), so be sure your CourseStudentPair class implements equals() and hashCode() based on those two fields.
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toSet;
...
Set<CourseStudentPair> result =
courseTeacherMap.entrySet()
.stream()
.flatMap(e -> e.getValue()
.stream()
.flatMap(t -> teacherStudentMap.getOrDefault(t, emptySet()).stream().map(s -> new CourseStudentPair(e.getKey(), s))))
.collect(toSet());
/*
Output:
CourseStudentPair{studentName='c1', courseName='s1'}
CourseStudentPair{studentName='c2', courseName='s2'}
CourseStudentPair{studentName='c2', courseName='s1'}
CourseStudentPair{studentName='c3', courseName='s2'}
CourseStudentPair{studentName='c3', courseName='s1'}
*/
List<Pair<String, String>> result = courseTeacherMap.entrySet()
.stream()
.flatMap(entry -> Optional.ofNullable(entry.getValue())
.orElse(new HashSet<>())
.stream()
.flatMap(teacher -> Optional.ofNullable(teacherStudentMap.get(teacher))
.orElse(new HashSet<>())
.stream()
.map(student -> Pair.of(entry.getKey(), student))))
.distinct()
.collect(Collectors.toList());
I edited to make it null-safe, in case a teacher has no students for example or your Map might have a key mapped to null for example.

Using Java 8 streams for aggregating list objects

We are using 3 lists ListA,ListB,ListC to keep the marks for 10 students in 3 subjects (A,B,C).
Subject B and C are optional, so only few students out of 10 have marks in those subjects
Class Student{
String studentName;
int marks;
}
ListA has records for 10 students, ListB for 5 and ListC for 3 (which is also the size of the lists)
Want to know how we can sum up the marks of the students for their subjects using java 8 steam.
I tried the following
List<Integer> list = IntStream.range(0,listA.size() -1).mapToObj(i -> listA.get(i).getMarks() +
listB.get(i).getMarks() +
listC.get(i).getMarks()).collect(Collectors.toList());;
There are 2 issues with this
a) It will give IndexOutOfBoundsException as listB and listC don't have 10 elements
b) The returned list if of type Integer and I want it to be of type Student.
Any inputs will be very helpful
You can make a stream of the 3 lists and then call flatMap to put all the lists' elements into a single stream. That stream will contain one element per student per mark, so you will have to aggregate the result by student name. Something along the lines of:
Map<String, Integer> studentMap = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(student -> student.name, summingInt(student -> student.mark)));
Alternatively, if your Student class has getters for its fields, you can change the last line to make it more readable:
Map<String, Integer> studentMap = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(Student::getName, summingInt(Student::getMark)));
Then check the result by printing out the studentMap:
studentMap.forEach((key, value) -> System.out.println(key + " - " + value));
If you want to create a list of Student objects instead, you can use the result of the first map and create a new stream from its entries (this particular example assumes your Student class has an all-args constructor so you can one-line it):
List<Student> studentList = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(Student::getName, summingInt(Student::getMark)))
.entrySet().stream()
.map(mapEntry -> new Student(mapEntry.getKey(), mapEntry.getValue()))
.collect(toList());
I would do it as follows:
Map<String, Student> result = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.toMap(
Student::getName, // key: student's name
s -> new Student(s.getName(), s.getMarks()), // value: new Student
(s1, s2) -> { // merge students with same name: sum marks
s1.setMarks(s1.getMarks() + s2.getMarks());
return s1;
}));
Here I've used Collectors.toMap to create the map (I've also assumed you have a constructor for Student that receives a name and marks).
This version of Collectors.toMap expects three arguments:
A function that returns the key for each element (here it's Student::getName)
A function that returns the value for each element (I've created a new Student instance that is a copy of the original element, this is to not modify instances from the original stream)
A merge function that is to be used when there are elements that have the same key, i.e. for students with the same name (I've summed the marks here).
If you could add the following copy constructor and method to your Student class:
public Student(Student another) {
this.name = another.name;
this.marks = another.marks;
}
public Student merge(Student another) {
this.marks += another.marks;
return this;
}
Then you could rewrite the code above in this way:
Map<String, Student> result = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.toMap(
Student::getName,
Student::new,
Student::merge));

Item-by-item list comparison, updating each item with its result (no third list)

The solutions I have found so far in my research on comparing lists of objects have usually generated a new list of objects, say of those items existing in one list, but not in the other. In my case, I want to compare two lists to discover the items whose key exists in one list and not the other (comparing both ways), and for those keys found in both lists, checking whether the value is the same or different.
The object being compared has multiple properites that constitute the key, plus a property that constitutes the value, and finally, an enum property that describes the result of the comparison, e.g., {Equal, NotEqual, NoMatch, NotYetCompared}. So my object might look like:
class MyObject
{
//Key combination
string columnA;
string columnB;
decimal columnC;
//The Value
decimal columnD;
//Enum for comparison, used for styling the item (value hidden from UI)
//Alternatively...this could be a string type, holding the enum.ToString()
MyComparisonEnum result;
}
These objects are collected into two ObservableCollection<MyObject> to be compared. When bound to the UI, the grid rows are being styled based on the caomparison result enum, so the user can easily see what keys are in the new dataset but not in the old, vice-versa, along with those keys in both datasets with a different value. Both lists are presented in the UI in data grids, with the rows styled based on the comparison result.
Would LINQ be suitable as a tool to solve this efficiently, or should I use loops to scan the lists and break out when the key is found, etc (a solution like this comes naturally to be from my procedural programming background)... or some other method?
Thank you!
You can use Except and Intersect:
var list1 = new List<MyObject>();
var list2 = new List<MyObject>();
// initialization code
var notIn2 = list1.Except(list2);
var notIn1 = list2.Except(list1);
var both = list1.Intersect(list2);
To find objects with different values (ColumnD) you can use this (quite efficient) Linq query:
var diffValue = from o1 in list1
join o2 in list2
on new { o1.columnA, o1.columnB, o1.columnC } equals new { o2.columnA, o2.columnB, o2.columnC }
where o1.columnD != o2.columnD
select new { Object1 = o1, Object2 = o2 };
foreach (var diff in diffValue)
{
MyObject obj1 = diff.Object1;
MyObject obj2 = diff.Object2;
Console.WriteLine("Obj1-Value:{0} Obj2-Value:{1}", obj1.columnD, obj2.columnD);
}
when you override Equals and GetHashCode appropriately:
class MyObject
{
//Key combination
string columnA;
string columnB;
decimal columnC;
//The Value
decimal columnD;
//Enum for comparison, used for styling the item (value hidden from UI)
//Alternatively...this could be a string type, holding the enum.ToString()
MyComparisonEnum result;
public override bool Equals(object obj)
{
if (obj == null || !(obj is MyObject)) return false;
MyObject other = (MyObject)obj;
return columnA.Equals(other.columnA) && columnB.Equals(other.columnB) && columnC.Equals(other.columnC);
}
public override int GetHashCode()
{
int hash = 19;
hash = hash + (columnA ?? "").GetHashCode();
hash = hash + (columnB ?? "").GetHashCode();
hash = hash + columnC.GetHashCode();
return hash;
}
}

Remove an item in List if not exists in other list using linq

I have two different types of Lists
class A
{
int number;
string name;
}
class B
{
int number;
}
List<A> a1;
List<B> b1;
And now both the lists were populated and now i want to remove items(number) in list a1 if that item(number) not exists in list b1.tried the below approach
a1.removeall(a=>b1.Exists(b1.number!=a1.number);
but the result is not as expected.Please help me...
I think you want this:
a1.RemoveAll(a=> !b1.Any(b=> b.number == a.number));
Be aware though that this is O(n^2). A more performant approach would be using a HashSet<int> (this might not matter for small lists but be aware of this for larger ones):
HashSet<int> bNums = new HashSet<int>(b1.Select(b => b.number));
a1.RemoveAll(a => !bNums.Contains(a.number));

Resources