Java Hash Tables and Hash Maps - java

Have been going through the Java for sometime now and got stuck at yet another question. HashMaps or HashTables.
have gone through the basics..how a hash table is implemented..like calculating a hash value..storing data at the hash value(or probably hashvalue % maxArray) index..linear probing and chaining for collisions.
Now to further, if somebody could please help with below:
Basic examples show storing Strings like "Jhon", "Lisa" , "Sam" etc in an array then going through collisions and converting the array to Linked list to store the names which is all good to understand and super fine.
But Hashtables store Key,Value pair. So how it is achieved?
The key,value pair examples show "Telephone Directory" example, but they do not show how they are stored in Arrays or linked list. It is just Map.put(k,v) and Map.get(k). Linked list store "One Data" and "Pointer".. so how can we store both Keys and Values in Linked list( A picture might help in understanding)
A bit more practical example for Hash Table than map.put("Rocket", 01111111111).

But Hashtables store Key,Value pair. So how it is achieved?
Imagine a data structure like this:
Node<K, V>[] table;
public static class Node<K, V> {
//voila! Key and Value stored here
private K key;
private V value;
//make it a linked list
private Node<K, V> next;
}
The key,value pair examples show "Telephone Directory" example, but they do not show how they are stored in Arrays or linked list. It is just Map.put(k,v) and Map.get(k). Linked list store "One Data" and "Pointer".. so how can we store both Keys and Values in Linked list( A picture might help in understanding)
Check point 1.
A bit more practical example for Hash Table than map.put("Rocket", 01111111111).
Here you go:
public class PersonIdentifier {
private final int id;
private final String ssn;
public PersonIdentifier(int id, String ssn) {
this.id = id;
this.ssn = ssn;
}
//getters, NO setters since fields are final...
//now the relevant methods!
#Override
public int hashCode() {
//available since Java 7...
return Objects.hash(id, ssn);
}
#Override
public boolean equals(Object o) {
if (o == null) return null;
if (this == o) return true;
if (!o.getClass().equals(this.getClass())) return false;
Person another = (Person)o;
return another.id == this.id && another.ssn.equals(this.ssn);
}
}
//...
Map<PersonIdentifier, Person> peopleMap = new HashMap<>();
peopleMap.put(new PersonIdentifier(1, "123456789"), new Person(/* fill with values like firstName, lastName, etc... */));
peopleMap.put(new PersonIdentifier(2, "987654321"), new Person(/* fill with values like firstName, lastName, etc... */));
//and on...

Related

Need help to understand behaviour of HashMap [duplicate]

