Check is HashMap contains another HashMap with specific value in Java - 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.

Related

Iterate over a HashMap with multiple values per key

I am currently learning sets and maps through university (still using Java 7).
They have given us a half finished to-do list app to complete. Currently the to-do list takes three String local variables to allow the user to state a job (aJob), a time to do it (aTime) and a date to do it (aDate).
The app also has an instance variable (today) that holds todays date.
I need to come up with a way to check the HashMap for any tasks that are due today. So I need to be able to query just the HashMap values attributed by the aDate local variable.
I know that to iterate Maps that I can place the keys or the values into a Set and then iterate over the set - not a problem. But if I use the values() method (within the Map class) to put these into a set - it places all three Strings per key into the set. I just want to move the aDate values into a set.
Any ideas?
I only seem to be able to find examples where the Maps have just a single Key and Single Value. This list has a single key and three values per key.
Any pointers would be good?
Kind Regards
Edit.....
Just thought I would add some code to help as there have been several different approaches - which I am all very greatful for. But not sure if they suit my needs....
The Job Class is constructed as such...
public Job(String aJob, String aDate, String aTime)
{
Job = aJob;
date = aDate;
time = aTime;
}
I then create the map within the instance declarations for the To Do List class....
Map<Integer, Job> toDoList = new HashMap<>();
So I need to know the best way to iterate over this map, but it is only the Job attribute 'aDate' that is possibly going to hold the value I am after.
Not sure if that helps at all?
Kind Regards
If really the only structure you're allowed to use is a Map where each key has 3 values (which is the case if I understand correctly), of which only one is a Date, you technically could do the following:
map.values()
.stream()
.filter(Date.class::isInstance)
...whatever else you want to do
The other suggested solutions are far better though, design wise.
If you can't use a custom class, as suggested by Toisen, maybe HashMap<String, HashMap<String, ArrayList<String>>> could do the trick for you.
I've added a sample of how to use it (as well as populating it with some random data)
public class FunkyMap {
private HashMap<String, HashMap<String, ArrayList<String>>> jobs;
// For random data
private String[] job = {"EAT", "SLEEP", "FART", "RELAX", "WORK"};
private String[] time = {"MORNING", "BEFORENOON", "NOON", "AFTERNOON", "EVENING", "MIDNIGHT"};
private String[] date = {"FIRST", "SECOND", "THIRD", "FOURTH"};
public FunkyMap() {
jobs = new HashMap<>();
// To populate some random data
Random r = new Random();
for(int i = 0; i < 20; i++) {
String d = date[r.nextInt(date.length)];
if(jobs.containsKey(d)) {
HashMap<String, ArrayList<String>> inner = jobs.get(d);
String t = time[r.nextInt(time.length)];
if(inner.containsKey(t)) {
inner.get(t).add(job[r.nextInt(job.length)]);
} else {
List<String> s = Arrays.asList(new String(job[r.nextInt(job.length)]));
inner.put(t, new ArrayList<String>(s));
}
} else {
jobs.put(d, new HashMap<String, ArrayList<String>>());
}
}
// Actual iteration over date => time => jobs
Iterator<String> i = jobs.keySet().iterator();
while(i.hasNext()) {
String iKey = i.next();
HashMap<String, ArrayList<String>> inner = jobs.get(iKey);
System.out.println("Jobs scheduled for " + iKey);
Iterator<String> j = inner.keySet().iterator();
while(j.hasNext()) {
String jKey = j.next();
ArrayList<String> actualJobs = inner.get(jKey);
System.out.println("\tAt " + jKey);
for(String s : actualJobs) {
System.out.println("\t\tDo " + s);
}
}
}
}
public static void main(String[] args) {
new FunkyMap();
}
}
I took the liberty to assume that dates were unique, and time was unique per date, while a time could hold any number of jobs including duplicates. If the last assumption with jobs is not true, you could swap ArrayList<String> with Set<String>.
Just create a class that holds all data that you need. E.g.
If you need something strange like Map<String, Tuple<String, Integer, Date>> just make a new class that holds the Tuple:
class TupleHolder {
private String firstValue;
private Integer secondValue;
private Date thirdValue;
// get/set here...
}
and use it: Map<String, TupleHolder>

