What is hashmap collisioning ? and does it occurs in my code? - java

I have written a code which has Student class and student objects are used as keys
as follows,
public class ExampleMain01 {
private static class Student{
private int studentId;
private String studentName;
Student(int studentId,String studentName){
this.studentId = studentId;
this.studentName = studentName;
}
#Override
public int hashCode(){
return this.studentId * 31;
}
#Override
public boolean equals(Object obj){
boolean flag = false;
Student st = (Student) obj;
if(st.hashCode() == this.hashCode()){
flag = true;
}
return flag;
}
#Override
public String toString(){
StringBuffer strb = new StringBuffer();
strb.append("HASHCODE ").append(this.hashCode())
.append(", ID ").append(this.studentId)
.append(", NAME ").append(this.studentName);
return strb.toString();
}
public int getStudentId() {
return studentId;
}
public String getStudentName() {
return studentName;
}
} // end of class Student
private static void example02() throws Exception{
Set<Student> studentSet = new HashSet<Student>();
studentSet.add(new Student(12, "Arnold"));
studentSet.add(new Student(12, "Sam"));
studentSet.add(new Student(12, "Jupiter"));
studentSet.add(new Student(12, "Kaizam"));
studentSet.add(new Student(12, "Leny"));
for(Student s : studentSet){
System.out.println(s);
}
} // end of method example02
private static void example03() throws Exception{
Map<Student, Integer> map = new HashMap<Student,Integer>();
Student[] students = new Student [] {
new Student(12, "Arnold"),
new Student(12, "Jimmy"),
new Student(12, "Dan"),
new Student(12, "Kim"),
new Student(12, "Ubzil")
};
map.put(students[0], new Integer(23));
map.put(students[1], new Integer(123));
map.put(students[2], new Integer(13));
map.put(students[3], new Integer(25));
map.put(students[4], new Integer(2));
Set<Map.Entry<Student, Integer>> entrySet = map.entrySet();
for(Iterator<Map.Entry<Student, Integer>> itr = entrySet.iterator(); itr.hasNext(); ){
Map.Entry<Student, Integer> entry = itr.next();
StringBuffer strb = new StringBuffer();
strb.append("Key : [ ").append(entry.getKey()).append(" ], Value : [ ").append(entry.getValue()).append(" ] ");
System.out.println(strb.toString());
}
} // end of method example03
public static void main(String[] args) {
try{
example02();
example03();
}catch(Exception e){
e.printStackTrace();
}
}// end of main method
} // end of class ExampleMain01
In the above code in Student class the hashcode and equals are implemented as follows,
#Override
public int hashCode(){
return this.studentId * 31;
}
#Override
public boolean equals(Object obj){
boolean flag = false;
Student st = (Student) obj;
if(st.hashCode() == this.hashCode()){
flag = true;
}
return flag;
}
Now when I compile and run the code,
the code in method example02 gives an output as
HASHCODE 372, ID 12, NAME Arnold
i.e the Set holds only one object,
What I understood that as the key of all the objects has the same hashcode hence only single object lies in the bucket 372. Am I right ?
Also the method example03() give output as
Key : [ HASHCODE 372, ID 12, NAME Arnold ], Value : [ 2 ]
From the above method we can see that as the key returns the same hashcode,
the Hashmap only holds the single key value pair.
So my question is where does the collision happens ?
Can a key can point to multiple values ?
Where does the linkedlist concept comes while searching for value of respective key ?
Can anybody please explain me the above things with respect to the examples I have shared ?

What is hashmap collisioning ?
There is no such thing as "hashmap collision".
There is such a thing as "hashcode collision". That happens when two objects have the same hashcode, but are not equal.
Hashcode collision is not a problem ... unless it happens frequently. A properly designed hash table data structure (including HashMap or HashSet) will cope with collision, though if the probability of collision is too high, performance will tend to suffer.
Does [hashcode collision] occur in my code?
No.
The problems with your code are not due hashcode collision:
Your equals method is in effect saying that two Student objects are equal if-and-only-if they have the same hashcode. Since your hashcode is computed from only the ID, this means that any Student objects with the same ID are equal by definition.
Then you add lots of Student objects that have the same ID (12) and different names. Obviously, that means they are equal. And that means that the HashSet will only hold one of them ... at any given time.
So my question is where does the collision happens ?
The problem is that the Student objects are all equal.
Can a key can point to multiple values ?
This is a HashSet not a HashMap. There are no "keys". A Set is a set of unique values ... where unique means that the members are not equal.
Where does the linkedlist concept comes while searching for value of respective key ?
If you are talking about the LinkedList class, it doesn't come into it.
If you are talking about linked lists in general, the implementation of HashSet can use a form of linked list to represent the hash chains. But that is an implementation detail, and not something that makes any difference to your example.
(If you really want to know how HashSet works use Google to search for "java.util.HashSet source" and read the source code. Note that the implementations are different in different versions of Java.)
Can a key can point to multiple values ?
No. The Map API doesn't support that.
But of course you could use a map like this:
Map<Key, List<Value>> myMultimap = .....

