SortedMap with Comparator ClassCast Exception - java

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.

Related

Can Collections.sort() take treeSet as first argument? If not why?

I have my 'Person' class as follows -
public class Person implements Comparable<Person> {
private int marks;
private String name;
Person(int marks, String name) {
this.marks = marks;
this.name = name;
}
public int getMarks() {
return marks;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "Person: Marks = " + marks + ", Name = " + name;
}
#Override
public int compareTo(Person person) {
return marks > person.marks ? 1 : -1;
}
}
Now in main method I have created another comparator with opposite sorting -
Comparator<Person> comparator = new Comparator<Person>() {
#Override
public int compare(Person p1, Person p2) {
int marks1 = p1.getMarks();
int marks2 = p2.getMarks();
return marks1 > marks2 ? -1 : 1;
}
};
Now, I create a TreeSet -
TreeSet<Person> treeSet1 = new TreeSet<>(List.of(
new Person(67, "Himani"),
new Person(73, "Hasani"),
new Person(21, "Rohini")
));
Now, I try passing treeSet as an argument to Collections.sort() -
Collections.sort(treeSet1, comparator);
Here, I get the following error -
Required type Provided
list:List<T> TreeSet<Person>
c:Comparator<? super T> Comparator<Person>
As far as I can deduce from the error - Collections.sort() with comparator can only be used on List and not Set.
Am I correct? If not where am I going wrong? If yes why am I correct?
The first argument to Collections.sort is of type List<T>. So you can only pass List objects to it, not Set objects, or anything else.
This is sensible. The reason is that Set objects are of two types.
There are those that are, by their nature, always unsorted, such as HashSet. It doesn't make sense to sort these.
There are those that are, by their nature, always sorted the same way, such as TreeSet. It doesn't make sense to sort these, because they're already sorted.
I think maybe what you want is to take your TreeSet and have it sorted using a different Comparator. To do that, you'll have to make a new TreeSet with your new Comparator, and copy the entries across from the old set using addAll.

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

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]

sorting a hashmap who's key is another hashmap

