I'm trying to use HashMap and Hastable with a List of Object as Key.
Please see below a simplified version of my code which doesn't work.
When I debug this code, I expect having 3 items in the TestMap4 Object but there is only 1.
List<String> lst = new ArrayList<>();
lst.add("Hello");
lst.add("World");
Map<List<String>, Integer> testMap4 = new HashMap<List<String>, Integer>();
testMap4.put(lst, 1);
testMap4.put(lst, 2);
testMap4.put(lst, 5);
What happens when I put a new item into the HashMap object ? why doesn't it work ?
I obtain the same result with this new example below. (Each List countains the same 2 String)
List<String> lst = new ArrayList<>();
lst.add("Hello");
lst.add("World");
List<String> lst2 = new ArrayList<>();
lst2.add("Hello");
lst2.add("World");
List<String> lst3 = new ArrayList<>();
lst3.add("Hello");
lst3.add("World");
Map<List<String>, Integer> testMap4 = new HashMap<List<String>, Integer>();
testMap4.put(lst,1);
testMap4.put(lst2,2);
testMap4.put(lst3,5);
If I modify only 1 char of the 2 String, this is OK
You do not understand the concept of HashMap.
Your problem is that you are using the same key each time.
testMap4.put(lst, 1); // <----same key, different value
testMap4.put(lst, 2); // <----same key, different value
testMap4.put(lst, 5); // <----same key, different value
In Hashmap, every value that is stored in the Hashmap, there's a key that is saved with that particular value and is unique for each value stored in Hashmap
Important points about HashMap:
1- A HashMap contains values based on the key.
2- It contains only unique elements.
3- It may have one null key and multiple null values.
4- It maintains no order.
Example
HashMap<Integer,String> hm = new HashMap<>();
Secondarily, using a mutable object (a List<>) as the key results in undefined behavior if any of the lists are modified after they are inserted into the map. The hash code is calculated according to the contract for List (see the Javadoc) only when the entry is first inserted into the map. A change to the list's contents will change the hash code and you will no longer be able to find the entry.
Using a List<> (or any mutable object) as the key in a HashMap<> is a Really Bad Idea™.
It is not working because you are using same key each time for storing different value due to which all the value are getting map to same key and hashmap is only storing the last value since this value override the previous values.
HashMap calls the hashCode() method on the key-object you put in the HashMap.
As you don't have overridden it for the key class you use (List<> in your case) it calls the hashCode() method on java.lang.Objectwhich returns a unique object id.
As you put the same object three times into the Map, it is the same key you put in three times in a row.
List<String> lst1 = new ArrayList<>();
lst.add("Hello");
lst.add("World");
List<String> lst2 = new ArrayList<>();
List<String> lst3 = new ArrayList<>();
Map<List<String>, Integer> testMap4 = new HashMap<List<String>, Integer>();
testMap4.put(lst1, 1);
testMap4.put(lst2, 2);
testMap4.put(lst3, 5);
will give you three entries in your Map.
If you need a HashCode over the contents of the list for usage as HashMap keys, have a look at:
static int java.util.Objects.hash(Object... values)
static boolean java.util.Arrays.equals(Object[] a, Object[] a2)
Don't forget that you always have to override both methods. hashCode() and equals(). You will instantly drop dead, if you only override one of them!! ;)
Related
I am having an arraylist which contains a list of numbers. I want to get all the values from the HashMap which has the keys which are in the array list.
For example say the array list contains 1,2,3,4,5,6,7,8,9 list
I want to get all the values for the keys 1,2,3,4,5,6,7,8,9 map
So currently I am implementing
for (i=0;i<list.size;i++){
map_new.put(list.get(),map.get(list.get()))
}
Is there any efficient way to do this?
Your code basically assumes that map.get(list.get()) always returns a value, you can try the following code which first filters the not null values from the list object and then adds to the new Map:
Map<String, Integer> newMap = list.stream().
filter(key -> (map.get(key) != null)).//filter values not present in Map
collect(Collectors.toMap(t -> t, t -> map.get(t)));//now collect to a new Map
In case, if map.get(list.get()) returns null, your code creates a Map with null values in it for which you might end up doing null checks, which is not good, rather you can ensure that your newly created Map always contains a value for each key.
Assuming the signature of list and the map are as following
List<Integer> list;
Map<Integer, Integer> map;
You can use following
for(int a : list){
Integer b = map.get(a);
if(b != null)
// b is your desired value you can store in another collection
}
Which is similar to the procedure you have already used.
As you can access the map in O(1) so the complexity of this code will be O(listsize)
There is not much you can do for efficiency. Still couple of small things you can do considering code example you have given above:
1) Change your for loop to
for(Long num : list)
instead of iterating using index, this will reduce you get calls over list.
2) You can update the existing map , so that you even do not need to iterate.
map.keySet().retainAll(list);
for(Long key: map.keySet()) {
System.out.println(map.get(key));
}
With this existing map will contain only those data whose keys are present in list, but you should use it carefully depending upon rest of the code logic.
You can capitalize on the fact that the keyset of a map is backed by the map itself and modifications to the keyset will reflect back to the map itself. This way, you can use the retainAll() method of the Set interface to reduce the map with a single line of code. Here is an example:
final Map<Integer, String> m = new HashMap<Integer, String>();
m.put(1, "A");
m.put(2, "B");
m.put(3, "C");
m.put(4, "D");
m.put(5, "E");
final List<Integer> al = Arrays.asList(new Integer[] { 2, 4, 5 });
System.out.println(m);
m.keySet().retainAll(al);
System.out.println(m);
This will output:
{1=A, 2=B, 3=C, 4=D, 5=E}
{2=B, 4=D, 5=E}
Is it possible to get list of values from HashMap as a reference
class MyCustomObject {
String name;
Integer id;
MyCustomObject(String name, Integer id){
this.name = name;
this.id = id;
}
}
HashMap<Integer, MyCustomObject> map = new LinkedHashMap<>();
map.put (1, new MyCustomObject("abc",1));
map.put (2, new MyCustomObject("xyz",2));
List<MyCustomObject> list = new ArrayList<>(map.values());
Log.i(TAG,"************ List from HashMap ************");
for (MyCustomObject s : list) {
Log.i(TAG,"name = "+s.name);
}
list.set(0,new MyCustomObject("temp",3));
Log.i(TAG,"************ List from HashMap after update ************");
for (MyCustomObject s : list) {
Log.i(TAG,"name = "+s.name);
}
Log.i(TAG,"************ List from HashMap ************");
List<MyCustomObject> list2 = new ArrayList<>(map.values());
for (MyCustomObject s : list2) {
Log.i(TAG,"name = "+s.name);
}
Output
**************** List from HashMap ***************
name = abc
name = xyz
**************** List from HashMap after update ***************
name = temp
name = xyz
**************** List from HashMap ***************
name = abc
name = xyz
Here if get list of values from HashMap it return deep-copy.
Update
My Requirement
I want list of values from HashMap because I want to access items using their position
I want to preserve order of values
If I modify anything in the extracted list then it should reflect in HashMap too
Please do tell, if any third party library provide such data structure, or what would be best approach to handle this situation
You are creating an new List based on the values of the Map :
List<MyCustomObject> list = new ArrayList<>(map.values());
That's what creates the copy of the values Collection, and changes in that List cannot be reflected in the original Map.
If you modify the Collection returned by map.values() directly (for example, map.values().remove(new MyCustomObject("abc",1))), it will be reflected in the contents of the original Map. You wouldn't be able to call set on the Collection, though, since Collection doesn't have that method.
Collection values()
Returns a Collection view of the values contained in this map. The
collection is backed by the map, so changes to the map are reflected
in the collection, and vice-versa.
So use a Collection and assign values() to it. Or the entrySet().
Try using the map entries which are backed by the map and which you get by calling entrySet(). A list of those almost works like you want it to do (although I'd still advocate you directly use map.put( key, updatedValue ).
Example:
Map<String, Integer> map = new HashMap<>();
map.put( "a", 1 );
map.put( "b", 2 );
//you create a list that's not backed by the map here but that isn't a problem
//since the list elements, i.e. the entries, are backed by the map
List<Entry<String, Integer>> entryList = new ArrayList<>(map.entrySet());
entryList.get(0).setValue( 5 );
System.out.println( map ); //prints: {a=5, b=2} (note that order is a coincidence here)
One final note though: as I already stated in my comment when dealing with a map order is not always deterministic (unless you know you're dealing with an ordered map like TreeMap) and thus using indices may introduces bugs or undesired behavior. That's why you'll want to at least check the key in most cases and thus you either need to use Map.Entry (which btw can't have its key altered, for good reasons) or use the key directly in which case you don't need a list/collection of values or entries anyways.
I want to determine if a given String startsWith any key in a Map.
The simple solution is to iterate through entire the keySet.
private static Map<String, String> someMap;
private static void method(String line) {
for (String key : someMap.keySet()) {
if (line.startsWith(key)) {
// do something with someMap.get(key);
}
}
}
My question is: Is there is a better data structure to handle this problem?
This can't be done directly with an HashMap: the problem is that HashMap uses an hash calculated on the key to manage its position inside the collection. So there is no way to search for a String key which starts with a specific substring since there is no correlation between two similar String values and their hashes.
But nothing is lost, if you switch to a TreeMap<String,String> the problem can be solved easily. A TreeMap is still an associative container but it stores entries by using a red-black tree in a sorted order.
This means that elements inside a TreeMap are always sorted. In addition to this it gives you some functionalities like:
Map.Entry<K,V> ceilingEntry(K key): Returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
Map.Entry<K,V> floorEntry(K key): Returns a key-value mapping associated with the greatest key less than or equal to the given key, or null if there is no such key.
Now, not only you can search for a specific key by using a substring of its value, but you also do it in an efficient way. Mind that this works thanks to the implementation of compareTo of String class.
So your problem becomes trivial:
TreeMap<String, Object> map = new TreeMap<String, Object>();
map.put("baz", new Object());
map.put("foo", new Object());
map.put("fooz", new Object());
map.put("fo", new Object());
Map.Entry<String, Object> test = map.ceilingEntry("fo");
bool containsSubStringKey = test != null && test.getKey().startsWith("fo");
TreeMap<String, Object> map = new TreeMap<String, Object>();
map.put("baz", new Object());
map.put("foo", new Object());
map.put("fooz", new Object());
map.put("foor", new Object());
NavigableMap tempMap = list.subMap("foo", true, "fop", false);
//This will return a map of keys that start with "foo". true means inclusive and //false exclusive. "fop" is the first key that does not start with "foo"
The following program maintains 2 data structures named:
map of type HashMap
map_1 also of type HashMap
At the beginning map is populated with key : 1 and value : suhail. Then this map is inserted into map_1 with key 20.
Again the map is populated with key : 1 and another value : CSE. This map is again inserted into map_1.
import java.util.*;
class KeyTest {
public static void main(String args[]) {
Map<Integer,String> map = new HashMap<Integer,String>();
Map<Integer,Object> map_1 = new HashMap<Integer,Object>();
map.put(1,"suhail");
map_1.put(20,map);
map.put(1,"CSE");
map_1.put(21,map);
Set<Integer> keys = map_1.keySet();
Iterator i = keys.iterator();
while(i.hasNext()) {
System.out.println(map_1.get((Integer)i.next()));
}
}
}
This is what I get, when I print map_1 values :
{1=CSE}
{1=CSE}
But this is not, what I expected.According to me this is how the program should have been running :
[1,suhail]--> map
[20,[1,suhail]]---> map_1
[1,CSE]--> map (A replace, because of the same keys)
[21,[1,CSE]]--->map_1
So the output should have been :
[1,suhail]
[1,CSE]
Can anyone please explain me, why don't I get this output
When you insert an object map into map_1, it does not get copied. When you modify it outside the map_1, the stored object is modified, too, because it's the same object.
Re-assign a new map to map to fix this problem:
Map<Integer,Object> map_1 = new HashMap<Integer,Object>();
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"suhail");
// The first "map" object gets inserted
map_1.put(20,map);
// Make a new object
map = new HashMap<Integer,String>();
map.put(1,"CSE");
// Now the second object gets inserted
map_1.put(21,map);
In a map you can have one value for one key so if you put something again on the same key that will get replaced with the new object. so when you put something again on key:1 the value is replaced. you have to create a new object to bypass that behavior means new map.
and you have put map ref in the map1 so when you edit it its inner copy is also modified.
The output is correct, in the below statement
map.put(1,"suhail");
//--> map has [1 = suhail]
map_1.put(20,map);
//--> map1 has [20 = [1 = suhail]]
map.put(1,"CSE");
//--> You are modifying the same map reference, hence now map is [1 = "CSE"]
map_1.put(21,map);
//--> you are putting the same map ref to map1 with new key so map1 is [20 = [1 = "CSE"], [21 = [1 = "CSE"]]
Since you are using the same map object the output is like that. If you wish to create a new map, you may need to assign a new HashMap to map reference
You are printing only values hence the output
This is because you are storing the reference of the object, in the map, and not it's value.
In your map_1, both key 20 and 21 links to the same object (map).
You'll have to create a new map object for each key if you want them to differ(for instance in a loop).
And by the way, you are just printing the values, and not the keys, and that's why 20 and 21 are not shown in your output (assuming that was part of the question, of course)
Here's hoping it helps.
That's because you're overwriting the last value in the same Map instance.
map <- [1: "suhail"]
map_1 <- [20: [1: "suhail"]]
map <- 1, 'CSE' (you lost "suhail", overwriting key 1) [1: "CSE"]
map_1 <- 21, map, same object in two different keys. [20: [1: "CSE"], 21: [1: "CSE"]]
In that case you have the same instance in related to two different keys in map_1.
To do what you want, you have to create a new Map instance for map.
import java.util.*;
class KeyTest {
public static void main(String args[]) {
Map<Integer,String> map = new HashMap<Integer,String>();
Map<Integer,Object> map_1 = new HashMap<Integer,Object>();
map.put(1,"suhail");
map_1.put(20,map);
map = new HashMap<Integer,String>();
map.put(1,"CSE");
map_1.put(21,map);
Set<Integer> keys = map_1.keySet();
Iterator i = keys.iterator();
while(i.hasNext()) {
System.out.println(map_1.get((Integer)i.next()));
}
}
}
If I extract an element from a hash map through the method get(<key>) and update the extracted element, will these updates persist in the map? Or do I have to re-insert the element back to the hash map?
If you change fields of the object you got out, like this...
Thing thing = map.get(key);
thing.setOtherThing(yetAnotherThing);
then that'll update the value in the map.
On the other hand, if you modify the reference that you obtained by getting a value out of the map...
Thing thing = map.get(key);
thing = doSomethingWith(thing);
then you need to put it back into the map.
If you modify the object obtained by the Map.get(K) method, the object does not need to be re-inserted. However, if you change a key in a way that the hashCode() function is affected, then you need to remove the map entry before modifying the key and then you can put back your value using your new key.
Consider the following map corruption case:
Map<List<String>, String> map = new HashMap<List<String>, String>();
List<String> key1 = new ArrayList<String>();
key1.add("key1");
map.put(key1, "value1");
System.out.println(map.get(key1)); //prints "value1"
key1.add("buzz2");
System.out.println(map.get(key1)); //prints "null"
List<String> k = map.keySet().iterator().next();
System.out.println(map.get(k)); //prints "null"
Morale of the story: for maps, always use immutable keys like String or int.