Related

HashSet internal storing same hash bucket

How are the following Person objects being stored inside the same hash bucket ? As a linked list ? Also, according to java 8, if a certain treshhold is reached, the linked list is transformed into an tree ? Is this also correct ?
class TestHashSet
{
public static void main (String[] args) throws java.lang.Exception
{
Person p1 = new Person("Mike");
Person p2 = new Person("Mike");
Set persons = new HashSet();
persons.add(p1);
persons.add(p2);
Iterator iterator = persons.iterator();
while (iterator.hasNext()) {
System.out.println("Value: "+((Person)iterator.next()).getName() + " ");
}
}
}
class Person {
String name;
String getName(){
return name;
}
Person(String name){
this.name = name;
}
public int hashCode(){
return name.hashCode();
}
public boolean equals(Object o){
return false;
}
}
Yes. Colliding entries are first stored as a linked list and later on
after a certain threshold as a tree
For your 1st question, the 2 objects will be stored as 2 different entries in the hash set. The reason being the false returned from equals method.
The 2 objects are compared. First of all, hash code is checked. Since same hash code is found, equals method is checked, which returns false, and hence the object is stored again.
The important thing to note here is that since the hash codes are same, they get into the same bucket, but as 2 different entries (as a scenario of collision).
For the 2nd question, as of java 8, binary tree is used instead of linked list for the purpose after a certain threshold is reached. For reference, check https://www.nagarro.com/de/blog/post/24/performance-improvement-for-hashmap-in-java-8

In Java: What happens if I change a key in an HashMap to be equal to another key? [duplicate]

This question already has answers here:
Changing an object which is used as a Map key
(5 answers)
Closed 5 years ago.
I know that I can not have 2 keys in a HashMap which are equal (by the equals()-method). And if I try to add a key-value-pair to the HashMap with the key already existing, the old value is just replaced by the new one.
But what if I change an already existing key to be equal to another existing key?
How will the map.get() method behave in this case (applied to one of these equal keys)?
Very simple example below.
public class Person{
private int age;
private String name;
public Person(int a, String n){
age = a;
name = n;
}
public void setAge(int a){ age = a; }
public int getAge(){return age; }
public String getName() {return name; }
#Override
public boolean equals(Object o){
if(!(o instanceof Person)){return false;}
Person p = (Person) o;
return ((p.getName().equals(this.getName())) && (p.getAge() == this.getAge()));
}
#Override
public int hashCode(){return age;}
}
public class MainClass{
public static void main(String[]args){
Person p1 = new Person("Bill", 20);
Person p2 = new Person("Bill", 21);
HashMap<Person, String> map = new HashMap<>();
map.put(p1, "some value");
map.put(p2, "another value");
p1.setAge(21);
String x = map.get(p1); // <-- What will this be??
System.out.println(x);
}
}
When you mutate a key which is already present in the HashMap you break the HashMap. You are not supposed to mutate keys present in the HashMap. If you must mutate such keys, you should remove them from the HashMap before the change, and put them again in the HashMap after the change.
map.get(p1) will search for the key p1 according to its new hashCode, which is equal to the hash code of p2. Therefore it will search in the bucket that contains p2, and return the corresponding value - "another value" (unless both keys happen to be mapped to the same bucket, in which case either value can be returned, depending on which key would be tested first for equality).
In short: p1 will not be reachable anymore.
In general the map is using the hash function to split the keys to buckets and then the equal function to locate the correct key-value. when you change the value of p1 and with that its hash value. If you will look for it the map will look for the value in a different bucket and will not see it and the p1 that is in the map will not be reachable.

How to give Rank in list of Object and sort on the basis of Rank and any other key?

