creating two dimensional hashset functionality - java

I am trying to make two dimensional hashset functionality. I have a loop that iterates through pairs of integers:
ResultSet getAssociations;
Statement getAssociationsSelect = sqlConn
.createStatement();
getAssociations = getAssociationsSelect
.executeQuery("SELECT ProductId, ThemeId FROM ProductTheme");
while(getAssociations.next()) {
int productId1 = getAssociations.getInt(1);
int themeId1 = getAssociations.getInt(2);
}
When the current pair of integers does not match a previous pair of integers I want to store them. I figured a hashset would be the best approach because I can insert the pairs and it wont take repeats. How do I do this?

I think you might be over-thinking the problem a bit. I would suggest the following:
Map<Integer, Set<Integer>> map = new HashMap<Integer, Set<Integer>>();
if(!map.containsKey(productId))
map.put(productId, new HashSet<Integer>());
map.get(productId).add(themeId);
This way you have a Set of all the themeIds that map to a given productId, guaranteeing uniqueness while creating an object with an easily iterable format.

Create a new object which can act as a composite key based on productId1 and themeId1. Make sure and implement the equals and hashCode methods and store these objects in the Set.
public class AssociationReference() {
private int productId;
private int themeId;
//constructor/getters/setters
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + productId;
result = prime * result + themeId;
return result;
}
public boolean equals(Object obj) {
}
}
Now you can use HashSet and only unique values will be stored. You can also use this Object as the Key on a HashMap if you need to store more.
I would avoid creating a key by conjoining values unless you are sure that the ranges will never change, and that you ca careful to pad your numbers so there can be no collision (i.e 9 and 11 should be 00090011 rather than 911 so as to be distinguishable from 91 and 1).

If you want a really simple solution concatenate both keys and store the resulting string, e.g.
String newKey = String.format( "%d-%d.", productId1 , themeId1 );
It will allways generate a unique key for each combination.

I would use a HashMap<Long, Association>
As Keys i would use ProductId * 1000000 + ThemeId so they look like this:
32000064 for ProductId = 32 and ThemeId = 64
be sure to implement equals and hashcodein your Association class

Related

Check is HashMap contains another HashMap with specific value in Java

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.

`ArrayList of HashMap` or `LinkedHashMap` to get item by index

My need to store a a huge amount of data in the key-value form.
Also, I have two requirements
query data via the index, like from an array.
hence the order in the data structure must be preserved.
For Requirement 2 - I can use a LinkedHashMap.
For Requirement 1 - I have two options :
1.1 | To implement an ArrayList Of HashMap. [ArrayList<HashMap<String,String>>]
1.2 | To implement a LinkedHashMap and query the items by index using something like
-> new ArrayList(hashMapObject.entrySet()).get(0);
The Question is which is better among 1.1 or 1.2 ?
By better, I mean - efficient in terms of memory and space.
Let's assume the volume of data is in the order of 50 to 100 key-value pairs with average sized Strings - say every key is 10-30 characters and value is 30-50 characters.
Try using SortedMap.
For example:
SortedMap<Key, Value> map = new TreeMap<Key, Value>();
This way you get the fast lookup time (via key), but they also remain ordered.
You can then iterate over the data like so:
for(Key k : map.keySet()) {
process(map.get(k));
}
I used them recently to analyze 10s millions tweets where the key was a date, and the value was a counter. I wanted to maintain the ordering of the dates.
update If you can get by with just itereating over the data, then my method will suffice. Perhaps you could supply a small example? If it's absolutely required that you can reference the data by index as well, it seems like you would just want to maintain two datastructures like #Jim mentioned. I'ved had to do that before.
Remember that collections do not contain the objects, only references to objects.
Use two collections:
An ArrayList to store the references for access by index
A HashMap to store the references for access by key
For example:
List<MyValue> list = new ArrayList<MyValue>(100000);
Map<MyKey,MyValue> map = new HashMap<MyKey,MyValue>(100000);
while(moreItems) {
// read input
MyKey key = ...
MyValue value = ...
list.add(value);
map.put(key,value);
}
// lookup by index
MyValue v1 = list.get(11241);
// lookup by key
MyValue v2 = map.get(someKey);
If you need to cross-reference (i.e. given a value object, find its index or its key) you have some options:
Save the index and key in the the value object itself
Wrap the value in a "handle" that contains the key and index.
For example
class Wrapper {
MyKey key;
MyValue value;
int index;
// constructor, getters and setters
}
int index=0;
while(moreItems) {
// read input
MyKey key = ...
MyValue value = ...
Wrapper w = new Wrapper(key,value,index++);
list.add(w);
map.put(key,w);
}
...
Wrapper w = list.get(23410);
MyKey k = w.getKey();
MyValue v = w.getValue();
int i = w.getIndex();
...
I think the LinkedHashMap is the best solution, but to get the item, you can use
hashMapObject.values().toArray()[index]
However, the toArray method will be slow for large amounts of data. But that is something you'll have to test.
If speed is really an issue, you can maintain a HashMap and an ArrayList.
I went with experimentating it myself. Turns out the method of creating an ArrayList of HashMaps is about 40 times faster with 1000 elements.
public class HashMapVsArrayOfHashMap {
public static void main(String[] args){
ArrayList<HashMap<String, String>> listOfMaps=new ArrayList<HashMap<String,String>>();
for( int i=0;i<1000;i++){
final int finalI=i;
listOfMaps.add(new HashMap<String, String>(){{put("asdfasdfasdfasdfadsf"+finalI,"asdfsdafasdfsadfasdf"+finalI);}});
}
LinkedHashMap<String, String> map=new LinkedHashMap<String, String>();
for(int i=0;i<1000;i++)
map.put("asdfasdfasdfasdfadsf"+i,"asdfsdafasdfsadfasdf"+i);
int position=700;
testArrayList("Method1:ArrayListOfHashMaps",position,listOfMaps);
testHashMap("Method2:LinkedHashMap",position,map);
}
private static void testArrayList(String string, int position,
ArrayList<HashMap<String, String>> listOfMaps) {
long start, end;
start=System.nanoTime();
listOfMaps.get(position).get("asdfasdfasdfasdfadsf"+position);
end=System.nanoTime();
System.out.println(string+"|Difference = "+(end-start));
}
private static void testHashMap(String string, int position,
LinkedHashMap<String, String> map) {
long start, end;
start=System.nanoTime();
String s= new ArrayList<String>(map.keySet()).get(position);
end=System.nanoTime();
System.out.println(string+"|Difference = "+(end-start));
}
}
When you increase the size to 30,000 elements - the difference is HUGE.

