Comparator.comparing(...).thenComparing(...) find out which fields did not match - java

I am trying to compare two objects of same class and the goal is to compare them as well as identify which fields didn't match.
Example of my domain class
#Builder(toBuilder=true)
class Employee {
String name;
int age;
boolean fullTimeEmployee;
}
Two objects
Employee emp1 = Employee.builder().name("john").age(25).fullTime(false).build();
Employee emp2 = Employee.builder().name("Doe").age(25).fullTime(true).build();
Comparing both objects
int result = Comparator.comparing(Employee::getName, Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparing(Employee::getAge, Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparing(Employee::isFullTimeEmployee, Comparator.nullsFirst(Comparator.naturalOrder()))
.compare(emp1, emp2);
result will be 0 because name & fullTime fields are not matching with each other.
But I also want to produce a list of fields which didn't match.. like below
List<String> unmatchedFields = ["name","fulltimeEmployee"];
Can I do it in a nicer way, other than bunch of if() else

Check out DiffBuilder. It can report which items are different.
DiffResult<Employee> diff = new DiffBuilder(emp1, emp2, ToStringStyle.SHORT_PREFIX_STYLE)
.append("name", emp1.getName(), emp2.getName())
.append("age", emp1.getAge(), emp2.getAge())
.append("fulltime", emp1.getFulltime(), emp2.getFulltime())
.build();
DiffResult has, among other things, a getDiffs() method that you can loop over to find what differs.

First of all, I don't think you understand how comparing().thenComparing() works. The result in your case won't be 0. The comparator compares the first statement, if they are equal it goes to the thenComparing() and so on. The result will be 0 if and only if all comparison()/thenComparing() are also equal. I actually wrote about this a few days ago: http://petrepopescu.tech/2021/01/simple-collection-manipulation-in-java-using-lambdas/
Anyway, back to your question, I think you are looking for an equal() where it also returns what fields were not equal. Here is a quick prototype.
public class Pair<T, U> {
private Function<? super T, ? extends Comparable> function;
private String fieldName;
public Pair(Function<? super T, ? extends Comparable> function, String fieldName) {
this.function = function;
this.fieldName = fieldName;
}
}
And the actual comparator:
public class MyComparator {
List<Pair> whatToCompare = Arrays.asList(
new Pair<Employee, String>(Employee::getName, "name"),
new Pair<Employee, String>(Employee::getAge, "age"),
new Pair<Employee, Double>(Employee::isFullTimeEmployee, "fullTimeEmployee")
);
public List<String> compare(Employee e1, Employee e2) {
List<String> mismatch = new ArrayList<>();
for(Pair pair:whatToCompare) {
int result = Comparator.comparing(pair.getFunction()).compare(e1, e2);
if (result != 0) {
mismatch.add(pair.getFieldName());
}
}
return mismatch;
}
}

Comparator itself has no ability to discover out the differences, it only compares two objects of the same type and returns -1, 0 or 1. This is the interface contract.
To discover the actual differences, you need to use Reflection API to get the fields, compare the equality of their real values of two passed objects and let the differences be listed. Here is a generic implementation:
public class Difference<T> {
T left;
T right;
//all-args constructor omitted
public Set<String> differences() throws IllegalAccessException {
// fieldName-field as a key-value for the left and right instances
Map<String, Field> leftFields = Arrays.stream(left.getClass().getDeclaredFields())
.peek(f -> f.setAccessible(true))
.collect(Collectors.toMap(Field::getName, Function.identity()));
Map<String, Field> rightFields = Arrays.stream(left.getClass().getDeclaredFields())
.peek(f -> f.setAccessible(true))
.collect(Collectors.toMap(Field::getName, Function.identity()));
// initialize Set as long as two fields cannot have a same name
Set<String> differences = new HashSet<>();
// list the fields of the left instance
for (Entry<String, Field> entry: leftFields.entrySet()) {
String fieldName = entry.getKey();
Field leftField = entry.getValue();
// find the right instance field matching the name
Field rightField = rightFields.get(fieldName);
// compare their actual values and add among the differences if they aren't equal
if (!leftField.get(left).equals(rightField.get(right))) {
differences.add(fieldName);
}
}
return differences;
}
}
Employee emp1 = Employee.builder().name("john").age(25).fullTime(false).build();
Employee emp2 = Employee.builder().name("Doe").age(25).fullTime(true).build();
Set<String> differences = new Difference<Employee>(emp1, emp2).differences();
System.out.println(differences);
[name, fullTimeEmployee]

Related

Sort a list by another list

I have a little problem that is driving me crazy.
I have a
List<Integer> with ids.
List<ObjectA> with 3 variables:
an id, and two string
I have to sort the second list by putting at the top the elements with id contained in the first list, then by string asc and by the second string asc.
What is the easiest way to make this work? I am trying to use the .sort(), Comparators etc.
An example:
#Getter
#Setter
public class ObjectA {
private Integer id;
private String code;
private String name;
}
// comparator:
static class SortByCode implements Comparator<ObjectA> {
public int compare(ObjectA a, ObjectA b) {
String as = a.getCode();
String bs = b.getCode();
return as.compareTo(bs);
}
}
static class SortByName implements Comparator<ObjectA> {
public int compare(ObjectA a, ObjectA b) {
String as = a.getName();
String bs = b.getName();
return as.compareTo(bs);
}
}
// then in service:
List<Integer> idsPreferred = new ArrayList<>();
List<ObjectA> listObj = new ArrayList<>();
idsPreferred = .... add preferred ids;
listObj = .... add objects;
listObj.sort(new SortByCode()).thenComparing(new SortByName());
With this i sort by code and by name - but i need to add the sorting by the first list - I need the elements that have an id contained in the List to come before the others.
I suppose something like this using chained comparing by extracted key:
listObj.sort(Comparator.comparing(o -> !idsPreferred.contains(((ObjectA) o).getId()))
.thenComparing(o -> ((ObjectA) o).getId())
.thenComparing(o -> ((ObjectA) o).getCode())
.thenComparing(o -> ((ObjectA) o).getName()));
or
listObj.sort(Comparator.comparing(ObjectA::getId,
(id1,id2)-> {if (!((idsPreferred.contains(id1))^idsPreferred.contains(id2)))
return 0;
else return (idsPreferred.contains(id2))?1:-1;})
.thenComparing(ObjectA::getId)
.thenComparing(ObjectA::getCode)
.thenComparing(ObjectA::getName));
The solution will involve 2 steps-
check id of objects from second list, which are present in first list.
Sort the contained objects using either of the solutions suggested- How to sort List of objects by some property

How to use Comparator with conditions in java

I am trying to sort a list based on sort key and sort order I receive from an API.
For example,
I have a list with sortkey and sortorder and based on that I need to sort.
List<SortList> sortlist;
I have a list of an object :
List<Employee> employee;
I am able to sort using
Collections.sort(sourceList, Comparator
.comparing(Employee::getAge).reversed()
.thenComparing(Employee::getCount));
But i need to check the sortfeild on a condition and based on that only the field is considered for sorting.
ex:
if(sortkey = "name")
sortbythatkey from sortlist by the sort order
if (sortkey = "place")
sortbythat key from sortlist by the sort order
So here if sortlist has both name and place then it should sort by both key and order
Any idea how could i achieve this?
Sort List contains:
{
"sortKey":"name",
"sortOrder":"ASC"
},
{
"sortKey":"place",
"sortOrder":"DESC"
}
Requirement is to chain them together like ORDER BY in SQL
Assuming that sortlist is a list of SortCriteria, which is a class like this:
class SortCritera {
private String key;
private String order;
public String getKey() {
return key;
}
public String getOrder() {
return order;
}
// constructors, setters...
}
You first need a HashMap<String, Comparator<Employee>> to store all the corresponding comparators for each possible key:
HashMap<String, Comparator<Employee>> comparators = new HashMap<>();
comparators.put("name", Comparator.comparing(Employee::getName));
comparators.put("age", Comparator.comparing(Employee::getAge));
// ...
Then you can loop through the sortlist and keep calling thenComparing:
Comparator<Employee> comparator = comparators.get(sortlist.get(0).getKey());
if (sortlist.get(0).getOrder().equals("DESC")) {
comparator = comparator.reversed();
}
for(int i = 1 ; i < sortlist.size() ; i++) {
if (sortlist.get(i).getOrder().equals("DESC")) {
comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()).reversed());
} else {
comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()));
}
}
// now you can sort with "comparator".
As Holger has suggested, you can use the Stream API to do this as well:
sortlist.stream().map(sc -> {
Comparator<Employee> c = comparators.get(sc.getKey());
return sc.getOrder().equals("DESC")? c.reversed(): c;
}).reduce(Comparator::thenComparing)
.ifPresent(x -> Collections.sort(originalList, x));
You can create a method which when passed the sort key, you provide the proper Comparator:
public Comparator<Employee> getComparator(String sortKey) {
if("name".equals(sortKey)) {
return Comparator.comparing(Employee::getName);
} else if ("place".equals(sortKey) {
return Comparator.comparing(Employee::getPlace);
} else {
throw new IllegalArgumentException();
}
}
To call it it would simply be:
Collections.sort(sourceList, getComparator(sortKey).reversed()
.thenComparing(Employee::getCount));
While you could also write your own, I find it is better to delegate the "standard" parts and simply write the part that differs from this.
If you find yourself having many such sort keys, then a more suitable means to do this would be to use a map:
private static final Map<String, Comparator<Employee>> COMPARE_MAP = new HashMap<>() {{
put.("name", Comparator.comparing(Employee::getName));
put.("place", Comparator.comparing(Employee::getPlace));
}});
public Comparator<Employee> getComparator(String sortKey) {
if(COMPARE_MAP.containsKey(sortKey)) {
return COMPARE_MAP.get(sortKey);
} else {
throw new IllegalArgumentException();
}
}
Reflection is also an option, but I would be cautious to use reflection unless it becomes impractical to do otherwise. In that case, you could create your own annotation to determine which fields of class Employee can be used for sorting.