so this is my hashmap
public HashMap<Integer, HashMap<String, Integer>> girls =
new HashMap<Integer, HashMap<String, **Integer**>>();;
I want to sort the bolded by value. For clarification the outer hashMaps key stands for a year a girl child was born and the inner hashmap stands for a name mapped to the popularity ranking of the name.
So let's say that in 2015, the name Abigail was given to 47373 babies and it was the most popular name in that year, I'd want to return the number 1 bc it's the number one name. Is there any way to sort a hashmap in this way?
how would I turn the inner hashmaps values into an arraylist that I could then easily sort? Any help?
There is no easy/elegant way to sort a Map by value in its data structure.
HashMaps are unsorted by definition.
LinkedHashMaps are sorted by insertion order.
TreeMaps are sorted by key.
If you really need to, you could write an algorithm which builds up you data structure using a LinkedHashMap as the "inner" structure and make sure the largest value is inserted first.
Alternatively, you could write a small class
class NameFrequency
{
String name;
int frequency;
}
and make your data structure a HashMap<Integer, TreeSet<NameFrequency>> and define a comparator for the TreeSet which orders those objects the way you like.
Or, finally, you could leave your data structure as it is and only order it when accessing it:
girls.get(2015).entrySet().stream()
.sorted((entry1, entry2) -> entry2.getValue() - entry1.getValue())
.forEachOrdered(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
You're better off just creating a class for a name and number of occurrences.
import java.util.Objects;
public class NameCount implements Comparable<NameCount> {
private final String name;
private int count;
public NameCount(String name) {
this.name = name;
count = 0;
}
public NameCount(String name, int count) {
this.name = name;
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public void incrementCount() {
count++;
}
#Override
public int hashCode() {
return Objects.hashCode(this.name);
}
#Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(getClass() != obj.getClass()) return false;
final NameCount other = (NameCount)obj;
if(!Objects.equals(this.name, other.name)) return false;
return true;
}
#Override
public int compareTo(NameCount o) {
return o.count - count;
}
}
You can then define your map as Map<Integer, List<NameCount>>. Note how the above class defines equality and hash code based only on the name, so if you want to see if a name is in a list, you can just create a NameCount for it and use contains. The compareTo implementation orders from higher count to lower, so when getting the List<NameCount> for a given year, you can then use Collections.sort(list) on it and ask for the index for a NameCount with the same name.
public void test(Map<Integer, List<NameCount>> map) {
int year = 2017;
List<NameCount> list = map.get(year);
// Do null-check on list first when using this...
Collections.sort(list);
NameCount check = new NameCount("Abigail");
int rank = list.indexOf(check) + 1;
}
It might seem to make more sense to use TreeSet map values to guarantee unique name entries and keep them sorted all the time, but note that TreeSet defines equality based on comparison, not equals, and it wouldn't let you get the index.

Sort List based on index value

This is what i have so far, i'm trying to sort a bunch of List<String>'s based on the value of an index.
LinkedHashSet<List<String>> sorted = new LinkedHashSet<List<String>>();
How do i sort the LinkedHashSet in order from Highest to Lowest index 2 value of the List's?
Example input:
List<String> data1 = Database.getData(uuid);
double price = Double.valueOf(data1.get(2))
data1.add("testval");
data1.add("testval");
data1.add("100.00");
sorted.add(data1);
and on another seperate List:
List<String> data2 = Database.getData(uuid);
double price = Double.valueOf(data2.get(2))
data2.add("anotherval");
data2.add("anotherval");
data2.add("50.00");
sorted.add(data2);
Output of the sorted LinkedHashSet in descending order.
testval testval 100.00
anotherval anotherval 50.00
Sorry if this is confusing, im not sure where to go about sorting like this.
Create a new class to represent you complex objects. There is no need to store multiple values in a list when you can do it in objects.
public class ComplexObject {
private String description1;
private String description2;
private Double value;
public ComplexObject(String description1, String description2, Double value) {
this.description1 = description1;
this.description2 = description2;
this.value = value;
}
public void setDescription1(String description1) {
this.description1 = description1;
}
public String getDescription1() {
return description1;
}
public void setDescription2(String description2) {
this.description2 = description2;
}
public String getDescription2() {
return description2;
}
public void setValue(Double value) {
this.value = value;
}
public Double getValue() {
return value;
}
}
Then add elements to the list and sort it using a new, custom, comparator:
public static void main(String[] args) {
List<ComplexObject> complexObjectList = new ArrayList<ComplexObject>();
//add elements to the list
complexObjectList.add(new ComplexObject("testval","testval",100.00d));
complexObjectList.add(new ComplexObject("anotherval","anotherval",50.00d));
//sort the list in descending order based on the value attribute of complexObject
Collections.sort(complexObjectList, new Comparator<ComplexObject>() {
public int compare(ComplexObject obj1, ComplexObject obj2) {
return obj2.getValue().compareTo(obj1.getValue()); //compares 2 Double values, -1 if less , 0 if equal, 1 if greater
}
});
//print objects from sorted list
for(ComplexObject co : complexObjectList){
System.out.println(co.getDescription1()+" "+co.getDescription2()+" "+co.getValue());
}
}
Output:
testval testval 100.0
anotherval anotherval 50.0
Firstly, you shouldn't use a LinkedHashSet but a TreeSet. LinkedHashSet will retain the insertion order without sorting.
Secondly, you need to initialize your TreeSet with a Comparator that compares based on whichever value of your List is required, that is, if you know the index of the String that will represent a double value in advance. Otherwise I would recommend using custom objects instead of List.
If you decide to use custom objects, you don't necessarily need to initialize your TreeSet with a Comparator as second argument.
Instead, you could have your custom objects implement Comparable, and implement a one-time comparation logic there.
It all depends on whether you only need to sort in a particular order.
Finally, custom objects will require you to override equals and hashCode.
First, and extracted from Oracle's Java reference:
This linked list defines the iteration ordering, which is the order in which elements were inserted into the set
So you can't sort your data just inserting it into the LinkedHashSet.
You may be confusing that set implementation with SortedSet. SortedSet allows you to pass a comparator which will determine the elements order in the data structure.
On the other hand, I don't know whether you chose you List<String> arbitrarily but it seems to me a wiser option to aggregate your the 3 strings as a class attributes. The point is that, if your elements are always going to be 3 elements, being the last one a double value: Why do you need a dynamic structure as a List?
EDIT
Here you have a possible better implementation of what you want:
public class Element
{
public Element(String a, String b, double val) {
this.a = a;
this.b = b;
this.val = val;
}
#Override
public String toString() {
return a + "\t" + b + "\t" + val;
}
public String a;
public String b;
public double val;
}
And you can use this class to store your elements. An example of use:
SortedSet<Element> sorted = new TreeSet<>(new Comparator<Element>() {
#Override
public int compare(Element o1, Element o2) {
return (new Double(o1.val)).compareTo(o2.val);
}
});
sorted.add(new Element("testval", "testval", 100.0));
sorted.add(new Element("anotherval", "anotherval", 50.0));
for(Element el: sorted)
{
System.out.println(el);
}
Note that the comparator is given as an instance of an anonympous inner class implementing Java's Comparator interface.

How HashMap Sorting work if all the key's hashcode is same

Sorry if this question is not clear. I have very limited knowledge about hashmap. This was the question which was asked to me in the interview.
If all objects return same hascode and If we use those objects as key to store in hashmap. How sorting works in this case?
My understanding was if hashcode are same then new entry will replace the old entry in the hashmap.
But when i searched to understand more about how hashmap works if the two objects hashcode is same, i foudn that both objects will be stored in the linked list form in the bucket.
But its not clear how sorting will work in this case. If we try to sort this hashpMap using TreeMap.
Please help me to understand this.
Below code stores more than one entry in the hashmap where all the objects hashcode is same.
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Employee implements Comparable
{
private String name;
private int age;
public Employee(String name, int age)
{
this.name = name;
this.age = age;
}
#Override
public String toString()
{
return name + ": " + age;
}
#Override
public int hashCode(){
return 1;
}
#Override
public boolean equals(Object o){
if (!(o instanceof Employee))
return false;
Employee e = (Employee) o;
return e.getName().equals(name) && e.getAge() == age;
}
String getName()
{
return name;
}
int getAge()
{
return age;
}
public static void main(String a[]){
Employee e = new Employee("Sub" , 25);
Employee e1 = new Employee("Sub1" , 20);
Employee e2 = new Employee("Sub2" , 22);
System.out.println(e);
Map m = new HashMap();
m.put(e , "A");
m.put(e1 , "B");
m.put(e2 , "C");
System.out.println(m);
TreeMap t = new TreeMap(m);
System.out.println(t);
}
#Override
public int compareTo(Object arg0) {
return ((Employee)arg0).getName().compareTo(this.name);
}
}
Objects are distributed on the storage space according to the hash value obtained from their hashcode function. If all objects return the same hashcode, you'll have a collision for each object you add to the HashMap. Also you'll have a sequential search (using equals method) for every object you get from the Hashmap.
In short: change your hashcode algorithm to reduce collisions.
If you need to add the same key but with different values I will recommend you to use MultiMap from apache commons or guava. I found it more useful because you can use ListMultimap (Guava) and then it doesnt mather if you add the same key the values will be added to a list. I know this is not the question but I think you asked it because you have some issue with it.
hashmap is not store elements in sorting order.
For storing it fisrt look for the appropriete bucket with the help of hashcode.
if hashcode are same it will store the element in linklist format and with utilization of single bucket only.

Categories