Pre-TL;DR: Need to see if a comparator is an instance of (or equal to) another comparator, even if it is reversed.
Hello, I think this problem is very simple but I am just thinking too much into it.
I have a program that allows a user to sort lists by different comparators. The user can choose ascending or descending order. Since comparators are by default in ascending order, all I do to achieve descending order is create a new reversed comparator:
Comparator newReversedCMP = Collections.reverseOrder(Comparator originalComp)
Another part of the program saves the last comparator used so that the user can sort or view the last comparator used. Let's say I have comparators dealing with names and salaries and a method that returns what it deals with, for example:
if (lastCMPUsed.equals(new NameComparator()){
return "Name";
}
if (lastCMPUsed.equals(new SalaryComparator()){
return "Salary";
}
Sorry for the long intro but my issue is that I also want to return "Name" or "Salary" even if it's a reverse of the original Name or Salary comparator. If lastCMPUsed is a reversedOrder CMP, then it does not find them equal.
I have tried this but with no success:
//lastCMPUsed = Collections.reversedOrder(new NameComparator());
if (lastCMPUsed.equals(Collections.reverseOrder(new NameComparator())
return "Name";
This does not return "Name" and sees them as not equal.
My equals method in each comparator is a simple instanceof check.
Thanks for reading.
If you need some particular definition of equality besides one defined by the supertype, you should write a class structure that will properly do this for you.
For example:
public abstract class PersonComparator implements Comparator<Person> {
private final boolean isReversed;
private PersonComparator(boolean isReversed) {
this.isReversed = isReversed;
}
public boolean isReversed() {
return isReversed;
}
#Override
public int compare(Person lhs, Person rhs) {
return isReversed ? compareImpl(rhs, lhs) : compareImpl(lhs, rhs);
}
public abstract String getComparison();
protected abstract int compareImpl(Person lhs, Person rhs);
public static final class Name extends PersonComparator {
private Name(boolean isReversed) { super(isReversed); }
#Override
protected int compareImpl(Person lhs, Person rhs) {
return lhs.getName().compareTo(rhs.getName());
}
#Override
public String getComparison() {
return "Name";
}
}
public static final Name NAME = new Name(false);
public static final Name NAME_REVERSE = new Name(true);
}
The equality here is that:
NAME.getClass() == NAME_REVERSE.getClass() // true
NAME instanceof PersonComparator.Name //
&& // true
NAME_REVERSE instanceof PersonComparator.Name //
NAME == NAME_REVERSE // false
NAME.equals(NAME_REVERSE); // false
NAME.getComparison() //
.equals(NAME_REVERSE.getComparison()) // true
Note that I did not need to override equals and hashCode, because there is only a single instance of each possible Comparator.
Related
I have an ArrayList of Objects. I want to see if that ArrayList contains an Object with a particular field.
TeamBuilder.java
public class TeamBuilder {
public static void main(String[] args) {
Team team = new Team();
team.addMember(new TeamMember("myID"));
System.out.println(team.containsMember("myID")); //false
}
}
TeamMember.java
public class TeamMember {
private String id;
public TeamMember(String id) {
this.id = id;
}
public String getID() {
return this.id;
}
#Override
public boolean equals(Object o) {
if (o instanceof TeamMember) {
o = ((TeamMember) o).getID();
}
return o.equals(this.getID());
}
}
Team.java
import java.util.ArrayList;
public class Team {
private ArrayList<TeamMember> members = new ArrayList<>();
public boolean addMember(TeamMember teamMember) {
if (members.contains(teamMember)) {
return false;
}
members.add(teamMember);
return true;
}
public boolean containsMember(String eid) {
System.out.println(members.get(0).equals(eid)); //true
System.out.println(members.contains(eid)); //false
if (members.contains(eid)) {
return true;
}
return false;
}
}
I do not want to use a loop and I do not want to overwrite arrayList.contains().
I was expecting .contains() to iterate through my list of TeamMember's and return true when it found one that was equal to the Object passed. The two Objects are equal, but the .contains() method is returning false.
How can I elegantly check to see if a Team contains a TeamMember with the specified ID? I was under the impression I could avoid a for loop because of Java method: Finding object in array list given a known attribute value, but I am not able to get it to work.
The most elegant solution is to
First and foremost fix your equals method so that it fulfills equals contract --- meaning if a.equals(b) then b.equals(a) must be true.
You should never have a TeamMember object be equal to a String. That will result in the possibility of hard to debug side effects and bugs, that you really don't want.
The equals method should never throw an exception. He should first check for reference equality, then class sameness, then field equality.
Your TeamMember class should also override hashCode() and it should use the same fields as the equals method does.
then set up a HashMap<String, TeamMember> that matches ID Strings TeamMember objects.
Be sure that your ID's are immutable.
Fill your Map with String-TeamMember pairs
Call get(String key) when you need a TeamMember that matches an ID.
I have to add User identified by his id into set and in runtime all users form that set have to be sorted by this id.
I've created TreeSet added some User objects and tried to iterate through it.
Here is my attempt:
//irrelevant code removed
TreeSet<User> userSet = new TreeSet<User>();
userSet.add(new User(2));
userSet.add(new User(1));
userSet.add(new User(3));
Iterator<User> iterator = userSet.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
I wrote class User where is one of the fields id and constructor have id as parameter.
public class User {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
}
When i run this code I get ClassCastException.
Here is the stacktrace:
Exception in thread "main" java.lang.ClassCastException: OrderedUsers$User cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at java.util.TreeSet.add(TreeSet.java:255)
at OrderedUsers.<init>(OrderedUsers.java:9)
at Main.main(Main.java:6)
What I am doing wrong?
You are on the right way when you decided to use TreeSet because with TreeSet you can get ordered output. But...
Note that if you use TreeSet, because of TreeSet is sorted you have to implement Comparable.
When you implement Comparable you will get what you expected.
I suggest that you perform changes like this:
public class User implements Comparable<User> {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
#Override
public int compareTo(User u) {
return id - u.id;
}
}
Either pass a custom Comparator to TreeSet constructor or implement Comparable in your model class
TreeSet maintains sorted order and it needs to know how Users can be compared
Here's the statement at TreeMap.java 1188:
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
So if comparator is null, then it will try to cast the set member to a (Comparable). If your class doesn't implement Comparable, this will cause the ClassCastException you saw. comparator is non-null only if you call the TreeMap constructor that provides a Comparator (or if you copy it from another SortedMap that already has a comparator).
TreeSet internally stores the object by performing comparasions with the existing ones similar to Binary Search Tree (actually its a Red-Black tree). So you need to implement interface Comparable in User or provide a custom Comparator to the TreeSet.
If you dont want the user objects to be stored in a sorted order I would recommend using ArrayList.
Method 1 :
public class User implements Comparable<User>
{
public int compare(User u)
{
if( u == null)
return 1;
return id - u.id;
}
}
Method 2 :
public class CompareUsers implements Comparator<User>
{
public int compareTo(User a, User b)
{
if(a == null)
return -1;
if(b == null)
return 1;
return a.id - b.id;
}
}
// Create an instance of this comparator class and pass to the TreeSet
// during initialization.
TreeSet<User> userSet = new TreeSet<User>(new CompareUsers());
I am using a map and want to use a value object as a map key..and a list as value. The value object has 2 properties first name, second name..i want to return map.containsKey() as true if both properties matched by some key in the same map..
I tried to use comparator as below
public class comaparatorEx implements Comparator<Test>{
public static void main(String args[]){
Map m= new HashMap<Test,List<String>>();
Test t = new Test();
t.setFirstname("vamsi");
t.setSecondname("priya");
List descriptionList=new ArrayList();
descriptionList.add("description1");
m.put(t, descriptionList);
Test t2 = new Test();
t2.setFirstname("vamsi");
t2.setSecondname("priya");
if(m.containsKey(t2)){
System.out.println("user found");
}
}
public int compare(Test o1, Test o2) {
if((o1.firstname.equals(o2.firstname) )&& o1.secondname.equals(o2.secondname))
return 0;
else return 1;
}
}
this is the value object i am using
public class Test {
String firstname;
String secondname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getSecondname() {
return secondname;
}
public void setSecondname(String secondname) {
this.secondname = secondname;
}
}
But it returns false for me..please help me ..thanks in advance
For a HashMap, you need to overwrite equals and hashCode in your class.
Possible implementation:
class Test
{
...
#Override
public int hashCode()
{
return 31*firstname.hashCode() + secondname.hashCode();
}
#Override
public boolean equals(Object obj)
{
// basic type validation
if (!(obj instanceof Test))
return false;
Test t = (Test)obj;
return firstname.equals(t.firstname) && secondname.equals(t.secondname);
}
}
Comparator is for comparison-based collections such as TreeMap. To use this, supply an instance of this class in the constructor:
Map m = new TreeMap<Test,List<String>>(new comaparatorEx());
But there is a problem with your compare function - there needs to be logical ordering between the elements (there isn't as you never return -1). String has a compareTo, which you can just use:
public int compare(Test o1, Test o2) {
int result = o1.firstname.compareTo(o2.firstname);
if (result == 0)
return o1.secondname.compareTo(o2.secondname));
else
return result;
}
HashMap uses the hashCode() and equals() methods, internally, to determine e.g. what buckets to look in, and whether the objects in that bucket are the same. You will need to implement both for your Test class, otherwise it will effectively default to reference equality (i.e. are they the exact same object)
You need override the hashcode() and equals() methods to give meaningful equality between the Test object.
HashMap insertions is bassed on the hashcode.
When we pass an both key and value to put() method to store on HashMap , it uses key object hashcode() method to calculate hashcode and they by applying hashing on that hashcode it identifies bucket location for storing value object and keys equals () method will be used to identify correct key value pair in HashMap .
Read more: http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html#ixzz2fDozSqmi
you would have to override the default equals method in your test class.
you can write something like this.
#Override
public boolean equals(Object o) {
if(null != o && o instanceof test && o.attr1.equals(this.attr1)) return true;
else return false;
}
containskey In map looks at the equals method. More info in the java docs
The implementation of equals i have given is just an example. For a proper implementation you should read this
I have a class Arraylist contains value
String word, String expandedword, double confidence, double support
I want to sort the arraylist based on the confidence, and then based on the support.
I have succeed sort the arraylist based on confidence, but i failed to make a new method to sort the arraylist based on the support
this is my code to sort it based on confidence
public class ExpandedTerm implements Comparable<ExpandedTerm> {
String word;
String expandedWord;
double support;
double confidence;
public ExpandedTerm (String word,String expandedWord, double confidence,double support){
this.word = word;
this.expandedWord = expandedWord;
this.support = support;
this.confidence = confidence;
}
public String getWord(){
return word;
}
public String expandedWord(){
return expandedWord;
}
public Double getSupport(){
return support;
}
public Double getConfidence(){
return confidence;
}
#Override
public int compareTo(ExpandedTerm conf) {
return new Double(this.confidence).compareTo(new Double(conf.confidence));
}
I failed to make another method like compareTo, to sort it based on the support value.
How to sort it first by the confidence, and then make another method to sort it by the support value?
User Comparator for this. As compaarble provide functionality to sort on single type basis.
here is link where you found when to use comparable and comapartor
http://iandjava.blogspot.in/2012/10/comparable-and-comparator.html
Use multiple comparartor
one for confidence
public class ConfidanceComparator implements Comparator<ExpandedTerm> {
#Override
public int compare(final ExpandedTerm o1, final ExpandedTerm o2) {
return new Double(o1.confidence).compareTo(new Double(o2.confidence));
}
}
one for support
public class SupportComparator implements Comparator<ExpandedTerm> {
#Override
public int compare(final ExpandedTerm o1, final ExpandedTerm o2) {
return new Double(o1.support).compareTo(new Double(o2.support));
}
}
and use Collections.sort(<List>, <comparator>) adn you will get the desired list.
THis is only required when you want to sort either on confidance basis or support basis.
But if you required then first sort on confidance basis and if confidance is equal then check for support basis. then comparable is sufficient and is
public int compareTo(ExpandedTerm conf) {
int compare = new Double(this.confidence).compareTo(new Double(conf.confidence));
if (compare == 0) {
compare = new Double(this.support).compareTo(new Double(conf.support));
}
return compare;
}
Try this code for your compareTo method:
#Override
public int compareTo(ExpandedTerm other) {
Double thisConfidence = new Double(getConfidence());
Double otherConfidence = new Double(other.getConfidence());
int compare = thisConfidence.compareTo(otherConfidence);
if (compare == 0) {
Double thisSupport = new Double(getSupport());
Double otherSupport = new Double(other.getSupport());
compare = thisSupport.compareTo(otherSupport);
}
return compare;
}
Basically only compare "support" if "confidence" is equal.
I saw your reply that you want to sort once and sort again except differently, so I'm assuming you want to add a custom Comparator when you sort. Is this what you're looking for?
public static void main(String[] args) {
ExpandedTerm term1 = new ExpandedTerm("a", "b", 1, 4);
ExpandedTerm term2 = new ExpandedTerm("c", "d", 3, 2);
List<ExpandedTerm> list = new ArrayList();
list.add(term1);
list.add(term2);
Collections.sort(list);
System.out.println(list);
Collections.sort(list, new Comparator<ExpandedTerm>() {
#Override
public int compare(ExpandedTerm o1, ExpandedTerm o2) {
return new Double(o2.confidence).compareTo(new Double(o1.confidence));
}
});
System.out.println(list);
}
Here's the output
[ExpandedTerm#2eeb3c84, ExpandedTerm#55d2162c]
[ExpandedTerm#55d2162c, ExpandedTerm#2eeb3c84]
Some other tips: make sure you implement the toString(), hashCode(), and equals() functions for your ExpandedTerm. These are essential for debugging and also for use in other Collections like HashMap, etc.
I have created a Vector object to store data in Table object as Vector<Table>. Vector<Table> contains components as below.
[Vector<Record> records, String tableName, String keyColumnName, int recordCount, int columnCount]
I need to sort tableName in above Vector to my own order and return Vector<Table> with sorted tableNames for other processes.
I have wrote method as below.
private Vector<Table> orderTables(Vector<Table> loadTables) {
List<String> tableNames = new ArrayList<String>();
for (Table table : loadTables) {
String tblName = table.getTableName();
tableNames.add(tblName);
}
Collections.sort(tableNames, new MyComparable());
return null;
}
But I have no idea about how to write Comparator to this. My own sort order is stored in .properties file. I can read it and get value. But I have no idea about how to compare it.
How could I do it?
Before clarification
You need to write a Comparator for Table objects that delegates to the tableName's comparator:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return one.getTableName().compareTo(two.getTableName());
}
}
Note that this will consider Tables that have the same name to be equal. This can mess things up if you put these tables in a HashMap or HashSet. To avoid this, you can detect this case and return one.hashCode() - two.hashCode() if the table names are the same.
Guava's ComparisonChain is a convenient way to write such multi-stage comparisons:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return ComparisonChain.start()
.compare(one.getTableName(), two.getTableName())
.compare(one.hashCode(), two.hashCode())
.result();
}
}
After clarification
Okay, the question is to impose a predefined sorting order rather than sorting the Tables by name. In that case, you need to make a Comparator that is aware of the ordering defined in the .properties file.
One way to achieve this is to initialize a mapping of table names to sorting order indices, and refer that mapping during the comparison. Given the property value:
SORT_ORDER = SALES,SALE_PRODUCTS,EXPENSES,EXPENSES_ITEMS
The mapping should look like:
{
SALES: 0,
SALE_PRODUCTS: 1,
EXPENSES: 2,
EXPENSES_ITEMS: 3
}
Here's what the comparator would look like:
private static class PredefinedOrderComparator implements Comparator<Table> {
public PredefinedOrderComparator() {
// Initialize orderIndex here
}
private final Map<String, Integer> orderIndex;
#Override public int compare(Table one, Table two) {
return orderIndex.get(one.getTableName()) - orderIndex.get(two.getTableName());
}
}
To populate orderIndex from the property value, you need to:
Get the comma-separated list using getProperty() as you mentioned
Split that value on comma (I recommend using Guava's Splitter, but String.split or others will work too)
Initialize a new HashMap<String, Integer> and an int index = 0
Iterate through the split tokens, map the current token to index and increment index
Note the implicit assumption that none of the table names have a comma in it.
public class MyComparable implements Comparator<Table>{
#Override
public int compare(Table table1, Table table2) {
return (table1.getTableName().compareTo(table2.getTableName());
}
}
make sure that you have overridden the hashcode and equals in Table class to achieve this.
I wrote you a very simple example on how to work with a Comparator. If you create a class called Main, copy paste below contents in it, compile and run it, you can see what's going on.
A comparator just needs to implement an interface. For this it needs to implement one method (public int compare(T arg0, T arg1). There you specify how a collection will get sorted; in this case according to the alfabet.
I hope this helps you.
import java.util.*;
public class Main {
public static void main(String[] args) {
System.out.println("Start\n");
List<Item> items = new ArrayList<Item>();
for(String s : new String[]{"mzeaez", "xcxv", "hjkhk", "azasq", "iopiop"}) {
items.add(createItem(s));
}
System.out.println("Items before sort:");
System.out.println(Item.toString(items));
Collections.sort(items, new ItemComparator());
System.out.println("Items after sort:");
System.out.println(Item.toString(items));
System.out.println("End");
}
private static Item createItem(String s) {
Item item = new Item();
item.setS(s);
return item;
}
}
class Item {
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
#Override
public String toString() {
return "Item: " + s;
}
public static String toString(Collection<Item> items) {
String s = "";
for(Item item : items) {
s += item + "\n";
}
return s;
}
}
class ItemComparator implements Comparator<Item> {
#Override
public int compare(Item item1, Item item2) {
return item1.getS().compareTo(item2.getS());
}
}