I have a situation where I need to make a list of object , where Object have (Name, Title) element. for e.g. :
obj1 = ('john', 'colonel')
obj2 = ('Alex', 'major')
obj3 = ('Roy', 'Major general')
obj4 = ('derrick', 'no Rank')
I need to do sorting in two ways:
1. First on the Title basis.
2. For any two names, if the title is same then sorting of object on the
alphabetical name basis(like chronological order of name).
3. And also need to remove duplicate names from the list.
Please help me out as I know how to sort the arraylist but don't know how to give ranking and sort on multiple conditions. If you need further details or do not understand my question then please let me know.
You need to create a Comparator Class, something like this:
class ObjectComparator implements Comparator<SomeClass>{
#Override
public int compare(SomeClass obj1, SomeClass obj2) {
// if two objects' ranks are the same
if(obj1.rank.equals(obj2.rank)){
// then compare their names
return obj1.name.compareTo(obj2.name);
}
else{
return obj1.rank.compareTo(obj2.rank);
}
}
}
I cannot understand how you want to remove the duplicates of the attribute name because that will ruin the sorting, unless you mean to remove the duplicates of the same name and rank (e.g. obj1 = "john" "colonel" and also obj2 ="john" "colonel") then in this case straightforwardly use a Set after overriding the equal and hashCode methods in your class, so your set can know if the two objects are equal...something like this:
#Override
#Override
public boolean equals(Object other){
if(other==null) return false;
if (other == this) return true;
return ((SomeClass)other).name.equalsIgnoreCase(this.name)
&& ((SomeClass)other).rank.equalsIgnoreCase(this.rank);
}
#Override
public int hashCode() {
return Objects.hash(name, rank);
}
But if you mean that you want to remove the duplicates names (i.e. objects have same fields but different ranks), then you may consider using a LinkedHashMap which preserves the order of items, but as I mentioned, this will Not preserve the sorting order for the particular duplicate (TBH I don't think that what you meant by duplicates)... an example for testing:
public class SomeClass{
String name, rank;
public SomeClass(String name, String rank){
this.name = name;
this.rank = rank;
}
public static void main(String[] args) {
SomeClass obj1 = new SomeClass("john", "colonel");
SomeClass obj2 = new SomeClass("XXX", "major");
SomeClass obj3 = new SomeClass("Roy", "general");
SomeClass obj4 = new SomeClass("derrick", "no Rank");
SomeClass obj5 = new SomeClass("john", "something");
SomeClass obj6 = new SomeClass("Alex", "major");
List<SomeClass> list = new ArrayList<>();
list.add(obj1); list.add(obj2); list.add(obj3);
list.add(obj4); list.add(obj5); list.add(obj6);
System.out.println(getSortedObjects(list));
}
public static Map<String, List<String>> getSortedObjects(List<SomeClass> list){
Map<String, List<String>> sortedMap = new LinkedHashMap<>();
// sort the list
Collections.sort(list, new ObjectComparator());
for(SomeClass obj : list){
// for testing sorting before ruining it in a map
System.out.println(obj.name + " " + obj.rank);
List<String> temp;
if(sortedMap.containsKey(obj.name)){
temp = sortedMap.get(obj.name);
temp.add(obj.rank);
sortedMap.put(obj.name, temp);
}
else{
temp = new ArrayList<>();
temp.add(obj.rank);
sortedMap.put(obj.name, temp);
}
}
return sortedMap;
}
}
Test
john colonel
Roy general
Alex major
XXX major
derrick no Rank
john something
{john=[colonel, something], Roy=[general], Alex=[major], XXX=[major], derrick=[no Rank]}
Complete Implementation Example of Set & Comparator:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class SomeClass{
// suppose the fields in your class
String name, rank;
// simple constructor
public SomeClass(String name, String rank){
this.name = name;
this.rank = rank;
}
// you need to override both equals and hashCode methods from
// Object superclass in order the set works properly and realizes
// that two given objects of type SomeClass are equal
#Override
public boolean equals(Object other){
if(other==null) return false;
if (other == this) return true;
return ((SomeClass)other).name.equalsIgnoreCase(this.name)
&& ((SomeClass)other).rank.equalsIgnoreCase(this.rank);
}
#Override
public int hashCode() {
return Objects.hash(name, rank);
}
#Override
public String toString(){
return name + " " + rank;
}
/**
* Pass a set then add the items to a temporary ArrayList
* to be sorted according to the Comparator condition
* then clear the set and add the sorted element back
* #param list
*/
public static void sort(Set<SomeClass> list){
List<SomeClass> temp = new ArrayList<>();
temp.addAll(list);
// anonymous class implementation of Comparator
Collections.sort(temp, new Comparator<SomeClass>(){
#Override
public int compare(SomeClass obj1, SomeClass obj2) {
// if two objects' ranks are the same
if(obj1.rank.equals(obj2.rank)){
// then compare their names
return obj1.name.compareTo(obj2.name);
}
else{
return obj1.rank.compareTo(obj2.rank);
}
}});
list.clear();
list.addAll(temp);
}
// testing
public static void main(String[] args) {
// create Objects of the class with duplicate
SomeClass obj1 = new SomeClass("john", "colonel");
SomeClass obj2 = new SomeClass("XXX", "major");
SomeClass obj3 = new SomeClass("Roy", "general");
SomeClass obj4 = new SomeClass("derrick", "no Rank");
SomeClass obj5 = new SomeClass("john", "something");
SomeClass obj6 = new SomeClass("Alex", "major");
SomeClass obj7 = new SomeClass("Alex", "major"); // duplicate object
// LinkedHashSet PRESERVES the order of elements' insertion
// in addition to removing duplicates
Set<SomeClass> list = new LinkedHashSet<>();
// populate the set
list.add(obj1); list.add(obj2); list.add(obj3);
list.add(obj4); list.add(obj5); list.add(obj6);
list.add(obj7);
//before sorting
System.out.println("Before " + list);
// after sorting
sort(list);
System.out.println("After " + list);
}
}
Test:
Before [john colonel, XXX major, Roy general, derrick no Rank, john something, Alex major]
After [john colonel, Roy general, Alex major, XXX major, derrick no Rank, john something]
You need to read up on Comparators, and implement one for your class.
If you are using Java 8, the easiest way is to use Comparators.comparing
https://praveer09.github.io/technology/2016/06/21/writing-comparators-the-java8-way/
In order to prevent duplicate names, I would keep a separate Set (which automatically constrains uniqueness) of names that already exist. Check to see if that set contains the names before you add the new object to the arraylist.

Java - How to use a for each loop to check the different occurrences of a value in a list of objects

Sorry if the title isn't clear, I wasn't sure how to word it. I have an arraylist of objects and within each of these objects I store an integer value referring to a category and one referring to an ID.
I want to find the number of unique combinations of category and IDs that there are.
So at the moment I have
for(Object object: listofObjects){
//For each unique type of object.getID
//For each unique type of object.getCategory
//Add 1 to counter
}
I can't figure out how to do this. Doing things like for(int cat: object.getCategory()) brings up an error.
I can add the values to a new list within the initial for each loop like so,
ArrayList<Integer> aList= new ArrayList<Integer>();
for (Object object : spriteExplore) {
aList.add(object.getCategory());
}
for (int cat : aList) {
testCounter++;
}
but this obviosuly does not take into account uniqueness and also makes it awkward for factoring in the other variable of ID.
I feel like there is probably some easier work around that I am missing. Any advice?
Thanks in advance.
So you list of UserDefine object in ArrayList and you want to find unique Object.Just create set from list.
For e.g Suppose you have
List<Customer> list=new ArrayList<Custeomer>();
list.add(new Customer("A",12));
list.add(new Customer("B",13));
list.add(new Customer("A",12));
now
create set From this list
Set<Customer> set = new HashSet<Customer>(list);
this will have unique Customer
IMP : dont forget to override equals and hashcode method for Customer
Your best approach would be storing the data correctly.
It's possible that you still need to store non-unique items, if that's so - continue using an ArrayList, but in addition, use the following:
Override the hashcode & equels function as shown in this link:
What issues should be considered when overriding equals and hashCode in Java?
Then, use a Set (HashSet would probably be enough for you) to store all your objects. This data structure will disregard elements which are not unique to elements already inside the set.
Then, all you need to do is query the size of the set, and that gives you the amount of unique elements in the list.
I don't know any library that does this automatically, but you can do it manually using sets. Sets will retain only unique object so if you try to add the same value twice it will only keep one reference.
Set<Integer> categories = new HashSet<Integer>();
Set<Integer> ids= new HashSet<Integer>();
for (Object object : listofObjects) {
categories.add(object.getCategory());
ids.add(object.getID());
}
Then you get the number of unique categories / ids by doing
categories.size()
ids.size()
And all your unique values are stored in the sets if you want to use them.
I would look into using a (Hash)Map<Integer, Integer>. Then just have 1 foreach loop, checking to see if the value of Map<object.getId(), object.getCategory()> is null by checking if map.get(object.getId()) is null - if it is, then this pair does not exist yet, so add this pair into the map by using map.put(object.getId(), object.getCategory()). If not, do nothing. Then at the end, to find the number of unique pairs you can just use map.size()
Hope this helps
Map<Integer,List<Integer>> uniqueCombinations = new HashMap<Integer,List<Integer>>();
for (Object object : listofObjects) {
if(uniqueCombinations.get(object.getCategoryId())==null) {
uniqueCombinations.put(object.getCategoryId(), new LinkedList<Integer>);
}
uniqueCombinations.get(object.getCategoryId()).add(object.getId());
}
return uniqueCombinations.size()
I believe you want unique combinations of both category and id, right?
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class SO {
class MyObject{
private int id;
private int category;
private String name;
private MyObject(int id, int category,String name) {
super();
this.id = id;
this.category = category;
this.name = name;
}
protected int getId() {
return id;
}
protected int getCategory() {
return category;
}
#Override
public String toString() {
return "MyObject [id=" + id + ", category=" + category + ", name=" + name + "]";
}
}
public static void main(String[] args) {
SO so = new SO();
List<Object> listofObjects = new ArrayList<Object>();
listofObjects.add(so.new MyObject(1,1,"One"));
listofObjects.add(so.new MyObject(1,1,"Two"));
listofObjects.add(so.new MyObject(1,2,"Three"));
Map<String,List<MyObject>> combinations = new HashMap<String,List<MyObject>>();
for(Object object: listofObjects ){
//For each unique type of object.getID
//For each unique type of object.getCategory
//Add 1 to counter
if (object instanceof MyObject){
MyObject obj = (MyObject)object;
String unique = obj.id+"-"+obj.category;
if (combinations.get(unique) == null){
combinations.put(unique, new ArrayList<MyObject>());
}
combinations.get(unique).add(obj);
}
}
System.out.println(combinations);
//counts
for(Entry<String,List<MyObject>> entry:combinations.entrySet()){
System.out.println(entry.getKey()+"="+entry.getValue().size());
}
}
}
Use the Hashmap to save occurence. Dont forget to implement hashcode und equals Methods. You can generate them if you work with Eclipse IDE.
public static void main(String[] args) {
List<MyObject> myObjects = Arrays.asList(new MyObject(1, 2), new MyObject(2, 3), new MyObject(3, 4), new MyObject(3, 4));
Map<MyObject, Integer> map = new HashMap<>();
for (MyObject myObject : myObjects) {
Integer counter = map.get(myObject);
if(counter == null){
counter = 1;
} else {
counter = counter + 1;
}
map.put(myObject, counter);
}
long uniqueness = 0;
for(Integer i : map.values()){
if(i == 1){
++uniqueness;
}
}
System.out.println(uniqueness);
}
The last part can be replaced by this one line expression if you are working with Java 8:
long uniqueness = map.values().stream().filter(i -> i == 1).count();

HashMap with Overriding hashcode() and equals() is not working in my case

this is first time I am posting problem.
Please help me to solve my problem.
In this code i am using HasMap to store key-value pairs, here key is String with three SubStrings separated by " " blank space delimiter.
For example,
String t1 = new String("A B C");
and stored in HashMap as-
m.put(t1,27);
Here, A, B and C are three different Strings. Where different combinations of A,B,C assumed as unique.
Like "A B C", "B A C", "C B A" are all treated as equal.
I implemented hashCode() and equal() for this,
Below code should print only
A B C:61046662
But it is not even calling hashCode() or equals(). Please give me some suggestion.
public class Test {
public int hashCode(){
System.out.println("hashcode method called");
return this.toString().length();
}
public boolean equals(Object obj) {
System.out.println("equal method called ");
int count = 0;
if(!(obj instanceof String))
return false;
if (obj == this)
return true;
count = 0;
StringTokenizer st = new StringTokenizer(((String)obj).toString(), " ");
while(st.hasMoreTokens()){
if(this.toString().contains(st.nextToken())){
count ++;
}
}
return (count == 3);
}
public static void main(String[] args) {
HashMap<String, Integer> m = new HashMap<String, Integer>();
String t1 = new String("A B C");
String t2 = new String("B A C");
String t3 = new String("C B A");
m.put(t1, 27);
m.put(t2, 34);
m.put(t3, 45);
System.out.println(m.get("A B C"));
for(Entry e : m.entrySet()){
System.out.println(((String)e.getKey())+":" +e.getKey().hashCode());
}
}
}
Your equals() and hashCode() methods don't come into the picture because the map keys are of type String, not of type Test. Thus the standard string comparison and hashcode are being used.
You'll need to modify Test so that it holds the string, and change equals() and hashCode() accordingly. You'll then need to change the map to be of type HashMap<Test,Integer>.
The hashCode of the key is used to determine the position and uniqueness of the key. You are adding String objects to the map, so the String.hashCode method is used.
Though you have implemented hashCode for your Test class, these are not used.
To solve your problem you would create your own class used as key, with your own hashCode implementation.
Using your example, you would add a property to your Test class which can hold a string, and use the Test class as key in your map.

Categories

Resources