This question already has answers here:
Are mutable hashmap keys a dangerous practice?
(10 answers)
Closed 7 years ago.
Let's say I have a person class and equality is based on id attribute. Below is the implementation of Person class -
class Person {
private int id;
private String firstName;
public Person(int id, String firstName) {
super();
this.id = id;
this.firstName = firstName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public int hashCode() {
return this.id;
}
public boolean equals(Object obj) {
return ((Person) obj).getId() == this.id;
}
}
I am using Person class as as key of a HashMap. Now see below code -
import java.util.HashMap;
public class TestReport {
public static void main(String[] args) {
Person person1 = new Person(1, "Person 1");
Person person2 = new Person(2, "Person 2");
HashMap<Person, String> testMap = new HashMap<Person, String>();
testMap.put(person1, "Person 1");
testMap.put(person2, "Person 2");
person1.setId(2);
System.out.println(testMap.get(person1));
System.out.println(testMap.get(person2));
}
}
Notice, though we have added two different person object as key to the HashMap, later we have changed the id of person1 object to 2 to make both the person object equal.
Now, I am getting output as -
Person 2
Person 2
I can see there are two key-value pairs in the HashMap with data: "person1/Person 1" and "person2/Person 2", still I will always get "Person 2" as output and I can never access value "Person 1". Also notice, we have duplicate key in HashMap.
I can understand the behavior after looking at the source code, but doesn't it seem to be problem? Can we take some precaution to prevent it?
It all depends on how hashCode() value is used by HashMap.
While it is required that two equal objects of same hash code, reverse is not necessarily true. Two unequal objects can have same hash code (as int has only finite set of possible values).
Everytime you put an object in HashMap, it stores the object in a bucket identified by key's hashCode(). So, based on how hashCode() is implemented, you should have a fair distribution of entries in various buckets.
Now, when you try to retrieve a value, the HashMap will identify the bucket in which given key falls, and then will iterate through all keys in that bucket to pick the entry for given key - in this stage it will use equals() method to identify the entry.
In your case, person1 is sitting in bucket 1 and person2 is sitting in bucket 2.
However, when you changed the hashCode() value of person1 by updating its id, the HashMap is unaware of this change. Later, when you look up an entry using person1 as key, the HashMap thinks that it should be present in bucket 2 (as person1.hashCode() is 2 now), and after that when it iterates bucket 2 using equals method, it finds an entry of person2 and thinks that it is the object that you are interested in as equals in your case too is based on id attribute.
Above explanation is evident when one looks at implementation of HashMap#get method as shown below:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
PS Sometimes when you know an answer to question, you forget to lookup for duplicate questions, and jump right into answering the question before anyone can reply - that's what happened in this case. Should be careful next time :-)
This is happening because your equals() method uses the hashcode only. You should be comparing all your person fields like firstName.

Which collection to use?

What kind of collection should I use if I need to create a collection that will allow me to store books and how many copies there are in circulation (for a library)?
I would use an ArrayList, but I also want to be able to sort the books by order of issue year.
You can create a Book Class with all the attributes you have for a book. And implement a Comparable for that Book Class and write sorting logic in there.
Maintain a List<Book>, and use Collections.sort method, to sort your List according to the implemented Sorting logic.
UPDATE: -
As far as, fast look-up is concerned, a Map is always the best bet. And is appropriate to implement a dictionary look-up kind of structure. For that, you would need some attribute that uniquely identifies each book. And then store your book as Map<String, Book>, where your key might be id of type String.
Also, in this case, your sorting logic will change a little. Now you would have to sort on the basis of your Map's value, i.e. on the basis of attributes of Book.
Here's a sample code you can make use of. I have just considered sorting on the basis of id. You can change the sorting logic as needed: -
class Book {
private int id;
private String title;
public Book() {
}
public Book(int id, String title) {
this.id = id;
this.title = title;
}
#Override
public String toString() {
return "Book[Title:" + this.getTitle() + ", Id:" + this.getId() + "]";
}
// Getters and Setters
}
public class Demo {
public static void main(String[] args) {
final Map<String, Book> map = new HashMap<String, Book>() {
{
put("b1", new Book(3, "abc"));
put("b2", new Book(2, "c"));
}
};
List<Map.Entry<String, Book>> keyList = new LinkedList<Map.Entry<String, Book>>(map.entrySet());
Collections.sort(keyList, new Comparator<Map.Entry<String, Book>>() {
#Override
public int compare(Map.Entry<String, Book> o1, Map.Entry<String, Book> o2) {
return o1.getValue().getId() - o2.getValue().getId();
}
});
Map<String, Book> result = new LinkedHashMap<String, Book>();
for (Iterator<Map.Entry<String, Book>> it = keyList.iterator(); it.hasNext();) {
Map.Entry<String, Book> entry = it.next();
result.put(entry.getKey(), entry.getValue());
}
System.out.println(result);
}
}
OUTPUT: -
"{b2=Book[Title:c, Id:2], b1=Book[Title:abc, Id:3]}"
Well, If the entire purpose of your collection is to store the counts of the books, than a dictionary/map, or whatever java's key-value collection is called.
It would probably have title as your key, and the count as your value.
Now I suspect that your collection might be a little more complicated than that, so you might want to make a Book class which has Count as a field, and then I'd probably have a string -> Book dictionary/map anyway, with the string as it's dewy decimal number or some other unique identifier.
Beyond a simple educational or toy project, you'd want to use a database rather than an in-memory collection. (Not really an answer, but I think worth stating.)
java.util.TreeMap can be used to index and sort this kind of requirements.
Check http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html for more details.
You can use your Book object as key mapped to the number of copies as the value.

how can Ikeep one and remove other duplicate arrayList objects and update another object property?

I need your help in JAVA (with some sample code if possible) regarding to the following scenario:
I have a list with a classes object and want to check if one object property has duplicates then keep one of them and add others amounts with the kept one's amount. For example:
I have this class:
class Salary {
String names;
Double amount;
}
and the list say salary_list contains the following elements in it(for example):
[jony,john 300.96]
[fuse,norvi,newby 1000.55]
[john,jony 22.6]
[richard,ravi,navin 55.6]
[fuse,norvi,newby 200.6]
... ... ...
So what is my expected output is the same input list with the following revised result:
[jony,john 323.56]
[fuse,norvi,newby 1201.15]
[richard,ravi,navin 55.6]
N.B: order in names is not important so not the order of the elements after the duplicate elimination.
I am not good at english as well as in Java. So forgive me if any mistakes there.
Thanks in advance.
Enhance your Salary class as follows:
class Salary {
String names;
Double amount;
private String sortedNames = null;
#Override
public boolean equals(Object o)
{
if (o == null || ! (o instanceof Salary)) return false;
Salary othr = (Salary) o;
String thisNames = this.getSortedNames();
String othrNames = othr.getSortedNames();
return thisNames.equals(othrNames);
}
#Override
public int hashCode()
{
return getSortedNames().hashCode();
}
public String getSortedNames()
{
if (this.sortedNames == null)
{
String[] nameArr = this.names.split(",");
Arrays.sort(nameArr);
StringBuilder buf = new StringBuilder();
for (String n : nameArr)
buf.append(",").append(n);
this.sortedNames = buf.substring(buf.length()==0?0:1);
}
return this.sortedNames;
}
}
This assumes that Salary is immutable (that is, after it's created the values of names and amount won't change. You could then use this with a hash map to add up all the amounts having the same names.
Map<String,Salary> map = new HashMap<String,Salary>();
for (Salary s : list)
{
Salary e = map.get(s.getSortedNames());
if (e == null)
map.put(s.getSortedNames(), s);
else
e.amount += s.amount;
}
At this point the map contains all unique Salary objects with the total amount for each.
You have few very helpful tools in Java. You can
split String into array defining separator like "jony,john".split(",") will give you array {"jony","john"}
sort data in arrays by Arrays.sort(arrayToSort)
compare if arrays are equal (contain same values with same order) by Arrays.equals(array1, array2) or by comparing its String representation using Arrays.toString(array)
use Maps (like HashMap) to hold pairs [key -> value]
With these you can solve your problem.
Tip: You can use Map like <String, Double> to count your data. As key (String) you can use sorted representation of names. If Map already contains some sorted representation of names then increase value stored under that key.

Sorting a HashMap with 2 fields

I have a hashmap with 8 fields. Among those 2 are id and idseq. Both are integer. There can be more than one similar idseq, but not for one id. Can this hasp map be sorted on the basis of these 2?
Create a key containing these two integer values and use that as a key for your map. Make this key Comparable and implement your sorting logic there.
Something like this:
class MyCustomKey implements Comparable<MyCustomKey> {
final int id;
final int idSeq;
public MyCustomKey(int id, int idSeq) {
this.id = id;
this.idSeq = idSeq;
}
public int getId() {
return this.id;
}
public int getIdSeq() {
return this.idSeq;
}
#Override
public int compareTo(MyCustomKey o) {
// your compare logic goes here
return -1;
}
}
You can then use this as the key for your map, preferably a TreeMap if it should be sorted.
Use a TreeMap instead with custom Comparator which you should pass to its constructor.
We can use a Tree map like this:
TreeMap<Integer,DRG> sortedMap = new TreeMap<Integer,DRG>();
sortedMap.putAll(hashmap);
Treemap will take care of the rest. The order of values as represented in the database can be restored by using a Tree map.

how to swap key in map?

is there a way to sort this numbers stored in a string variable?
TreeMap<String,List<QBFElement>> qbfElementMap = new TreeMap<String, List<QBFElement>>();
this is the map where the key is :
27525-1813,
27525-3989,
27525-4083,
27525-4670,
27525-4911,
27526-558,
27526-1303,
27526-3641,
27526-4102,
27527-683,
27527-2411,
27527-4342
this is the list of keys and the value for each of the key is a list.
now, how can i sort this key in ascending order by number.
ex. if i want to sort : 1,2,11,20,31,3,10
i want to have as output is : 1,2,3,10,11,20,31
but when i use the autosort of treemap the output goes : 1,10,11,2,20,3,31
how can i sort it in ascending order by numeric?
and the language is java :) thank you:)
The keys in your map are not Integer but String values. That's why the key's are sorted like observed.
Either change the Map to
TreeMap<Long,List<QBFElement>> qbfElementMap
or create it with a specialized Comparatorthat will provide the expected numerical order for the String type keys.
A mapping from your String values to Longs could be done like this:
private Long convertToLongTypeKey(String key) {
String[] parts = key.split("-");
// next lines assumes, that the second part is in range 0...9999
return Long.parseLong(parts[0]) * 10000 + Long.parseLong(parts[1]);
}
An implementation of Comparator<String> could use the same mapping to create a numerical comparision of two String based keys:
new TreeMap<String,List<QBFElement>>(new Comparator<String>(){
#Override
public int compare(String key1, String key2) {
String[] parts1 = key1.split("-");
Long long1 = Long.parseLong(parts1[0]) * 10000 + Long.parseLong(parts1[1]);
String[] parts2 = key2.split("-");
Long long2 = Long.parseLong(parts2[0]) * 10000 + Long.parseLong(parts2[1]);
return long1.compareTo(long2);
}
});
You can change the way that the TreeMap sorts its keys by providing a custom comparator to the constructor. If you want, you can define a new Comparator that compares strings by breaking them up into numeric components.
It seems like a better idea, though, would be to not use Strings as your keys. The data you're using as keys is clearly not textual - it's numeric - and you might want to define a custom type to represent it. For example:
public class KeyType implements Comparable<KeyType> {
private final int first;
private final int second;
public KeyType(int first, int second) {
this.first = first;
this.second = second;
}
#Override
public boolean equals(Object other) {
if (!(other instanceof KeyType)) return false;
KeyType realOther = (KeyType) other;
return realOther.first == first && realOther.second == second;
}
#Override
public int hashCode() {
return first + 31 * second;
}
public int compareTo(KeyType other) {
if (first != other.first)
return first - other.first;
return second - other.second;
}
}
This approach is the most expressive and robust. It gives you better access to the individual fields of the keys you're using, and also prevents you from adding nonsensical keys into the map like the string "Lalalalala". I'd strongly suggest using this approach, or at least one like it. The type system is your friend.
A TreeMap can take a custom comparator for custom sorting. Write a comparator that sorts the keys the way you want and use it when you create the treemap
TreeMap<String,List<QBFElement>> qbfElementMap = new TreeMap<String, List<QBFElement>>(myComparator);

Categories

Resources