Efficient way to "update" an entry in a HashMap?

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

Efficient way to represent numerous object move in Java

There are boxes and objects. A object stays in a box. Both box and object have a unique index and each object has a weight.
I need to create a method which obtains numerous orders (> 1 million) in which you can see how much weight should be moved with departure and destination box indices, and then returns moving object set and their destination.
It is very clear and easy to implement without performance thought. (Below, the type of box index is Long and object is Integer just for clarification)
public static void main(String[] args) {
Map<Long, Set<Integer>> objectsInBox = new HashMap<>();
objectsInBox.put(1l, new HashSet<>(Arrays.asList(1,2,3)));
objectsInBox.put(2l, new HashSet<>(Arrays.asList(4,5,6)));
// .... a lot of objects
Map<Integer, Double> weightsOfObject = new HashMap<>();
weightsOfObject.put(1, 99.9);
weightsOfObject.put(2, 23.4);
// ....
List<Map<Pair<Long, Long>, Double>> moveOrderList = receiveOrderList();
getFinalDestinationOfMovingObject(moveOrderList);
}
public static Map<Long, Set<Integer>> getFinalDestinationOfMovingObject(
List<Map<Pair<Long, Long>, Double>> moveOrderList){
Map<Long, Set<Integer>> finalDestinationOfObjects = new HashMap<>();
for(Map<Pair<Long, Long>, Double> moveOrder : moveOrderList){
// Convert moving amount into object move is not trivial, but given somewhere.
Map<Integer, Pair<Long,Long>> movingObjects = calculateMovingObjectSet(moveOrder);
for(Map.Entry<Integer, Pair<Long,Long>> movingObject : movingObjects.entrySet()) {
int movingObjectIndex = movingObject.getKey();
long departureIndex = movingObject.getValue().getFirst();
long destinationIndex = movingObject.getValue().getSecond();
if(!finalDestinationOfObjects.containsKey(destinationIndex)){
finalDestinationOfObjects.put(departureIndex, new HashSet<Integer>(Arrays.asList(movingObjectIndex)));
}else{
finalDestinationOfObjects.get(departureIndex).add(movingObjectIndex);
}
if(!finalDestinationOfObjects.containsKey(departureIndex)){
// We need just final destination. Remove past object state.
finalDestinationOfObjects.get(departureIndex).remove(movingObjectIndex);
}
}
}
return finalDestinationOfObjects;
}
When move order contains numerous elements, it takes a lot of time. I guess it's because insert or remove element from HasSet is not efficient. What is more efficient way?
Can't you simply record the final destination based on the object, i.e.,
finalDestination.put(movingObjectIndex, destinationIndex);
instead of all the complicated logic? This handles the case of previous destination existing as well as non-existing.
If you really need finalDestinationOfObjects, you can create it at the end by something like
Multimap<Long, Integer> finalDestinationOfObjects = HashMultimap.create();
for (val e : finalDestination.entrySet()) {
finalDestinationOfObjects.put(e.getValue(), e.getKey());
}
where Multimap comes from Guava (you don't need it, but - unlike your nested Map - it's the right thing).
This will be more efficient in case your objects moves between the boxes a lot and possibly less efficient in case they usually get moved just once.
I'd suggest to give it a try and post the code together with calculateMovingObjectSet on CR, which is better suited for such questions.