ensuring all the ids value in the hashMap are different?

The method below (generateID())it generate a random ids, And when i store students in the HashMap i want to check if the generated id is not exist in the hashMap value and if its exists I want to generate a new id and then store it, The problem with the method store sometimes it does not store all the student because some student might have the same id and this is not allowed, So what is the best why to check that all ids are uniqe and if there is duplication the method generateid will be called again util all the ids are uniqe and then it will store it, I want to ensure that the ids values produced by generateId() are all different
private String generateId(String perfix, int numberaOfDigits)
{
for(int i=0;i<numberaOfDigits;i++)
{
perfix += randomGenerator.nextInt(9)+ 1;
}
return perfix;
}
public void store(Student student)
{
int index = 0;
studentMap.setId(generateId("AB-",1));
while(index <= studentMap.size())
{
for(Student stu : studentMap.values() )
{
if(student.getStduentID().equals(stu.getStduentID()))
{
student.setId(generateId("AB-",1));
}
}
index++;
}
}
studentMap.put(student.getStduentID(),student);
}
you can use the containsKey() method to check if an ID is already in use as key
Use an UUID. Or a sequence as already answered
If that's actually a Map implementation, you should be able to use .containsKey(). The problem is that, depending on how well written your ID generator is, this can cause significant performance issues over time. Say you have a 6 digit ID, and in a few years 80000 students have passed through the system. How many guesses will it need before it finds one of the remaining 20000 available keys?
You can get from the map with the generated Id, if it returns null, then it doesn't exist
yet.
Map<Long, Object> myMap = new HashMap<Long, Object>();
Long id = generateRandomId()
Object value = getMyObjectValue();
while(myMap.get(id) != null){
id = generateRandomId();
}
myMap.put(id, value);
I would eliminate the generateId() method altogether, and simply do this:
private int id;
then:
student.setId(id++);
I suggest to try following implementation to get unique random number each time.
Add items to the list .
Use Collections.shuffle(list); to shuffle the list .
Iterate over list and get random number each time from the given range. (For below case range is from 0 to numberOfStudent-1).
int numberOfStudent = 10;
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < numberOfStudent; i++)
list.add(i);
Collections.shuffle(list);

Sorting HashMap using absolute values of doubles

I am attempting to sort a hashmap on type <Integer,Double> using a TreeMap and a SortedMap I want to sort on the absolute values of the Doubles but I also want to retain the sign value (hence not storing as an unsigned Double).
Below is the code I am using, however I am not getting the values I expect, presumably due to the use of hashcode() can anybody point out how to fix this?
Map<Integer,Double> termWeights = new HashMap<Integer,Double>();
SortedMap sortedData = new TreeMap(new ValueComparer(termWeights));
System.out.println(termWeights);
sortedData.putAll(termWeights);
System.out.println(sortedData);
class ValueComparer implements Comparator {
private Map _data = null;
public ValueComparer(Map data) {
super();
_data = data;
}
public int compare(Object o1, Object o2) {
Double e1 = Math.abs((Double) _data.get(o1));
Double e2 = Math.abs((Double) _data.get(o2));
int compare = e2.compareTo(e1);
if (compare == 0) {
Integer a = o1.hashCode();
Integer b = o2.hashCode();
return b.compareTo(a);
}
return compare;
}
}
Thanks
Can you give an example of expected and actual results?
Sorted map: {17=1.644955871228835, 0=-1.029545248153297, 10=-5.291765636407169E-4, 9=-3.331976978545177E-4, 1=-2.7105555587851366E-4, 2=-2.7105555587851366E-4, 7=-2.0897436261984377E-4, 8=-1.305197184270594E-5, 3=0.0, 4=0.0, 5=0.0, 6=0.0, 11=0.0, 12=0.0, 13=0.0, 14=0.0, 15=0.0, 16=0.0, 18=0.0, 19=0.0, 20=0.0, 21=0.0, 22=0.0}
So what is the problem?
That looks correctly sorted from biggest to smallest.
But I would avoid using hashCode in the tie-break secondary comparator, because you need it to never return the same value for different inputs. In this case, it works, because you are calling it on an Integer, where hashCode just returns the same int. But if you used Long or String keys in your map, it would have collisions. Compare the two keys directly instead.
And finally, you must not change the weights after starting to use the comparator. That will lead to an inconsistent TreeMap.

Java: Composite key in hashmaps

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.

Categories