I learned how to use the comparable but I'm having difficulty with the Comparator. I am having a error in my code:
Exception in thread "main" java.lang.ClassCastException: New.People cannot be cast to java.lang.Comparable
at java.util.Arrays.mergeSort(Unknown Source)
at java.util.Arrays.sort(Unknown Source)
at java.util.Collections.sort(Unknown Source)
at New.TestPeople.main(TestPeople.java:18)
Here is my code:
import java.util.Comparator;
public class People implements Comparator {
private int id;
private String info;
private double price;
public People(int newid, String newinfo, double newprice) {
setid(newid);
setinfo(newinfo);
setprice(newprice);
}
public int getid() {
return id;
}
public void setid(int id) {
this.id = id;
}
public String getinfo() {
return info;
}
public void setinfo(String info) {
this.info = info;
}
public double getprice() {
return price;
}
public void setprice(double price) {
this.price = price;
}
public int compare(Object obj1, Object obj2) {
Integer p1 = ((People) obj1).getid();
Integer p2 = ((People) obj2).getid();
if (p1 > p2) {
return 1;
} else if (p1 < p2){
return -1;
} else {
return 0;
}
}
}
import java.util.ArrayList;
import java.util.Collections;
public class TestPeople {
public static void main(String[] args) {
ArrayList peps = new ArrayList();
peps.add(new People(123, "M", 14.25));
peps.add(new People(234, "M", 6.21));
peps.add(new People(362, "F", 9.23));
peps.add(new People(111, "M", 65.99));
peps.add(new People(535, "F", 9.23));
Collections.sort(peps);
for (int i = 0; i < peps.size(); i++){
System.out.println(peps.get(i));
}
}
}
I believe it has to do something with the casting in the compare method but I was playing around with it and still could not find the solution
There are a couple of awkward things with your example class:
it's called People while it has a price and info (more something for objects, not people);
when naming a class as a plural of something, it suggests it is an abstraction of more than one thing.
Anyway, here's a demo of how to use a Comparator<T>:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, new LexicographicComparator());
System.out.println(people);
Collections.sort(people, new AgeComparator());
System.out.println(people);
}
}
class LexicographicComparator implements Comparator<Person> {
#Override
public int compare(Person a, Person b) {
return a.name.compareToIgnoreCase(b.name);
}
}
class AgeComparator implements Comparator<Person> {
#Override
public int compare(Person a, Person b) {
return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
}
}
class Person {
String name;
int age;
Person(String n, int a) {
name = n;
age = a;
}
#Override
public String toString() {
return String.format("{name=%s, age=%d}", name, age);
}
}
EDIT
And an equivalent Java 8 demo would look like this:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, (a, b) -> a.name.compareToIgnoreCase(b.name));
System.out.println(people);
Collections.sort(people, (a, b) -> a.age < b.age ? -1 : a.age == b.age ? 0 : 1);
System.out.println(people);
}
}
Here's a super short template to do the sorting right away :
Collections.sort(people, new Comparator<Person>() {
#Override
public int compare(final Person lhs, Person rhs) {
// TODO return 1 if rhs should be before lhs
// return -1 if lhs should be before rhs
// return 0 otherwise (meaning the order stays the same)
}
});
If it's hard to remember, try to just remember that it's similar (in terms of the sign of the number) to:
lhs-rhs
That's in case you want to sort in ascending order : from smallest number to largest number.
Use People implements Comparable<People> instead; this defines the natural ordering for People.
A Comparator<People> can also be defined in addition, but People implements Comparator<People> is not the right way of doing things.
The two overloads for Collections.sort are different:
<T extends Comparable<? super T>> void sort(List<T> list)
Sorts Comparable objects using their natural ordering
<T> void sort(List<T> list, Comparator<? super T> c)
Sorts whatever using a compatible Comparator
You're confusing the two by trying to sort a Comparator (which is again why it doesn't make sense that Person implements Comparator<Person>). Again, to use Collections.sort, you need one of these to be true:
The type must be Comparable (use the 1-arg sort)
A Comparator for the type must be provided (use the 2-args sort)
Related questions
When to use Comparable vs Comparator
Sorting an ArrayList of Contacts
Also, do not use raw types in new code. Raw types are unsafe, and it's provided only for compatibility.
That is, instead of this:
ArrayList peps = new ArrayList(); // BAD!!! No generic safety!
you should've used the typesafe generic declaration like this:
List<People> peps = new ArrayList<People>(); // GOOD!!!
You will then find that your code doesn't even compile!! That would be a good thing, because there IS something wrong with the code (Person does not implements Comparable<Person>), but because you used raw type, the compiler didn't check for this, and instead you get a ClassCastException at run-time!!!
This should convince you to always use typesafe generic types in new code. Always.
See also
What is a raw type and why shouldn't we use it?
For the sake of completeness, here's a simple one-liner compare method:
Collections.sort(people, new Comparator<Person>() {
#Override
public int compare(Person lhs, Person rhs) {
return Integer.signum(lhs.getId() - rhs.getId());
}
});
Java 8 added a new way of making Comparators that reduces the amount of code you have to write, Comparator.comparing. Also check out Comparator.reversed
Here's a sample
import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import static org.junit.Assert.assertTrue;
public class ComparatorTest {
#Test
public void test() {
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("A", 1000));
peopleList.add(new Person("B", 1));
peopleList.add(new Person("C", 50));
peopleList.add(new Person("Z", 500));
//sort by name, ascending
peopleList.sort(Comparator.comparing(Person::getName));
assertTrue(peopleList.get(0).getName().equals("A"));
assertTrue(peopleList.get(peopleList.size() - 1).getName().equals("Z"));
//sort by name, descending
peopleList.sort(Comparator.comparing(Person::getName).reversed());
assertTrue(peopleList.get(0).getName().equals("Z"));
assertTrue(peopleList.get(peopleList.size() - 1).getName().equals("A"));
//sort by age, ascending
peopleList.sort(Comparator.comparing(Person::getAge));
assertTrue(peopleList.get(0).getAge() == 1);
assertTrue(peopleList.get(peopleList.size() - 1).getAge() == 1000);
//sort by age, descending
peopleList.sort(Comparator.comparing(Person::getAge).reversed());
assertTrue(peopleList.get(0).getAge() == 1000);
assertTrue(peopleList.get(peopleList.size() - 1).getAge() == 1);
}
class Person {
String name;
int age;
Person(String n, int a) {
name = n;
age = a;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
}
For the sake of completeness.
Using Java8
people.sort(Comparator.comparingInt(People::getId));
if you want in descending order
people.sort(Comparator.comparingInt(People::getId).reversed());
You want to implement Comparable, not Comparator. You need to implement the compareTo method. You're close though. Comparator is a "3rd party" comparison routine. Comparable is that this object can be compared with another.
public int compareTo(Object obj1) {
People that = (People)obj1;
Integer p1 = this.getId();
Integer p2 = that.getid();
if (p1 > p2 ){
return 1;
}
else if (p1 < p2){
return -1;
}
else
return 0;
}
Note, you may want to check for nulls in here for getId..just in case.
Two corrections:
You have to make an ArrayList of People objects:
ArrayList<People> preps = new ArrayList<People>();
After adding the objects to the preps, use:
Collections.sort(preps, new CompareId());
Also, add a CompareId class as:
class CompareId implements Comparator {
public int compare(Object obj1, Object obj2) {
People t1 = (People)obj1;
People t2 = (People)obj2;
if (t1.marks > t2.marks)
return 1;
else
return -1;
}
}
Here's an example of a Comparator that will work for any zero arg method that returns a Comparable. Does something like this exist in a jdk or library?
import java.lang.reflect.Method;
import java.util.Comparator;
public class NamedMethodComparator implements Comparator<Object> {
//
// instance variables
//
private String methodName;
private boolean isAsc;
//
// constructor
//
public NamedMethodComparator(String methodName, boolean isAsc) {
this.methodName = methodName;
this.isAsc = isAsc;
}
/**
* Method to compare two objects using the method named in the constructor.
*/
#Override
public int compare(Object obj1, Object obj2) {
Comparable comp1 = getValue(obj1, methodName);
Comparable comp2 = getValue(obj2, methodName);
if (isAsc) {
return comp1.compareTo(comp2);
} else {
return comp2.compareTo(comp1);
}
}
//
// implementation
//
private Comparable getValue(Object obj, String methodName) {
Method method = getMethod(obj, methodName);
Comparable comp = getValue(obj, method);
return comp;
}
private Method getMethod(Object obj, String methodName) {
try {
Class[] signature = {};
Method method = obj.getClass().getMethod(methodName, signature);
return method;
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
private Comparable getValue(Object obj, Method method) {
Object[] args = {};
try {
Object rtn = method.invoke(obj, args);
Comparable comp = (Comparable) rtn;
return comp;
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
}
public static Comparator<JobSet> JobEndTimeComparator = new Comparator<JobSet>() {
public int compare(JobSet j1, JobSet j2) {
int cost1 = j1.cost;
int cost2 = j2.cost;
return cost1-cost2;
}
};
The solution can be optimized in following way:
Firstly, use a private inner class as the scope for the fields is to be the enclosing class TestPeople so as the implementation of class People won't get exposed to outer world. This can be understood in terms of creating an APIthat expects a sorted list of people
Secondly, using the Lamba expression(java 8) which reduces the code, hence development effort
Hence code would be as below:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class TestPeople {
public static void main(String[] args) {
ArrayList<People> peps = new ArrayList<>();// Be specific, to avoid
// classCast Exception
TestPeople test = new TestPeople();
peps.add(test.new People(123, "M", 14.25));
peps.add(test.new People(234, "M", 6.21));
peps.add(test.new People(362, "F", 9.23));
peps.add(test.new People(111, "M", 65.99));
peps.add(test.new People(535, "F", 9.23));
/*
* Collections.sort(peps);
*
* for (int i = 0; i < peps.size(); i++){
* System.out.println(peps.get(i)); }
*/
// The above code can be replaced by followin:
peps.sort((People p1, People p2) -> p1.getid() - p2.getid());
peps.forEach((p) -> System.out.println(" " + p.toString()));
}
private class People {
private int id;
#Override
public String toString() {
return "People [id=" + id + ", info=" + info + ", price=" + price + "]";
}
private String info;
private double price;
public People(int newid, String newinfo, double newprice) {
setid(newid);
setinfo(newinfo);
setprice(newprice);
}
public int getid() {
return id;
}
public void setid(int id) {
this.id = id;
}
public String getinfo() {
return info;
}
public void setinfo(String info) {
this.info = info;
}
public double getprice() {
return price;
}
public void setprice(double price) {
this.price = price;
}
}
}
Here is a lambda version of comparator. This will sort a string list according to length.
Collections.sort(str, (str1, str2) -> {
if(str1.length() < str2.length())
return 1;
else if(str2.length() < str1.length())
return -1;
else
return 0;
});
You should use the overloaded sort(peps, new People()) method
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test
{
public static void main(String[] args)
{
List<People> peps = new ArrayList<>();
peps.add(new People(123, "M", 14.25));
peps.add(new People(234, "M", 6.21));
peps.add(new People(362, "F", 9.23));
peps.add(new People(111, "M", 65.99));
peps.add(new People(535, "F", 9.23));
Collections.sort(peps, new People().new ComparatorId());
for (int i = 0; i < peps.size(); i++)
{
System.out.println(peps.get(i));
}
}
}
class People
{
private int id;
private String info;
private double price;
public People()
{
}
public People(int newid, String newinfo, double newprice) {
setid(newid);
setinfo(newinfo);
setprice(newprice);
}
public int getid() {
return id;
}
public void setid(int id) {
this.id = id;
}
public String getinfo() {
return info;
}
public void setinfo(String info) {
this.info = info;
}
public double getprice() {
return price;
}
public void setprice(double price) {
this.price = price;
}
class ComparatorId implements Comparator<People>
{
#Override
public int compare(People obj1, People obj2) {
Integer p1 = obj1.getid();
Integer p2 = obj2.getid();
if (p1 > p2) {
return 1;
} else if (p1 < p2){
return -1;
} else {
return 0;
}
}
}
}
Here is my answer for a simple comparator tool
public class Comparator {
public boolean isComparatorRunning = false;
public void compareTableColumns(List<String> tableNames) {
if(!isComparatorRunning) {
isComparatorRunning = true;
try {
for (String schTableName : tableNames) {
Map<String, String> schemaTableMap = ComparatorUtil.getSchemaTableMap(schTableName);
Map<String, ColumnInfo> primaryColMap = ComparatorUtil.getColumnMetadataMap(DbConnectionRepository.getConnectionOne(), schemaTableMap);
Map<String, ColumnInfo> secondaryColMap = ComparatorUtil.getColumnMetadataMap(DbConnectionRepository.getConnectionTwo(), schemaTableMap);
ComparatorUtil.publishColumnInfoOutput("Comparing table : "+ schemaTableMap.get(CompConstants.TABLE_NAME));
compareColumns(primaryColMap, secondaryColMap);
}
} catch (Exception e) {
ComparatorUtil.publishColumnInfoOutput("ERROR"+e.getMessage());
}
isComparatorRunning = false;
}
}
public void compareColumns(Map<String, ColumnInfo> primaryColMap, Map<String, ColumnInfo> secondaryColMap) {
try {
boolean isEqual = true;
for(Map.Entry<String, ColumnInfo> entry : primaryColMap.entrySet()) {
String columnName = entry.getKey();
ColumnInfo primaryColInfo = entry.getValue();
ColumnInfo secondaryColInfo = secondaryColMap.remove(columnName);
if(secondaryColInfo == null) {
// column is not present in Secondary Environment
ComparatorUtil.publishColumnInfoOutput("ALTER", primaryColInfo);
isEqual = false;
continue;
}
if(!primaryColInfo.equals(secondaryColInfo)) {
isEqual = false;
// Column not equal in secondary env
ComparatorUtil.publishColumnInfoOutput("MODIFY", primaryColInfo);
}
}
if(!secondaryColMap.isEmpty()) {
isEqual = false;
for(Map.Entry<String, ColumnInfo> entry : secondaryColMap.entrySet()) {
// column is not present in Primary Environment
ComparatorUtil.publishColumnInfoOutput("DROP", entry.getValue());
}
}
if(isEqual) {
ComparatorUtil.publishColumnInfoOutput("--Exact Match");
}
} catch (Exception e) {
ComparatorUtil.publishColumnInfoOutput("ERROR"+e.getMessage());
}
}
public void compareTableColumnsValues(String primaryTableName, String primaryColumnNames, String primaryCondition, String primaryKeyColumn,
String secTableName, String secColumnNames, String secCondition, String secKeyColumn) {
if(!isComparatorRunning) {
isComparatorRunning = true;
Connection conn1 = DbConnectionRepository.getConnectionOne();
Connection conn2 = DbConnectionRepository.getConnectionTwo();
String query1 = buildQuery(primaryTableName, primaryColumnNames, primaryCondition, primaryKeyColumn);
String query2 = buildQuery(secTableName, secColumnNames, secCondition, secKeyColumn);
try {
Map<String,Map<String, Object>> query1Data = executeAndRefactorData(conn1, query1, primaryKeyColumn);
Map<String,Map<String, Object>> query2Data = executeAndRefactorData(conn2, query2, secKeyColumn);
for(Map.Entry<String,Map<String, Object>> entry : query1Data.entrySet()) {
String key = entry.getKey();
Map<String, Object> value = entry.getValue();
Map<String, Object> secondaryValue = query2Data.remove(key);
if(secondaryValue == null) {
ComparatorUtil.publishColumnValuesInfoOutput("NO SUCH VALUE AVAILABLE IN SECONDARY DB "+ value.toString());
continue;
}
compareMap(value, secondaryValue, key);
}
if(!query2Data.isEmpty()) {
ComparatorUtil.publishColumnValuesInfoOutput("Extra Values in Secondary table "+ ((Map)query2Data.values()).values().toString());
}
} catch (Exception e) {
ComparatorUtil.publishColumnValuesInfoOutput("ERROR"+e.getMessage());
}
isComparatorRunning = false;
}
}
private void compareMap(Map<String, Object> primaryValues, Map<String, Object> secondaryValues, String columnIdentification) {
for(Map.Entry<String, Object> entry : primaryValues.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Object secValue = secondaryValues.get(key);
if(value!=null && secValue!=null && !String.valueOf(value).equalsIgnoreCase(String.valueOf(secValue))) {
ComparatorUtil.publishColumnValuesInfoOutput(columnIdentification+" : Secondary Table does not match value ("+ value +") for column ("+ key+")");
}
if(value==null && secValue!=null) {
ComparatorUtil.publishColumnValuesInfoOutput(columnIdentification+" : Values not available in primary table for column "+ key);
}
if(value!=null && secValue==null) {
ComparatorUtil.publishColumnValuesInfoOutput(columnIdentification+" : Values not available in Secondary table for column "+ key);
}
}
}
private String buildQuery(String tableName, String column, String condition, String keyCol) {
if(!"*".equalsIgnoreCase(column)) {
String[] keyColArr = keyCol.split(",");
for(String key: keyColArr) {
if(!column.contains(key.trim())) {
column+=","+key.trim();
}
}
}
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("select "+column+" from "+ tableName);
if(!ComparatorUtil.isNullorEmpty(condition)) {
queryBuilder.append(" where 1=1 and "+condition);
}
return queryBuilder.toString();
}
private Map<String,Map<String, Object>> executeAndRefactorData(Connection connection, String query, String keyColumn) {
Map<String,Map<String, Object>> result = new HashMap<String, Map<String,Object>>();
try {
PreparedStatement preparedStatement = connection.prepareStatement(query);
ResultSet resultSet = preparedStatement.executeQuery();
resultSet.setFetchSize(1000);
if (resultSet != null && !resultSet.isClosed()) {
while (resultSet.next()) {
Map<String, Object> columnValueDetails = new HashMap<String, Object>();
int columnCount = resultSet.getMetaData().getColumnCount();
for (int i=1; i<=columnCount; i++) {
String columnName = String.valueOf(resultSet.getMetaData().getColumnName(i));
Object columnValue = resultSet.getObject(columnName);
columnValueDetails.put(columnName, columnValue);
}
String[] keys = keyColumn.split(",");
String newKey = "";
for(int j=0; j<keys.length; j++) {
newKey += String.valueOf(columnValueDetails.get(keys[j]));
}
result.put(newKey , columnValueDetails);
}
}
} catch (SQLException e) {
ComparatorUtil.publishColumnValuesInfoOutput("ERROR"+e.getMessage());
}
return result;
}
}
Utility Tool for the same
public class ComparatorUtil {
public static Map<String, String> getSchemaTableMap(String tableNameWithSchema) {
if(isNullorEmpty(tableNameWithSchema)) {
return null;
}
Map<String, String> result = new LinkedHashMap<>();
int index = tableNameWithSchema.indexOf(".");
String schemaName = tableNameWithSchema.substring(0, index);
String tableName = tableNameWithSchema.substring(index+1);
result.put(CompConstants.SCHEMA_NAME, schemaName);
result.put(CompConstants.TABLE_NAME, tableName);
return result;
}
public static Map<String, ColumnInfo> getColumnMetadataMap(Connection conn, Map<String, String> schemaTableMap) {
try {
String schemaName = schemaTableMap.get(CompConstants.SCHEMA_NAME);
String tableName = schemaTableMap.get(CompConstants.TABLE_NAME);
ResultSet resultSetConnOne = conn.getMetaData().getColumns(null, schemaName, tableName, null);
Map<String, ColumnInfo> resultSetTwoColInfo = getColumnInfo(schemaName, tableName, resultSetConnOne);
return resultSetTwoColInfo;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/* Number Type mapping
* 12-----VARCHAR
* 3-----DECIMAL
* 93-----TIMESTAMP
* 1111-----OTHER
*/
public static Map<String, ColumnInfo> getColumnInfo(String schemaName, String tableName, ResultSet columns) {
try {
Map<String, ColumnInfo> tableColumnInfo = new LinkedHashMap<String, ColumnInfo>();
while (columns.next()) {
ColumnInfo columnInfo = new ColumnInfo();
columnInfo.setSchemaName(schemaName);
columnInfo.setTableName(tableName);
columnInfo.setColumnName(columns.getString("COLUMN_NAME"));
columnInfo.setDatatype(columns.getString("DATA_TYPE"));
columnInfo.setColumnsize(columns.getString("COLUMN_SIZE"));
columnInfo.setDecimaldigits(columns.getString("DECIMAL_DIGITS"));
columnInfo.setIsNullable(columns.getString("IS_NULLABLE"));
tableColumnInfo.put(columnInfo.getColumnName(), columnInfo);
}
return tableColumnInfo;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static boolean isNullOrEmpty(Object obj) {
if (obj == null)
return true;
if (String.valueOf(obj).equalsIgnoreCase("NULL"))
return true;
if (obj.toString().trim().length() == 0)
return true;
return false;
}
public static boolean isNullorEmpty(String str) {
if(str == null)
return true;
if(str.trim().length() == 0)
return true;
return false;
}
public static void publishColumnInfoOutput(String type, ColumnInfo columnInfo) {
String str = "ALTER TABLE "+columnInfo.getSchemaName()+"."+columnInfo.getTableName();
switch(type.toUpperCase()) {
case "ALTER":
if("NUMBER".equalsIgnoreCase(columnInfo.getDatatype()) || "DATE".equalsIgnoreCase(columnInfo.getDatatype())) {
str += " ADD ("+columnInfo.getColumnName()+" "+ columnInfo.getDatatype()+");";
} else {
str += " ADD ("+columnInfo.getColumnName()+" "+ columnInfo.getDatatype() +"("+columnInfo.getColumnsize()+"));";
}
break;
case "DROP":
str += " DROP ("+columnInfo.getColumnName()+");";
break;
case "MODIFY":
if("NUMBER".equalsIgnoreCase(columnInfo.getDatatype()) || "DATE".equalsIgnoreCase(columnInfo.getDatatype())) {
str += " MODIFY ("+columnInfo.getColumnName()+" "+ columnInfo.getDatatype()+");";
} else {
str += " MODIFY ("+columnInfo.getColumnName()+" "+ columnInfo.getDatatype() +"("+columnInfo.getColumnsize()+"));";
}
break;
}
publishColumnInfoOutput(str);
}
public static Map<Integer, String> allJdbcTypeName = null;
public static Map<Integer, String> getAllJdbcTypeNames() {
Map<Integer, String> result = new HashMap<Integer, String>();
if(allJdbcTypeName != null)
return allJdbcTypeName;
try {
for (Field field : java.sql.Types.class.getFields()) {
result.put((Integer) field.get(null), field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
return allJdbcTypeName=result;
}
public static String getStringPlaces(String[] attribs) {
String params = "";
for(int i=0; i<attribs.length; i++) { params += "?,"; }
return params.substring(0, params.length()-1);
}
}
Column Info Class
public class ColumnInfo {
private String schemaName;
private String tableName;
private String columnName;
private String datatype;
private String columnsize;
private String decimaldigits;
private String isNullable;
If you are using Java 8 then it's better to use below code like this:
Comparator<People> comparator = Comparator.comparing(People::getName);
And then simply use:
Collections.sort(list, comparator);
If you are using Java 7 or below then you can use a comparator for customized sorting order by implementing compare method.
For example:
import java.util.Comparator;
public class PeopleNameComparator implements Comparator<People> {
#Override
public int compare(People people1, People people2) {
return people1.getName().compareTo(people2.getName());
}
}
And then simply use like this:
Collections.sort(list, new PeopleNameComparator);
Do not waste time implementing Sorting Algorithm by your own. Instead use Collections.sort() to sort data.
Related
I'm implementing a TreeMap in java today to track the navigation for next page.
I have 5 entries in the treemap, 3 of them works while 2 don't work.
NavigationHelper.java :
public class NavigationHelper {
private static String hhSection = HXConstants.CSR_SECTION_FAMILY_DETAILS;
private static String[] hhPages = {
HXConstants.CSR_PAGE_ID_HOUSEHOLD_MEMBERS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_RELATIONSHIP,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_ADDITIONAL_QUESTIONS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_SUMMARY_NEW,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_PRIVACY_AGREEMENT
};
private static String hhPath = "household";
public static HashMap<String, NavLocation> getHHNavMap()
{
HashMap<String, NavLocation> hhNavMap = new HashMap<String, NavLocation>();
for (int i=0;i<hhPages.length;i++ ) {
hhNavMap.put(hhPath+"/"+hhPages[i], new NavLocation(hhSection,hhPages[i]));
}
return hhNavMap;
}
public static Map<NavLocation,String> getHHBackNavMap() {
TreeMap<NavLocation,String> hhBackNavMap = new TreeMap<NavLocation, String>();
HashMap<String, NavLocation> hhNavMap = getHHNavMap();
for(Entry<String, NavLocation> entry : hhNavMap.entrySet()) {
hhBackNavMap.put(entry.getValue(), entry.getKey());
}
return hhBackNavMap;
}
public static class NavLocation implements Comparable<NavLocation>{
private String section;
public NavLocation(String s, String p) {
this.section = s;
this.page = p;
}
public String getSection() {
return section;
}
public String getPage() {
return page;
}
private String page;
#Override
public int compareTo(NavLocation navObj) {
if(navObj.getPage().equals(this.page) && (navObj.getSection().equals(this.section)))
return 0;
return 1;
}
}
}
AppAggNavigationHelper.java :
public class AppAggNavigationHelper extends RestServiceBaseTest {
private static String hhSection = HXConstants.CSR_SECTION_FAMILY_DETAILS;
private static String[] hhPages = {
HXConstants.CSR_PAGE_ID_HOUSEHOLD_MEMBERS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_RELATIONSHIP,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_ADDITIONAL_QUESTIONS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_SUMMARY_NEW,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_PRIVACY_AGREEMENT
};
NavigationHelper navigationHelper = new NavigationHelper();
List<NavLocation> navList = new ArrayList<NavLocation>();
#Before
public void populateNavLocations() {
for(int i = 0 ; i < hhPages.length ; i++) {
navList.add(new NavLocation(hhSection, hhPages[i]));
}
}
#Test
public void test() {
testWithoutRest();
}
public void testWithoutRest() {
TreeMap<NavLocation,String> map = (TreeMap<NavLocation, String>) navigationHelper.getHHBackNavMap();
for(Map.Entry<NavLocation, String> entry : map.entrySet()) {
NavLocation nav = entry.getKey();
System.out.println(nav.getPage() + " " + nav.getSection());
System.out.println(entry.getValue());
}
p("*****");
for(NavLocation navLocation : navList) {
System.out.println(navLocation.getPage() + " " + navLocation.getSection() + " " + map.get(navLocation));
}
}
}
Then the output is wired, for member, summary, privacy it's working.
But for relation and questions it's not.
:
member familydetails household/member
relation familydetails null
question familydetails null
summary familydetails household/summary
privacy familydetails household/privacy
Why relation and question are not working?
Your compareTo method is broken. If two NavLocation objects, a and b differ in their page or section, both a.compareTo(b) and b.compareTo(a) will return 1, thus violating the general contract of the method, which may lead to unexpected results.
Instead, the classic way to implement such a method depending on the objects properties would probably look something like this:
#Override
public int compareTo(NavLocation other) {
int result = getPage().compareTo(other.getPage());
if (result != 0) {
return result;
}
return getSection().compareTo(other.getSection());
}
Let's say that I want my application to determine user's fitness level based on some criteria.
The criteria could be something like: age, currently taking medication?, 400m run
At first I though I could create a Map where the value is the fitness level and the key is an object that has all the criteria, but since the criteria are ranges this wouldn't work.
For example:
if age is between 18 and 22 and onMedication = false and run400m = [70, 80]
fitness level = GOOD
Now if only one of the parameters is in a different range the fitness level would be different. How could I achieve this?
You can use a TreeMap class for this. There are very useful methods to deal with ranges of key values. For example:
TreeMap<Integer, String> myTreeMap = new TreeMap<>();
myTreeMap.put(10, "A");
myTreeMap.put(20, "B");
myTreeMap.put(30, "C");
myTreeMap.put(40, "D");
System.out.println(myTreeMap.floorEntry(25));
Will be print the second option (20=B). I recommend that you check the TreeMap and all its methods for this case.
Maybe you could use OOP and do something like this:
public class FitnessApp {
public static void main(String[] args) {
Map<String, Object> params = new HashMap<>();
params.put("age", 17);
params.put("onMedication", false);
System.out.printf(new FitnessLevelCalculator().calculateFor(params).name());
}
}
class FitnessLevelCalculator {
private LinkedList<FitnessLevel> fitnessLevels = new LinkedList<>();
public FitnessLevelCalculator() {
fitnessLevels.add(new FitnessLevel(FitnessLevelEnum.ATHLETIC, Arrays.asList(new RangeCriteria("age", 18, 25), new BooleanCriteria("onMedication", false))));
fitnessLevels.add(new FitnessLevel(FitnessLevelEnum.GOOD, Arrays.asList(new RangeCriteria("age", 14, 17), new BooleanCriteria("onMedication", false))));
fitnessLevels.add(new FitnessLevel(FitnessLevelEnum.ILL, Arrays.asList(new RangeCriteria("age", 16, 17))));
}
public FitnessLevelEnum calculateFor(Map<String, Object> params) {
ListIterator<FitnessLevel> listIterator = fitnessLevels.listIterator();
while (listIterator.hasNext()) {
FitnessLevel fitnessLevel = listIterator.next();
if (fitnessLevel.accept(params)) {
return fitnessLevel.getLevel();
}
}
return FitnessLevelEnum.NOT_CLASSIFIED;
}
}
enum FitnessLevelEnum {
ILL, GOOD, ATHLETIC, NOT_CLASSIFIED
}
class FitnessLevel {
private List<Criteria> criteriaList = new ArrayList<>();
private FitnessLevelEnum level;
public FitnessLevel(FitnessLevelEnum level, List<Criteria> criteriaList) {
this.criteriaList = criteriaList;
this.level = level;
}
public boolean accept(Map<String, Object> params) {
for (Criteria criteria : criteriaList) {
if (!params.containsKey(criteria.getName())) {
return false;
}
if (!criteria.satisfies(params.get(criteria.getName()))) {
return false;
}
}
return true;
}
public FitnessLevelEnum getLevel() {
return level;
}
}
abstract class Criteria<T> {
private String name;
public Criteria(String name) {
this.name = name;
}
public abstract boolean satisfies(T param);
public String getName() {
return name;
}
}
class RangeCriteria extends Criteria<Integer> {
private int min;
private int max;
public RangeCriteria(String name, int min, int max) {
super(name);
this.min = min;
this.max = max;
}
#Override
public boolean satisfies(Integer param) {
return param >= min && param <= max;
}
}
class BooleanCriteria extends Criteria<Boolean> {
private Boolean expectedValue;
public BooleanCriteria(String name, Boolean expectedValue) {
super(name);
this.expectedValue = expectedValue;
}
#Override
public boolean satisfies(Boolean param) {
return param == expectedValue;
}
}
In your specific case, I don't think it's good to use as a key in Map. There maybe a way to put the Object with all conditions as your business but it's quite complex and not worth to do that. The Interpreter Pattern may help you on this.
Hope this help.
This is my VO
public class SomeVO {
private String name;
private String usageCount;
private String numberofReturns;
private String trendNumber;
private String nonTrendNumber;
private String trendType;
private String auditType;
public SomeVO(String name,String usageCount,String numberofReturns,String trendNumber,String nonTrendNumber,String trendType,String auditType){
this.name = name;
this.usageCount = usageCount;
this.numberofReturns = numberofReturns;
this.trendNumber = trendNumber;
this.nonTrendNumber = nonTrendNumber;
this.trendType = trendType;
this.auditType = auditType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsageCount() {
return usageCount;
}
public void setUsageCount(String usageCount) {
this.usageCount = usageCount;
}
public String getNumberofReturns() {
return numberofReturns;
}
public void setNumberofReturns(String numberofReturns) {
this.numberofReturns = numberofReturns;
}
public String getTrendNumber() {
return trendNumber;
}
public void setTrendNumber(String trendNumber) {
this.trendNumber = trendNumber;
}
public String getNonTrendNumber() {
return nonTrendNumber;
}
public void setNonTrendNumber(String nonTrendNumber) {
this.nonTrendNumber = nonTrendNumber;
}
public String getTrendType() {
return trendType;
}
public void setTrendType(String trendType) {
this.trendType = trendType;
}
public String getAuditType() {
return auditType;
}
public void setAuditType(String auditType) {
this.auditType = auditType;
}
}
Here is my values
List<SomeVO> myList = new ArrayList<SomeVO>();
SomeVO some = new SomeVO("A","0","0","123","123","Trend","AuditX");
myList.add(some);
some = new SomeVO("B","1","1","234","234","Non trend","AuditX");
myList.add(some);
some = new SomeVO("C","0","2","345","345","Trend","AuditX");
myList.add(some);
some = new SomeVO("D","2","3","546","546","Trend","AuditX");
myList.add(some);
some = new SomeVO("E","2","4","678","678","Non trend","AuditX");
myList.add(some);
some = new SomeVO("F","0","0","123","123","Non trend","AuditA");
myList.add(some);
some = new SomeVO("G","0","0","123","123","Trend","AuditB");
myList.add(some);
Here is my comparator
public String currentAudit = "AuditX";
public class AuditComparator implements Comparator<SomeVO> {
#Override
public int compare(SomeVO o1, SomeVO o2) {
if(currentAudit.equalsIgnoreCase(o1.getAuditType()) && currentAudit.equalsIgnoreCase(o2.getAuditType())) {
int value1 = o2.getUsageCount().compareTo(o1.getUsageCount());
if (value1 == 0) {
int value2 = o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
if(o1.getTrendType().equalsIgnoreCase("Trend") && o2.getTrendType().equalsIgnoreCase("Trend")) {
if (value2 == 0) {
return o1.getTrendNumber().compareTo(o2.getTrendNumber());
} else {
return value2;
}
} else {
if (value2 == 0) {
return o1.getNonTrendNumber().compareTo(o2.getNonTrendNumber());
} else {
return value2;
}
}
}
return value1;
} else {
return 1;
}
}
}
I am trying to sort the VO based on below conditions
First only set of values of currentAudit should be taken in to
consideration i.e., AuditX
a) then it should be sorted with
Usage count in descending order
b) if same usage count found then it
should be sorted with Return count in ascending order
c) if same
return count then it should check for trendType, if trendType
="Trend" then it should sort with Trend number otherwise nonTrend number.
then it should consider rest all auditType's and sorted with
a),b),c) condition as like currentAudit. I tried achieving it and i
ended up with only above comparator. Expected result: D, A, C, E,
F, G. But i get G,F,D,E,B,A,C. Please help me to update the
comparator above.
Your comparator does not meet a simple condition: it is not stateless. A following should always be true: A>B => B<A. In your case, in some scenarios A>B and B>A.
I resolved it by splitting the actual list in to 2 list based on AuditX and rest in another list. Then used below comparator one by one, and then merged in to a result list. Works good.
for(SomeVO some:myList) {
if(some.getAuditType().equalsIgnoreCase("AuditX")) {
auditX.add(some);
} else {
auditY.add(some);
}
}
Collections.sort(auditX, new AuditComparator());
Collections.sort(auditY, new AuditComparator());
public class AuditComparator implements Comparator<SomeVO> {
#Override
public int compare(SomeVO o1, SomeVO o2) {
int value1 = o2.getUsageCount().compareTo(o1.getUsageCount());
if (value1 == 0) {
int value2 = o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
if (value2 == 0) {
return (o1.getTrendType().equalsIgnoreCase("Trend") && o2.getTrendType().equalsIgnoreCase("Trend")) ?
o1.getTrendNumber().compareTo(o2.getTrendNumber()):o1.getNonTrendNumber().compareTo(o2.getNonTrendNumber());
} else {
return value2;
}
}
return value1;
}
The return 1 at the bottom of the comparator makes a bug.
The comparator shall only return 1 if the second element is bigger than the first one, but if they're different, you always return 1, so the very first sorting criteria will be messy.
// a helper for case insensitive comparison
private int compareIgnoreCase(String o1,String o2) {
return o1.toLowercase.compareTo(o2.toLowercase());
}
#Override
public int compare(SomeVO o1, SomeVO o2) {
int result=compareIgnoreCase(o1.getAuditType(),o2.getAuditType());
if (result==0) {
// we need to go to the 2nd criteria
result=o2.getUsageCount().compareTo(o1.getUsageCount());
}
if (result==0) {
// ok, 1st and 2nd criteria was the same, go to the 3rd
result=o1.getNumberofReturns().compareTo(o2.getNumberofReturns());
}
if (result==0) {
// check trends
...
}
return result;
}
I found that this representation of multiple comparison criteria makes the code much easier to follow. We first do the highest priority of comparison, and go on with further comparions if the previous comparisons returned that the two elements are the same (i.e. result is still zero).
In case you need to make a descending sorting at some level, simply put a -, e.g.:
result=-o1.something.compareTo(o2.something)
It is a good idea to have only one exit point in a method (this also makes easier to follow what is happening).
I have implemented Dictionary with Vector(Array). In array i store a String data. Now i have get position Method. But i want to retrieve data at some position. what will be the method? Thank you.
private int findpositionProfile(String smkey){
DictionaryProfile p = new DictionaryProfile(smkey,null);
return data.getposi(p);
}
public Profile getProfile(int i){
// DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
this is not working
public class Dictionary {
private Vector data;
private Vector data1;
public Dictionary() {
data = new Vector(100);
data1 = new Vector(100);
}
public void addProfile(String smkey, Profile smvalue) {
DictionaryProfile d = new DictionaryProfile(smkey, smvalue);
if (data.getposi(d) == -1) {
data.addLast(d);
}
data.replace(d);
}
public void addCorporate(String smkey, CorporateProfile smvalue) {
DictionaryCorporate d = new DictionaryCorporate(smkey, smvalue);
if (data1.getposi(d) == -1) {
data1.addLast(d);
}
data1.replace(d);
}
private int findpositionProfile(String smkey) {
DictionaryProfile p = new DictionaryProfile(smkey,null);
return data.getposi(p);
}
public CorporateProfile getCorporate(int i){
return data.get(i);
}
public Profile getProfile(int i){
DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
}
My dictionaryPair::
public class DictionaryProfile implements Comparable
{
private String userName ;
private Profile completeProfile ;
public DictionaryProfile ( String name,Profile p){
userName = name;
completeProfile = p;
}
public String getUserName(){
return userName;
}
public Profile getProfile(){
return completeProfile;
}
public void setUsename ( String newname ){
userName= newname;
}
public void setProfile ( Profile pro ){
completeProfile = pro;
}
public int compareTo(Object obj){
DictionaryProfile dp = (DictionaryProfile) obj;
return (this.getUserName()).compareTo(dp.getUserName());
}
}
No one should be using the JDK 1.0 vintage Vector class. This doesn't look like a generic Dictionary ADT to me.
This method makes no sense whatsoever:
public Profile getProfile(int i){
DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
}
The local variable p is instantiated, never used, and eligible for GC when it goes out of scope. Data is a Vector holding type Object. Where do you expect to get a Profile from?
This code makes no sense.
This will work, unless you pass an index that's out of bounds.
public Profile getProfile(int i){
return (Profile) data.get(i);
}
None of this describes how a Dictionary works. It's a synonym for a Map, which has a key/value pair. Your code isn't doing that. Doesn't use generics for key or value. Why would you do this instead of just using a Map<K, V>?
I think you should start with this:
package collections;
public interface Dictionary<K, V> {
V get(K key);
V put(K key, V value);
boolean containsKey(K key);
int size();
}
Your keys ought to be immutable.
This is what I would consider the minimal interface for a proper Dictionary.
Here's an implementation that uses backing ArrayList:
package collections;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation of a Dictionary interface
* Created by Michael
* Creation date 12/30/2015.
* #link https://stackoverflow.com/questions/34538520/data-structures-and-algorithms-implementation-dictionary/34538668?noredirect=1#comment56819702_34538668
*/
public class DictionaryImpl<K, V> implements Dictionary<K, V> {
private List<K> keys;
private List<V> values;
public DictionaryImpl() {
this.keys = new ArrayList<>();
this.values = new ArrayList<>();
}
#Override
public V get(K key) {
V value = null;
if (this.keys.contains(key)) {
int index = this.getIndex(key);
if (index != -1) {
value = this.values.get(index);
}
}
return value;
}
#Override
public V put(K key, V value) {
V previousValue = null;
if (this.keys.contains(key)) {
previousValue = this.get(key);
}
this.keys.add(key);
this.values.add(value);
return previousValue;
}
#Override
public boolean containsKey(K key) {
return this.keys.contains(key);
}
#Override
public int size() {
return this.keys.size();
}
private int getIndex(K keyToFind) {
int index = -1;
if (this.keys.contains(keyToFind)) {
for (K key : this.keys) {
++index;
if (key.equals(keyToFind)) {
break;
}
}
}
return index;
}
}
Here's a Junit test to prove that it's all working:
package collections;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Junit test for Dictionary
* Created by Michael
* Creation date 12/30/2015.
* #link https://stackoverflow.com/questions/34538520/data-structures-and-algorithms-implementation-dictionary/34538668?noredirect=1#comment56819702_34538668
*/
public class DictionaryTest {
private Dictionary<String, Integer> testDictionary;
#Before
public void setUp() {
this.testDictionary = new DictionaryImpl<>();
this.testDictionary.put("foo", 17);
this.testDictionary.put("bar", 23);
this.testDictionary.put("baz", 31);
this.testDictionary.put("bat", 41);
}
#Test
public void testContainsKey_True() {
String [] keys = { "foo", "bar", "baz", "bat" };
for (String key : keys) {
Assert.assertTrue(String.format("Should have contained key '%s'", key), this.testDictionary.containsKey(key));
}
}
#Test
public void testContainsKey_False() {
String [] keys = { "dopey", "sleepy", "doc", "sneezy" };
for (String key : keys) {
Assert.assertTrue(String.format("Should not have contained key '%s'", key), !this.testDictionary.containsKey(key));
}
}
#Test
public void testGet_Success() {
String [] keys = { "foo", "bar", "baz", "bat" };
Integer [] values = { 17, 23, 31, 41 };
for (int i = 0; i < keys.length; ++i) {
Assert.assertEquals(String.format("Should have returned value %d for key '%s'", values[i], keys[i]), values[i], this.testDictionary.get(keys[i]));
}
}
#Test
public void testGet_NoSuchKey() {
String [] keys = { "dopey", "sleepy", "doc", "sneezy" };
for (String key : keys) {
Assert.assertNull(String.format("Should have returned null for key '%s'", key), this.testDictionary.get(key));
}
}
#Test
public void testSize() {
int expected = 4;
Assert.assertEquals(expected, this.testDictionary.size());
}
}
Eclipse can auto-generate a toString() method from a object's fields. If those fields are objects then they too may have similarly auto-generated toString() methods.
e.g. a President object might look like this:
President [country=USA, name=Name [title=Mr, forename=Barack, surname=Obama], address=Address [houseNumber=1600, street=Pennsylvania Avenue, town=Washington]]
which is easier to read if I format it:
President [
country=USA,
name=Name [
title=Mr,
forename=Barack,
surname=Obama],
address=Address [
houseNumber=1600,
street=Pennsylvania Avenue,
town=Washington]]
What is the best way to parse this String to create a map of maps?
I've got a solution, but it's not pretty. I was hoping to be able to avoid the low level String manipulation somehow, but here it is:
import java.util.LinkedHashMap;
import java.util.Map;
public class MappedObject {
public String className;
public Map<String, String> leafFields = new LinkedHashMap<>();
public Map<String, MappedObject> treeFields = new LinkedHashMap<>();
#Override
public String toString() {
return "[className=" + className
+ (leafFields.isEmpty() ? "" : ", leafFields=" + leafFields)
+ (treeFields.isEmpty() ? "" : ", treeFields=" + treeFields)
+ "]";
}
public static MappedObject createFromString(String s) {
MappedObject mo = new MappedObject();
new Mapper(s).mapObject(mo);
return mo;
}
private static class Mapper {
private String s;
public Mapper(String s) {
this.s = s;
}
private String mapObject(MappedObject mo) {
mo.className = removeFirstNCharacters(s.indexOf(' '));
while (s.contains("=")) {
removeLeadingNonLetters();
String key = removeFirstNCharacters(s.indexOf('='));
removeFirstNCharacters(1); // remove the =
String leafValue = getLeafValue();
if (leafValue != null) {
mo.leafFields.put(key, leafValue);
if (s.startsWith("]")) { // that was the last field in the tree
return s;
}
} else {
MappedObject treeField = new MappedObject();
mo.treeFields.put(key, treeField);
s = new Mapper(s).mapObject(treeField);
}
}
return s; // s contains only close brackets - ]
}
private void removeLeadingNonLetters() {
int i = 0;
while (!Character.isLetter(s.charAt(i))) {
i++;
}
removeFirstNCharacters(i);
}
private String removeFirstNCharacters(int n) {
String value = s.substring(0, n);
s = s.substring(value.length());
return value;
}
private String getLeafValue() {
int endIndex = getEndIndex();
if (!s.contains("[") || s.indexOf('[') > endIndex) {
return removeFirstNCharacters(endIndex);
}
return null;
}
/** The end of the value, if it's a leaf field. */
private int getEndIndex() {
if(s.contains(",")) {
return Math.min(s.indexOf(','), s.indexOf(']'));
}
return s.indexOf(']');
}
}
}