`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.

java.util.ConcurrentModificationException: concurrent access to hashmap

In an implementation, i have written the following code. The try-catch block is in a method
try{
InputVals iv = task.getInputVals();
Map<String, String> map = iv.getAllValues();
String a = map.get("value1");
String b = map.get("value2");
String x = funcxy.methodGetX();
String y = funcxy.methodGetY();
iv.setValue(xval, x);
iv.setValue(yval,y);
String []names = {"name1", "name2", "name3"}
for(int i = 0; i<names.length; i++ ){
iv.setValue("name"+i, names[i] );
}
}
When I make concurrent request few times (not always) it throws "java.util.ConcurrentModificationException: concurrent access to hashmap" error. I tried using
Map<String, String> map= new ConcurrentHashMap<String, String>();
map = iv.getAllValues();
But this didnt solve the issue. Can you help me out and let me know where I'm making the mistake. I cannot change the implementation of
InputVals iv = task.getInputVals();
I'm not entirely sure what you are trying to achieve, but as others have already pointed out, you likely have multiple threads trying to manipulate the map returned by iv.getAllValues() at the same time, hence throwing the exception.
Copying the map with a ConcurrentHashMap will work as you would be working off a local copy. However keep in mind that in doing so, you would be using it locally only, and consequently not need the concurrency checking that it provides. The problem with your code is that you do not actually copy the data to your new map. You would have needed to do:
Map<String, String> map= new ConcurrentHashMap<String, String>( iv.getAllValues() );
Depending on your needs when making the modifications to the map entries, the simplest & fastest would probably copy the map and work off a local copy. This will prevent any concurrency issues. Of course, if the other threads need access to your updated information, this plan does not work.
try{
InputVals iv = task.getInputVals();
Map<String, String> map = new HashMap<String, String>();
// copy all map values to a local var
map.putAll( iv.getAllValues() );
String a = map.get("value1");
String b = map.get("value2");
String x = funcxy.methodGetX();
String y = funcxy.methodGetY();
iv.setValue(xval, x);
iv.setValue(yval,y);
String []names = {"name1", "name2", "name3"}
for(int i = 0; i<names.length; i++ ){
iv.setValue("name"+i, names[i] );
}
}
Short of this, you would need to ensure that any calls to the map are made in synchronized blocks. However, this can be extremely difficult and tedious if you have several different places in the code where you access this map.
You have to synchronize all access to the Map returned by the getInputVals(). It is being altered somewhere in a separate thread. You will need something along the lines of the code below, but what is also very important is that the lockObj must also be applied everywhere else that this map is being used, including wherever it is currently being modified (this code has not been shown).
try{
InputVals iv = task.getInputVals();
String a;
String b;
synchronized (lockOjb) {
Map<String, String> map = iv.getAllValues();
a = map.get("value1");
b = map.get("value2");
}
String x = funcxy.methodGetX();
String y = funcxy.methodGetY();
iv.setValue(xval, x);
iv.setValue(yval,y);
String []names = {"name1", "name2", "name3"}
for(int i = 0; i<names.length; i++ ){
iv.setValue("name"+i, names[i] );
}
}
This may or may not be possible, depending on whether you have the ability to change this code. If it is already happening within a library, for instance, you may simply have to redesign your code so that it is not multithreaded, at least anywhere where this map is accessed.
What does your InputVals.setValue() does? How is it defined? there is no use if you define ConcurrentHashMap in the business logic as you are using the reference of original variable. You would need to take care in the actual bean itself.
If it some thing like below
Class Inputvals {
Map<String, String> map = new HashMap <String,String>();
public void setValue(String a,String b){
map.put(a,b);
}
public Map<String,String> getAllValues(){
return map;
}
}
I suggest you to change as below
Class Inputvals {
Map<String, String> map = new ConcurrentHashMap<String,String>();
public void setValue(String a,String b){
map.put(a,b);
}
..
...
this should help you to resolve multi threaded access issue.
You may need to wrap your map in a Synchronized collection
Map<String, String> map = Collections.synchronizedMap(iv.getAllValues());
Then go ahead and access your map.

Categories