I am iterating over two collections and check if both collections contain
the same elements. I can't use Java 8.
edit 1 year after:
I created the method in the question to check if two Collections contain the same elements, without thinking about the fact that I am passing two Collection implementations into the method.
But Collection does not determine how elements are sorted. And I am iterating over the collections. Thus, some implementation of Collection could save elements in random order, while containing the same elements.
Both collections contain elements that are comparable and the content
is defined as equal, if all elements return a x.compareTo(y) with 0.
Two values are defined as different, if one of them is null, but not the other.
I want to find an elegant way to compare on nullity and prevent
a null check on the final compareTo().
My current implementation:
public static <T extends Comparable<T>> boolean isSame(#Nullable Collection<T> a, #Nullable Collection<T> b) {
if (a == null || b == null) {
return (a == null && b == null);
}
if (a.size() != b.size()) {
return false;
}
Iterator<T> aIt = a.iterator();
Iterator<T> bIt = b.iterator();
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
//both null, don't compare, continue looping...
} else if (aValue.compareTo(bValue) != 0) {
return false;
}
}
return true;
}
I want to continue the while loop, if both values are null, because that is
defined as equal.
But I am struggling with this part:
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
}
Question:
Is there a more elegant and readable way to compare on nullity, do a further compare if both are not null, return false if only one is null, and continue the loop, if both values are null?
The sequence as follows should work well:
if(aValue == null && bValue == null) continue; // both null; continue
if(aValue == null || bValue == null) return false; // any null; return false
if(aValue.compareTo(bValue) != 0) { // both non-null; compare
return false;
}
In Java8, you can build a Comparator that would replace comparison sequence at cost of creating an extra object (you will need to decide if you care about that):
Comparator<T> cmp = Comparator.nullsLast(Comparator.naturalOrder());
The compararor will take care of null comparison for you (since you assume that two nulls are equal):
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (cmp.compare(aValue, bValue) != 0) {
return false;
}
}
Related
I have a <display:table> with potential null values in the columns, and I'd like to configure the sorting so that when a given <display:column> is sorted alphabetically ("A" strings at top), the null values are at the bottom of the table (after "Z" strings).
To do this, I've written a Comparator implementation that should put null values at the end of the list:
public class DefaultComparatorNullsLast implements Comparator<Object>{
private final Collator collator;
public DefaultComparatorNullsLast(){
this(Collator.getInstance());
} // DefaultComparatorNullsLast
public DefaultComparatorNullsLast(Collator collatorToUse){
this.collator = collatorToUse;
this.collator.setStrength(Collator.PRIMARY);
} // DefaultComparatorNullsLast
public int compare(final Object obj0, final Object obj1){
//similar to NullComparator.compare()
if(obj0 == obj1) return 0;
else if(obj0 == null) return 1;
else if(obj1 == null) return -1;
//similar to DefaultComparator.compare()
if(obj0 instanceof String && obj1 instanceof String) return this.collator.compare(obj0, obj1);
else if(obj0 instanceof Comparable && obj1 instanceof Comparable) return ((Comparable<Object>) obj0).compareTo(obj1);
else return this.collator.compare(obj0.toString(), obj1.toString());
} // compare
} // DefaultComparatorNullsLast
Strangely, though, the null values are still displaying at the top of the table. When I debug, I see that my DefaultComparatorNullsLast.compare() method is never called on any null values. Digging into the displaytag code, I can see that TableModel.sortRowList() actually places my Comparator implementation into a RowSorter, which is then passed to Collections.sort():
private void sortRowList(List list)
{
if (isSorted())
{
HeaderCell sortedHeaderCell = getSortedColumnHeader();
if (sortedHeaderCell != null)
{
// If it is an explicit value, then sort by that, otherwise sort by the property...
if (sortedHeaderCell.getBeanPropertyName() != null
|| (this.sortedColumn != -1 && this.sortedColumn < this.headerCellList.size()))
{
String sorted = (sortedHeaderCell.getSortProperty() != null)
? sortedHeaderCell.getSortProperty()
: sortedHeaderCell.getBeanPropertyName();
Collections.sort(list, new RowSorter(
this.sortedColumn,
sorted,
getTableDecorator(),
this.sortOrderAscending,
sortedHeaderCell.getComparator()));
}
}
}
}
And when I look into RowSorter, it seems the checkNullsAndCompare() method actually handles null values opposite how I want them to be handled and only calls my DefaultComparatorNullsLast.compare() method if both values are non-null:
private int checkNullsAndCompare(Object object1, Object object2)
{
int returnValue;
if (object1 == null && object2 != null)
{
returnValue = -1;
}
else if (object1 != null && object2 == null)
{
returnValue = 1;
}
else if (object1 == null && object2 == null)
{
// both null
returnValue = 0;
}
else
{
returnValue = comparator.compare(object1, object2);
}
int ascendingInt = this.ascending ? 1 : -1;
return ascendingInt * returnValue;
}
Essentially, I'm wondering if there's any way around this. Of course, I know I can sort the list in my action code before the JSP renders and use sort="external", but I also want this special sorting of null values to take place when the user clicks a column header to sort by that column's property. Is there any way to get the displaytag library to call my custom Comparator implementation instead of RowSorter.checkNullsAndCompare()?
I'm trying to sort a list by offerValues using compareTo method.
It works fine it offerValue is a single digit like 8.00, but if offerValue is 20.00 or 15.00, it's considering them as 2(for 20.00) and 1(for 15.00). Don't understand it.
Comparator<OfferVO> comparatorAsc = (o1, o2) -> {
if (o1.getOfferValue() == null && o2.getOfferValue() == null) {
return 0;
} else if (o1.getOfferValue() != null) {
return o1.getOfferValue().compareTo(o2.getOfferValue());
} else {
return -1;
}
};
Collections.sort(offersList, comparatorAsc);
OfferVO.class:
public class OfferVO extends OfferBaseVO implements Serializable {
private static final long serialVersionUID = 1L;
private String offerValue;
public String getOfferValue() {
return offerValue;
}
public void setOfferValue(String offerValue) {
this.offerValue = offerValue;
}
}
Output sample:
"offerValue": "5.00",
"offerValue": "3.00",
"offerValue": "3.00",
"offerValue": "20.00",
"offerValue": "15.00",
"offerValue": "15.00",
Strings are compared lexicographical (colloquially alphabetically), not numerically. That means "10" will come before "2" and so on. If you want to compare the numerical values you'll need to parse the strings into numbers, for instance with Double.valueOf().
1) To compare String as numeric value, convert them into numbers in your comparator as String.compare() relies on the lexicographical order.
2) Note that your comparator is not symmetric.
The Comparator.compare() specification states that :
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y,
x)) for all x and y.
Here :
else if (o1.getOfferValue() != null) {
return o1.getOfferValue().compareTo(o2.getOfferValue());
}
You rely on the non nullity of only o1.getOfferValue() to compare the two objects :
To write a more precise numerical comparison about the rounding you should use an epsilon as you compare the floating values such as
float epsilon = 0.0001F;
if (o1.getOfferValue() == null && o2.getOfferValue() == null){
return 0;
} else if (o1.getOfferValue() != null && o2.getOfferValue() != null) {
return Math.abs(Float.valueOf(o1.getOfferValue()) - Float.valueOf(o2.getOfferValue())) < epsilon)
}
// TODO
// as last you have to decide here how to sort if only one of the value is not null
If the rounding is not an issue in the comparison : Float.compare() is enough :
if (o1.getOfferValue() == null && o2.getOfferValue() == null){
return 0;
} else if (o1.getOfferValue() != null && o2.getOfferValue() != null) {
return Float.compare(Float.valueOf(o1.getOfferValue()), Float.valueOf(o2.getOfferValue());
}
Used below code:
Comparator<OfferVO> comparatorDesc = (o1, o2) -> {
if (o1.getOfferValue() == null && o2.getOfferValue() == null) {
return 0;
} else if (o2.getOfferValue() != null) {
return
Double.valueOf(o2.getOfferValue()).compareTo(Double.valueOf(o1.getOfferValue()));
} else {
return -1;
}
};
What is the best way to deal with null values, when doing Collections.sort() on nested objects?
I'd like to sort a couple of objects, basically applying this rule:
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
However, any of these objects can be null (i.e. invoice position, invoice and invoice number).
public class InvoicePos {
private Invoice invoice = null;
// ...
}
public class Invoice {
private String invoiceNo = "";
// ...
}
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
For clarification: I'm aware that my above example may raise NullPointerExceptions. Currently I'm doing the following and basically, I questioned myself, if there is any smarter approach.
Collections.sort(allInvoicePositions, new Comparator<InvoicePos>() {
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
if (null == invoicePosOne && null == invoicePosTwo) {
return 0;
}
if (null == invoicePosOne) {
return -1;
}
if (null == invoicePosTwo) {
return 1;
}
if (null == invoicePosOne.getInvoice() && null == invoicePosTwo.getInvoice()) {
return 0;
}
if (null == invoicePosOne.getInvoice()) {
return -1;
}
if (null == invoicePosTwo.getInvoice()) {
return 1;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo() && null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 0;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo()) {
return -1;
}
if (null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 1;
}
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
});
There is something called as NullComparator in org.apache.commons.collections.jar.
This might help you https://commons.apache.org/proper/commons-collections/javadocs/api-2.1.1/org/apache/commons/collections/comparators/NullComparator.html.
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
If these values don't represent anything in your collection, then the best thing you can do is avoid them; don't allow inserting them, so you won't have to handle them when comparing items.
If you insist to have them, then you must check if they're null to avoid NullPointerException.
If you have null values then you need to handle them explicitly and in a consistent way so to have a valid ordering relation. That is, something like:
compare (a, b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return +1;
return comp(a,b);
}
Don't be tempted to do something like:
compare (a, b) {
if (a == null || b == null) return -1;
return comp(a,b);
}
which would break the ordering relation.
I need to compare two binary search trees and see if they are equal or not.
I developed following code that uses recursion.
private boolean compareTrees(BinaryTreeNode n1, BinaryTreeNode n2)
{
if(n1.getNodeData() != n2.getNodeData())
return false;
else
{
if(n1.left != null && n2.left != null)
compareTrees(n1.left, n2.left);
if(n1.right != null && n2.right != null)
compareTrees(n1.right, n2.right);
}
return true;
}
The problem is that if two nodes are not equal, the method will return false but because I use recursion, the return value will be overridden to true no matter what. I have been stuck with this problem for all day and nothing worked for me. I searched online but I didn't find anything relevant to my code.
Is there any way to break from all nested methods and return value to the first method?
You need to return the result of the subtree comparison:
boolean b1, b2;
if(n1.left != null && n2.left != null)
b1 = compareTrees(n1.left, n2.left);
if(n1.right != null && n2.right != null)
b2 = compareTrees(n1.right, n2.right);
return b1 && b2;
But why not just deal with nulls before-hand?
private boolean compareTrees(BinaryTreeNode n1, BinaryTreeNode n2)
{
if (n1 == null || n2 == null)
return n1 == n2; // i.e. both null
if (n1.getNodeData() != n2.getNodeData())
return false;
return compareTrees(n1.left, n2.left) && compareTrees(n1.right, n2.right);
}
I would do it changing the order:
private boolean compareTrees(BinaryTreeNode n1, BinaryTreeNode n2)
{
boolean equalLeft = false;
boolean equalRight = false;
if(n1.getNodeData() == n2.getNodeData())
{
if(n1.left != null && n2.left != null){
equalLeft = compareTrees(n1.left, n2.left);
} else{
equalLeft = true;
}
if(n1.right != null && n2.right != null){
equalRight = compareTrees(n1.right, n2.right);
} else{
equalRight = true;
}
return equalLeft && equalRight;
} else{
return false;
}
}
Try to face the problem avoiding null values and using equals() method instead of == comparison for your nodes. I shoud do it this way:
private boolean compareTrees(BinaryTreeNode n1, BinaryTreeNode n2){
//avoid nulls :TDD
if (n1==null && n1==n2)
return true;
if ((n1==null && n2!=null) || (n2==null && n1!=null))
return false;
//ensure logic without nulls, comparing with equals() method
boolean areEquals = n1.getNodeData().equals(n2.getNodeData());
//compare left
areEquals = areEquals && compareTrees(n1.left, n2.left);
//if still equals, compare right
if(areEquals) areEquals = areEquals && compareTrees(n1.right, n2.right);
return areEquals;
}
Effectively, your code could reduce to:
private boolean compareTrees(BinaryTreeNode n1, BinaryTreeNode n2)
{
if(n1==null || n2==null) return n1==n2;
return (n1.getNodeData()==n2.getNodeDate()) && compareTrees(n1.left, n2.left) && compareTrees(n1.right, n2.right)
}
I will tell you couple of problems your code has.
Termination criteria when root is null (it will always happen in the end).
Return statements in recursive calls. You are always returning the true in the end.
PS: If you add NULL checks (explained in 1), you need not to add null checks in the subsequent recursive calls. Now the second half of your code will look like:
return compareTrees(n1.left, n2.left) && compareTrees(n1.right, n2.right);
I have an array of a custom type that I want to sort by one of its String attributes. For some reason, the following code is producing wrong results. Could you point out where I might have made a mistake?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if(p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else if(p1_LN.equals(p2_LN))
return 0;
else if(p1_LN.compareTo(p2_LN) > 0)
return -1;
else
return +1;
}
}
One problem to start with - your comparator is inconsistent if you give it two patients with null names, or two null patient references. In particular:
Patient p1 = null;
Patient p2 = null;
int x = comparator.compare(p1, p2);
int y = comparator.compare(p2, p1);
The signs of x and y ought to be different - but they'll both be -1.
After that, it depends on how you want to compare the names. I would usually use
return p1_LN.compareTo(p2_LN);
if you want to sort in ascending order. Note that to sort in descending order you shouldn't just return -p1_LN.compareTo(p2_LN), as if the comparison returns the Integer.MIN_VALUE, the negation won't work. Instead you'd want to return p2_LN.compareTo(p1_LN);.
Note that if you're using this scheme, you don't need to call p1_LN.equals(p2_LN) either - that will be handled by the compareTo call.
You want patient to be ordered by alphabetical by last name, null patients and null last names up front?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if (p1_LN == null && p2_LN == null)
return 0;
else if (p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else
return p1_LN.compareTo(p2_LN);
}
}
To be stable, it really should order by some other fields, like first name, when last names are equal.
I'm assuming you want natural string ordering for this.
First of all, as it is, your compareTo branch is giving inversed results. Don't know if that's what you intended or not (as in you're saying p1 is greater than p2 when the p1's string is lower than p2's).
Furthermore, you can ditch the .equals branch of the if. The compareTo already handles this case.
Therefore a simple
if(p2_LN == null && p1_LN == null)
return 0;
else if(p1_LN == null)
return +1;
else if(p2_LN == null)
return -1;
else return p1_LN.compareTo(p2_LN)
would suffice.
I would use Guava's Ordering class for this:
class Patient {
// ...
public static final Function<Patient, String> GET_LAST_NAME =
new Function<Patient, String>() {
public String apply(Patient from) {
if (from == null) return null;
return from.last_name;
}
};
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.nullsFirst();
}
This will resolve the issue with inconsistent comparison of nulls. It also makes it easy to add a secondary order (e.g. first name):
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.compound(Ordering.natural().onResultOf(GET_FIRST_NAME))
.nullsFirst();