Distinguish objects by different fields for different contexts

Let say there is such immutable class:
public class Foo {
private final Long id;
private final String name;
private final LocalDate date;
public Foo(Long id, String name, LocalDate date) {
this.id = id;
this.name = name;
this.date = date;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public LocalDate getDate() {
return date;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
return Objects.equals(getId(), foo.getId()) &&
Objects.equals(getName(), foo.getName()) &&
Objects.equals(getDate(), foo.getDate());
}
#Override
public int hashCode() {
return Objects.hash(getId(), getName(), getDate());
}
}
There is a collection of objects of this class. In some cases, it is required to distinguish only by name and in some cases by name and date.
So pass collection to java.util.Set<Foo> or create java 8 Stream<Foo> calling .distinct() method is not working for this case.
I know it is possible to distinguish using TreeSet and Comparator. It looks like this:
private Set<Foo> distinct(List<Foo> foos, Comparator<Foo> comparator) {
TreeSet<Foo> treeSet = new TreeSet<>(comparator);
treeSet.addAll(foos);
return treeSet;
}
usage:
distinct(foos, Comparator.comparing(Foo::getName)); // distinct by name
distinct(foos, Comparator.comparing(Foo::getName).thenComparing(Foo::getDate)); // distinct by name and date
But I think that is not a good way to do it.
What's the most elegant way to solve this problem?
First, let's consider your current approach, then I'll show a better alternative.
Your current approach is succinct, yet uses a TreeMap when all you need is a TreeSet. If you are OK with the O(nlogn) complexity imposed by the red/black tree structure of TreeMap, I would only change your current code to:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
Set<T> set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
Note that I've made your method generic and static, so that it can be used in a generic way for any collection, no matter the type of its elements. I've also changed the first argument to Collection, so that it can be used with more data structures.
Also, TreeSet still has O(nlogn) time complexity because it uses a TreeMap as its backing structure.
The usage of TreeSet has 3 disadvantages: first, it sorts your elements according to the passed Comparator (maybe you don't need this); second, time complexity is O(nlogn) (which might be way too much if all you require is to have distinct elements); and third, it returns a Set (which might not be the type of collection the caller needs).
So, here's another approach that returns a Stream, which you can then collect to the data-structure you want:
public static <T> Stream<T> distinctBy(
Collection<? extends T> list,
Function<? super T, ?>... extractors) {
Map<List<Object>, T> map = new LinkedHashMap<>(); // preserves insertion order
list.forEach(e -> {
List<Object> key = new ArrayList<>();
Arrays.asList(extractors)
.forEach(f -> key.add(f.apply(e))); // builds key
map.merge(key, e, (oldVal, newVal) -> oldVal); // keeps old value
});
return map.values().stream();
}
This converts every element of the passed collection to a list of objects, according to the extractor functions passed as the varargs argument.
Then, each element is put into a LinkedHashMap with this key and merged by means of preserving the initially put value (change this as per your needs).
Finally, a stream is returned from the values of the map, so that the caller can do whatever she wants with it.
Note: this approach requires that all the objects returned by the extractor functions implement the equals and hashCode methods consistently, so that the list formed by them can be safely used as the key of the map.
Usage:
List<Foo> result1 = distinctBy(foos, Foo::getName)
.collect(Collectors.toList());
Set<Foo> result2 = distinctBy(foos, Foo::getName, Foo::getDate)
.collect(Collectors.toSet());

SortedMap with Comparator ClassCast Exception

Why am getting a ClassCastException at (Person p2 = (Person) o2;) in overridden compare method . :(
Actually Instead of Person Object the values in compare overridden method is coming as "Jim" and "Jack" (Key values). So the Cast Cast Exception . But Why is it coming with keys not values i,e the Person object , Why is it only applied for keys . Are there any other way to sort it based on values .
Please correct me if am wrong
1) We can Pass the comparator object in the TreeMap which will sort it accordingly.?
2) Always the Sorting is performed over Keys . ?
3) How can we sort a Map over its values without using anymore collection object (Is it possible) and why is not supported by default ?
public class HashTableExamples {
/**
* #param args
*/
public static void main(String[] args) {
SortedMap persorSorted = new TreeMap(new Comparator() {
#Override
public int compare(Object o1, Object o2) {
Person p2 = (Person) o2;
return 2;
}
});
Person p = new Person(10);
Person p1 = new Person(20);
persorSorted.put("Jim", p);
persorSorted.put("Jack", p1);
Iterator sortedit = persorSorted.entrySet().iterator();
while (sortedit.hasNext()) {
Map.Entry pairs = (Map.Entry) sortedit.next();
Person pw = (Person) pairs.getValue();
System.out.println("From SortedMap : " + pw.getAge());
}
}
public static class Person {
Person(int agevalue) {
this.age = agevalue;
}
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Yes, TreeMap always sorts on keys.
As to why it's not "supported by default" -- it's because there doesn't exist a data structure in general that supports it efficiently. It's not supported efficiently in any programming language, because the point of a Map is to be able to look things up by their key, and sorting by values means you can't organize the data in a way that makes it efficient to look things up by keys.
If you must sort a Map's entries by value, you can use something like this:
List<Map.Entry<Foo, Bar>> entryList =
new ArrayList<Map.Entry<Foo, Bar>>(map.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<Foo, Bar>>() {
public int compare(Map.Entry<Foo, Bar> entry1, Map.Entry<Foo, Bar> entry2) {
return entry1.getValue().compareTo(entry2.getValue());
}
});
Alternately, if you like, you can use an alternate comparator to compare the values if you don't control the implementation of the value type.
If you look at the documentation for TreeMap you'll see it says:
Constructs a new, empty tree map, ordered according to the given comparator. All keys inserted into the map must be mutually comparable by the given comparator: comparator.compare(k1, k2) must not throw a ClassCastException for any keys k1 and k2 in the map. If the user attempts to put a key into the map that violates this constraint, the put(Object key, Object value) call will throw a ClassCastException.
The main point here is that it's comparing keys, but you're casting the key (ie: a String) into a Person.

Compare 2 Java arraylists of different objects and add the matching rows to a new List

We need to compare 2 arraylists of different objects having some common fields, and then store the matching rows to a new arraylist. I have searched for solutions, but wasn't able to get what I need.
List<Person> personList = new ArrayList<Person>();
Person:
private String firstName;
private String lastName;
private String street1;
private String street2;
private String city;
private String stateCode;
private String zipCode;
List<PersonNpi> npiList = new ArrayList<PersonNpi>();
PersonNpi:
private String name;
private String npi;
private Address address;
So I need to check if the name & address in the PersonNpi object in the PersonNpiList match to a Person object in the PersonList, and if yes save the Person details + Npi to a new Arraylist<Employee>
Hope I'm clear on the question. Please let me know on how to solve this efficiently.
Thanks
Harry
EDIT:
I need to save the non-matching rows (on the first arraylist) as well to another list. Do I need to have another loop or can I do it on the same For loop? Anyone please?
Since I don't see any superclasses from which they extend, you have to manually iterate through your lists. I am assuming a lot, for instance that you have getters and setters for your attributes, that PersonNpi.name is more or less the same as Person.firstname + Person.lastname, that you have some function in Address like boolean checkEquality(String street1, String street2, String city, String state, String zip), that your Person class has a getName() method to compare with PersonNpis. In that case, loop through the first array, and check for every item if the second has anything equal to it.
ArrayList<Employee> employees = new ArrayList<Employee>();
for(Person person : personList) {
for(PersonNpi personNpi : npiList) {
if (person.getName().equals(personNpi.getName()) &&
person.getAddress().checkEquality(...address parts here...)) {
employees.add(new Employee(person, personNpi));
}
}
}
Again, I made a lot of assumptions, also the one that you have an Employee constructor which just requires the Person and the PersonNpi, and gets the required information accordingly.
You should elaborate more, use superclasses, and use the contains() function. In other words, make comparing the Person and the PersonNpi easier through a function.
Edit: your second question is highly, if not extremely dependant on your further implementation of Employee, Person and PersonNpi. For now, I'll yet again assume you have some methods that verify equality between Employee, Person and PersonNpi.
I'd suggest to not do the checking in one loop, since you have two ArrayLists which are ran through. The PersonNpi-list is ran through for every record in the first List. So what might happen is after we checked everything, a few Persons are left unmatched, and a few PersonNpis are left unmatched, since we don't flag which Persons and PersonNpis we've matched.
In conclusion: for easiness' sake, just add this part:
ArrayList<Object> nonMatchedPersons = new ArrayList<Object>();
for (Person person : personList)
if (!employees.contains(person))
nonMatchedPersons.add(person);
for (PersonNpi personNpi : npiList)
if (!employees.contains(personNpi))
nonMatchedPersons.add(personNpi);
This method does require you to implement the equals(Object) method for all 3 person classes, which you might consider putting beneath a superclass like Human. In that case, you can make the Object ArrayList into a ArrayList<Human>
With one loop (requires equals(Object) method for the 3 person classes):
List<Employee> employees = new ArrayList<Employee>();
ArrayList<Object> nonMatchedPersons = new ArrayList<Object>();
Iterator<Person> personIterator = personList.iterator();
while (personIterator.hasNext()) {
Iterator<PersonNpi> npiIterator = npiList.iterator();
while(npiIterator.hasNext()) {
Person person = personIterator.next();
PersonNpi personNpi = npiIterator.next();
if (person.equals(personNpi)) {
employees.add(new Employee(person, personNpi));
personIterator.remove();
npiIterator.remove();
}
}
}
nonMatchedPersons.addAll(personList);
nonMatchedPersons.addAll(npiList);
Explanation: we loop with Iterators through both lists, to enable us to remove from the list while iterating. So in the personList and the npiList, only the singles remain, as we add doubles to the Employee-list, instantly removing them from the other two lists. We add the remaining singles in the two lists to our nonMatchedPerson-list with the addAll method.
Edit2: If you can't edit those classes for whatever reason, make 3 wrapper classes, something like:
public class PersonWrapper {
private Person person;
public PersonWrapper(Person person) {
this.person = person;
}
#override
public boolean equals(Object other) {
if (other == null)
return false;
if (other instanceof PersonWrapper) {
//etc etc, check for equality with other wrappers.
...
}
}
}
If you choose to use this approach, change this line in the loop:
if (person.equals(personNpi)) {
to this:
if (new PersonWrapper(person).equals(new PersonNpiWrapper(personNpi))) {
Using this, you can still implement your own equals() method.
Another solution could be that you make a static method like this:
public static boolean equals(Object this, Object that) {
if (this instanceof Person || this instanceof PersonNpi) //et cetera, et cetera
return true;
return false;
}
Now just call Person.equals(person, personNpi), assuming you put the method in the class Person.
If you implement equals to compare the values under question, you can then use contains to see if object is in other list.
Otherwise you'll have to manually iterate though lists, and check each object.
And if you using jdk8 Lambda, you could do something like this (compiles and runs btw, with correct jdk) :
public static void main(String args[]) throws ParseException {
TransformService transformService = (inputs1, inputs2) -> {
Collection<String> results = new ArrayList<>();
for (String str : inputs1) {
if (inputs2.contains(str)) {
results.add(str);
}
}
return results;
};
Collection<String> inputs1 = new ArrayList<String>(3) {{
add("lemon");
add("cheese");
add("orange");
}};
Collection<String> inputs2 = new
ArrayList<String>(3) {{
add("apple");
add("random");
add("cheese");
}};
Collection<String> results = transformService.transform(inputs1, inputs2);
for (String result : results) {
System.out.println(result);
}
}
public interface TransformService {
Collection<String> transform(Collection<String> inputs1, Collection<String> inputs2);
}
Something like this should work. It assumes that you have a way of constructing an Employee from a Person and a PersonNpi. Also, since you don't tell the structure of an Address, I'll leave it to you to write the address matching logic.
public List<Employee> findCommonElements(List<Person> list1,
List<PersonNpi> list2)
{
List<Employee> common = new ArrayList<Employee>();
for (Person p1 : list1) {
PersonNpi p2 = find(list2, p1);
if (p2 != null) {
common.add(new Employee(p1, p2));
}
}
}
private PersonNpi find(List<PersonNpi> list, Person p) {
for (PersonNpi p2 : list) {
if (matches(p, p2)) {
return p2;
}
}
return null;
}
private boolean matches(Person p1, PersonNpi p2) {
return /* logic for comparing name and address info */;
}
This is an O(n2) operation. You could speed this up considerably by sorting both arrays by name and address. The sorting operation is O(n log(n)) and the comparison could then be implemented as an O(n) operation.
Use HashMap to store the first list PersonNpiList. Use map.get(Person) == null to check whether the person is in the hash map.

Categories