I'm wondering if there is an more efficient way to do this:
private void updateRelations(String relation, Gene gen) {
if (relations.containsKey(gen)) {
HashSet<String> relationDat= relations.get(gen);
relationDat.add(relation);
relations.put(gen, relationDat);
}
else {
HashSet<String> relationDat = new HashSet<>();
relationDat.add(relation);
relations.put(gen, relationDat);
}
}
Both Gene and Relation are objects. So what I do is I check if there is already a Gene object (with the same gene ID) and if this is the case I want to add the new found relation as a value. If not I just create a new key value pair. Is there a more efficient way to do this?
In Java 8 you can use computeIfAbsent - it will add a value associated with the given key if the key is not present in the Map.
computeIfAbsent returns the current (existing or computed) value associated with the specified key, so it will return either an existing HashSet or a newly created HashSet, to which you can add the new element :
relations.computeIfAbsent(gen, k -> new HashSet<String>()).add(relation);
I have refactored your code. Check this
private void updateRelations(String relation, Gene gen) {
HashSet<String> relationDat;
if (relations.containsKey(gen)) {
relationDat= relations.get(gen);
} else {
relationDat = new HashSet<>();
}
relationDat.add(relation);
relations.put(gen, relationDat);
}
You can use ternary operator instead of if else to simplify it further.
private void updateRelations(String relation, Gene gen) {
HashSet<String> relationDat = (relations.containsKey(gen)) ?
relations.get(gen) : new HashSet<>();
relationDat.add(relation);
relations.put(gen, relationDat);
}
Your code is fine. However, You should not include the line 5:
relations.put(gen, relationDat);
That code is not necessary as relationDat is an objec
What you are trying to do is perfectly handled by a data structure called Multimap, from Google guava's library.
You can see a Multimap as Map<K, List<V>> or Map<K, Set<V>>. A Multimap is a general way to associate keys with arbitrarily many values.
For instance :
private Multimap<Gen, String> relations = HashMultimap.create();
private void updateRelations(String relation, Gene gen) {
relations.put(gen, relation);
}
Here is the documentation
Related
I have a HashMap like this:
private HashMap<Integer, HashMap<String, Material>> logs = new HashMap<>();
Then I have multiple Materials stored as enum (for example. Material.OAK_LOG).
Is there any easy way to check if HashMap logs contains HashMap with specific Material?
I came up with this, which works, but I want to know if there is any other way to do this without looping through the entire HashMap
private boolean hasLog(Material mat){
boolean contains = false;
for (Map.Entry<Integer, HashMap<String, Material>> entry : this.logs.entrySet()) {
if(entry.getValue().containsValue(mat)){
contains = true;
break;
}
}
return contains;
}
No, you have to loop through the maps, doing sequential search.
You can simplify the logic a little by using values() instead of entrySet(), and simply return directly, but that's just minor refactoring:
private boolean hasLog(Material mat) {
for (HashMap<String, Material> submap : this.logs.values())
if (submap.containsValue(mat))
returns true;
return false;
}
You can write the same logic using Java 8+ Streams, but it is the same nested loop sequential search, so runtime complexity remains O(nm).
private boolean hasLog(Material mat) {
return this.logs.values().stream()
.anyMatch(submap -> submap.containsValue(mat));
}
If your Material objects are immutable and unique from an equals perspective, you could use them as a key in a cross reference map. But if Material will change, your maps could get corrupted depending on how equals is set up.
Map<Material, String> crossRef = new HashMap<>();
Whenever you add a new Map with a material to logs, do the following:
int outerKey; = ... // some integer to get the inner map
String innerKey = .. // some string to get the actual Material
Map<String, Material> innerMap = logs.get(outerKey);
Material mat = new Material(...);
innerMap.put(innerKey, mat);
crossRef.put(mat, outerKey+"_"+innerKey);
Then later
if (crossRef.contains(mat)) {
// it exists somewhere.
String mapId = crossRef.get(mat);
key[] parts = mapId.split("_");
int outerKey = Integer.valueOf(parts[0]);
String innerKey = parts[1];
Map<String, Material> map = logs.get(outerKey);
Material mat = map.get(innerKey);
}
One other downside is that your speeding up lookup time at the cost of more storage.
And to re-emphasize if two different Material objects compare equally they will be considered duplicates and thus cannot be used as keys to access both types of material.
Instead of using a concatenated String as the cross-ref key you could use a simple class or record that holds those as their specific type.
This was a drawn out answer to a simple question but it may provide some alternative ideas as to how to address your problem.
I'm counting words in a file and i've loaded the words and their respective counts into a hash map. I've sorted the values and use this to retrieve my key:
public static String getKey(TObjectIntHashMap<String> hash, int value){
for(String s: hash.keySet()){
if(value == hash.get(s)){
key = s;
hash.remove(key);
return key;
}
}
I know this is a pretty ugly way to do it, but it's the only way I can seem to get to work. I'm completely aware of the existence of bimaps, but would prefer not to use one. Any ideas?
A slightly more efficient way to do this would be to use an iterator, since this avoids having to do a separate lookup of the key and value:
public static String getKey(TObjectIntHashMap<String> hash, int value){
TObjectIntIterator<String> iterator = hash.iterator();
while (iterator.hasNext()) {
iterator.advance();
if (iterator.value() == value) {
key = iterator.key();
iterator.remove();
return key;
}
}
}
The best way to do this, is using a Multiset from guava collections. This is simple code:
//create multiset
Multiset<String> multiset = HashMultiset.create();
//add some strings
multiset.add("a");
multiset.add("a");
multiset.add("b");
//sort and print
System.out.println(Multisets.copyHighestCountFirst(multiset).entrySet();
More about Multiset, you can find here:
https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multiset
I have a class called PriceList
class PriceList {
Integer priceListID;
...
}
and I have extended it in another class to accommodate some user functionality
class PriceListManager extends PriceList{
boolean user;
boolean manager;
}
One user can have an ArrayList of PriceListManager objects, that can contain duplicates (same PriceListID), so I would like to find these duplicates and compare they're fields to create one entry
eg.:
{ PriceListID = 5; user = false; manager = true;
PriceListID = 5; user = true; manager = false; }
should become
PriceListID = 5; user = true; manager = true;
What would be the best approach to that?
I already have equals methods for both classes, PriceList compares two objects by just checking their IDs while PriceListManagers does that AND checks if both boolean fields are the same.
edit: I need to find any objects with same ID, so I can merge their attributes and leave only one object.
How about something like this:
Map<Integer, PriceListManager> map = new HashMap<Integer, PriceListManager>();
for (PriceListManager manager : yourArrayList) {
if (!map.contains(manager.getPriceListID())) {
map.put(manager.getPriceListID(), manager);
}
if (manager.isUser()) {
map.get(manager.getPriceListID()).setIsUser(true);
}
if (manager.isManager()) {
map.get(manager.getPriceListID()).setIsManager(true);
}
}
List<PriceListManager> newList = new ArrayList<PriceListManager>();
newList.addAll(map.values());
// Do stuff with newList....
You can try to iterate through list and convert it into HashMap, where priceListID will be key and PriceListManager as value. While iterating over the ArrayList, check if hashmap whether value for particular priceListID exists :
1. if yes compare the same with current one
2. if not equal update as per your logic.
3. If equal no need to update and
4. if doesn't exists add it to hashmap
I hope this helps.
You have implemented equals and hashCode on PriceListManager to use all fields, but for this particular purpose you need them to match on priceListID alone, right? Maybe you want to give this construction one more thought: what is your entity here? does priceListID alone already determine a priceListManager? In any case, if you want a local solution to this, i'd use a Map and then do something like this:
Map<Integer, PriceListManager> lookup = new HashMap<Integer, PriceListManager>();
for (PriceListManager item: priceListManagers) {
PriceListManager manager = lookup.get(item.getPriceListID());
if (manager == null) {
manager = new PriceListManager();
manager.setPriceListID(item.getPriceListID());
manager.setUser(false);
manager.setManager(false);
lookup.put(manager.getPriceListID(), manager);
}
manager.setUser(manager.getUser() || item.getUser());
manager.setManager(manager.getManager() || item.getManager());
}
If you don't want duplicates. Isn't it a good idea to work with a Set instead of an ArrayList?
Uniqueness is guaranteed in a Set.
You gain performance and have to implement less code since you don't have to do the duplicate check afterwards...
Go through the original list, and if you find an object that was already there, then merge those two into one:
Collection<? extends PriceList> convertToMergedList(Iterable<? extends PriceList> listWithDuplicated) {
Map<Integer, PriceList> idToMergedObject = new LinkedHashMap<>();
for(PriceList price : listWithDuplicated) {
if (idToMergedObject.get(price.piceListId) == null) {
idToMergedObject.put(price.piceListId, price);
} else {
PriceList priceSoFar = idToMergedObject.get(price.piceListId);
PriceList priceMerged = merge(priceSoFar, price);
idToMergedObject.put(price.piceListId, priceMerged);
}
}
return idToMergedObject.values();
}
PriceList merge(PriceList price1, PriceList price2) {
// merging logic
}
I use LinkedHashMap, so that the original order of elements is preserved.
I would like to store a group of objects in a hashmap , where the key shall be a composite of two string values. is there a way to achieve this?
i can simply concatenate the two strings , but im sure there is a better way to do this.
You could have a custom object containing the two strings:
class StringKey {
private String str1;
private String str2;
}
Problem is, you need to determine the equality test and the hash code for two such objects.
Equality could be the match on both strings and the hashcode could be the hashcode of the concatenated members (this is debatable):
class StringKey {
private String str1;
private String str2;
#Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof StringKey) {
StringKey s = (StringKey)obj;
return str1.equals(s.str1) && str2.equals(s.str2);
}
return false;
}
#Override
public int hashCode() {
return (str1 + str2).hashCode();
}
}
You don't need to reinvent the wheel. Simply use the Guava's HashBasedTable<R,C,V> implementation of Table<R,C,V> interface, for your need. Here is an example
Table<String, String, Integer> table = HashBasedTable.create();
table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);
System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100
table.put("key-1", "lock-1", 150); //replaces 50 with 150
public int hashCode() {
return (str1 + str2).hashCode();
}
This seems to be a terrible way to generate the hashCode: Creating a new string instance every time the hash code is computed is terrible! (Even generating the string instance once and caching the result is poor practice.)
There are a lot of suggestions here:
How do I calculate a good hash code for a list of strings?
public int hashCode() {
final int prime = 31;
int result = 1;
for ( String s : strings ) {
result = result * prime + s.hashCode();
}
return result;
}
For a pair of strings, that becomes:
return string1.hashCode() * 31 + string2.hashCode();
That is a very basic implementation. Lots of advice through the link to suggest better tuned strategies.
Why not create a (say) Pair object, which contains the two strings as members, and then use this as the key ?
e.g.
public class Pair {
private final String str1;
private final String str2;
// this object should be immutable to reliably perform subsequent lookups
}
Don't forget about equals() and hashCode(). See this blog entry for more on HashMaps and keys, including a background on the immutability requirements. If your key isn't immutable, then you can change its components and a subsequent lookup will fail to locate it (this is why immutable objects such as String are good candidates for a key)
You're right that concatenation isn't ideal. For some circumstances it'll work, but it's often an unreliable and fragile solution (e.g. is AB/C a different key from A/BC ?).
I have a similar case. All I do is concatenate the two strings separated by a tilde ( ~ ).
So when the client calls the service function to get the object from the map, it looks like this:
MyObject getMyObject(String key1, String key2) {
String cacheKey = key1 + "~" + key2;
return map.get(cachekey);
}
It is simple, but it works.
I see that many people use nested maps. That is, to map Key1 -> Key2 -> Value (I use the computer science/ aka haskell curring notation for (Key1 x Key2) -> Value mapping which has two arguments and produces a value), you first supply the first key -- this returns you a (partial) map Key2 -> Value, which you unfold in the next step.
For instance,
Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance
add(k1, k2, value) {
table2 = table1.get(k1);
if (table2 == null) table2 = table1.add(k1, new HashMap())
table2.add(k2, value)
}
get(k1, k2) {
table2 = table1.get(k1);
return table2.get(k2)
}
I am not sure that it is better or not than the plain composite key construction. You may comment on that.
Reading about the spaguetti/cactus stack I came up with a variant which may serve for this purpose, including the possibility of mapping your keys in any order so that map.lookup("a","b") and map.lookup("b","a") returns the same element. It also works with any number of keys not just two.
I use it as a stack for experimenting with dataflow programming but here is a quick and dirty version which works as a multi key map (it should be improved: Sets instead of arrays should be used to avoid looking up duplicated ocurrences of a key)
public class MultiKeyMap <K,E> {
class Mapping {
E element;
int numKeys;
public Mapping(E element,int numKeys){
this.element = element;
this.numKeys = numKeys;
}
}
class KeySlot{
Mapping parent;
public KeySlot(Mapping mapping) {
parent = mapping;
}
}
class KeySlotList extends LinkedList<KeySlot>{}
class MultiMap extends HashMap<K,KeySlotList>{}
class MappingTrackMap extends HashMap<Mapping,Integer>{}
MultiMap map = new MultiMap();
public void put(E element, K ...keys){
Mapping mapping = new Mapping(element,keys.length);
for(int i=0;i<keys.length;i++){
KeySlot k = new KeySlot(mapping);
KeySlotList l = map.get(keys[i]);
if(l==null){
l = new KeySlotList();
map.put(keys[i], l);
}
l.add(k);
}
}
public E lookup(K ...keys){
MappingTrackMap tmp = new MappingTrackMap();
for(K key:keys){
KeySlotList l = map.get(key);
if(l==null)return null;
for(KeySlot keySlot:l){
Mapping parent = keySlot.parent;
Integer count = tmp.get(parent);
if(parent.numKeys!=keys.length)continue;
if(count == null){
count = parent.numKeys-1;
}else{
count--;
}
if(count == 0){
return parent.element;
}else{
tmp.put(parent, count);
}
}
}
return null;
}
public static void main(String[] args) {
MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
m.put("brazil", "yellow", "green");
m.put("canada", "red", "white");
m.put("USA", "red" ,"white" ,"blue");
m.put("argentina", "white","blue");
System.out.println(m.lookup("red","white")); // canada
System.out.println(m.lookup("white","red")); // canada
System.out.println(m.lookup("white","red","blue")); // USA
}
}
public static String fakeMapKey(final String... arrayKey) {
String[] keys = arrayKey;
if (keys == null || keys.length == 0)
return null;
if (keys.length == 1)
return keys[0];
String key = "";
for (int i = 0; i < keys.length; i++)
key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");
keys = Arrays.copyOf(keys, keys.length + 1);
keys[keys.length - 1] = FAKE_KEY_SEPARATOR;
return MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";
INPUT:
fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3
I’d like to mention two options that I don’t think were covered in the other answers. Whether they are good for your purpose you will have to decide yourself.
Map<String, Map<String, YourObject>>
You may use a map of maps, using string 1 as key in the outer map and string 2 as key in each inner map.
I do not think it’s a very nice solution syntax-wise, but it’s simple and I have seen it used in some places. It’s also supposed to be efficient in time and memory, while this shouldn’t be the main reason in 99 % of cases. What I don’t like about it is that we’ve lost the explicit information about the type of the key: it’s only inferred from the code that the effective key is two strings, it’s not clear to read.
Map<YourObject, YourObject>
This is for a special case. I have had this situation more than once, so it’s not more special than that. If your objects contain the two strings used as key and it makes sense to define object equality based on the two, then define equals and hashCode in accordance and use the object as both key and value.
One would have wished to use a Set rather than a Map in this case, but a Java HashSet doesn’t provide any method to retrieve an object form a set based on an equal object. So we do need the map.
One liability is that you need to create a new object in order to do lookup. This goes for the solutions in many of the other answers too.
Link
Jerónimo López: Composite key in HashMaps on the efficiency of the map of maps.
I need a simple scenario to produce a hashing collision in a HashMap. Could someone please provide one.
Is it possible to produce hashing collision if my hashmap keys are immutable?
Regards,
Raju komaturi
You could create your own type and create a bad hash function:
public class BadHash {
private String aString;
public BadHash(String s) {
aString = s;
}
public int hashCode() {
return aString.length();
}
public boolean equals(Object other) {
// boilerplate stuff
BadHash obj = (BadHash) other;
return obj.aString.equals(aString);
}
}
This will make it easy to create a collision.
An example would be:
BadHash a = new BadHash("a", value1);
BadHash b = new BadHash("b", value2);
hashMap.add(a);
hashMap.add(b);
These two entries would collide because a and b hash to the same value even though they are not equal.
Assuming you can change the key class's hash code method.
public int hashCode() {
return 1; // Or any constant value
}
This will make every single key collide.
Can't get much simpler than this:
Map<String, Object> map = new HashMap<String, Object>();
map.put("a", null);
map.put("a", null);
The simplest way is to set the initialCapacity of the HashMap to a low value and start inserting elements.
I suppose you could also design a class such that two objects can return the same hashCode value even though equals would return false.
From what I can see though, there's no way with the default HashMap to get it to tell you if there's a collision.