This question already has answers here:
What's the quickest way to remove an element from a Map by value in Java?
(12 answers)
Closed 4 years ago.
I have an assignment that requires me to has a Map that is configured...
Map<Integer,Event> eventList = new HashMap<>();
And I have to write a method that has the following header...
public String removeEvent(Event eventObj)
The idea is to pass it an Event object, check if the event already exist as a value in the Map, and if it does, remove it and return a String message as confirmation that it has been removed.
The issue I have is that it stipulates that I cannot iterate over the map for the solution.
I can use the containsValue() method, or my overidden equals() method, to check if the object already exists in the map, but I now have the issue where I am not sure how I can remove the key pair value that matches?
Any assistance would be good as I am quite new to Maps and often struggle moving between Key and Value.
If you cannot iterate1 the Map, you need another Map to represent the reverse mapping; e.g.
Map<Integer,Event> forwardMap = new HashMap<>();
Map<Event,Integer> reverseMap = new HashMap<>();
....
void remove (Event event) {
Integer key = reverseMap.get(event);
if (key != null) {
forwardMap.remove(key);
reverseMap.remove(event);
}
}
Obviously, all operations that modify the forward map must make the corresponding modification to the reverse map.
1 - I'm assuming that all forms of iteration are disallowed. That includes using Java 8+ streams, where the iteration is happening under the hood. If that is not what you mean, then update your Question to make it clear what is allowed and what it not allowed.
The simplest solution would be:
eventList.values().remove(eventObj);
However this uses iteration under the hood. You cannot solve this without iteration.
The solution is to write two-phase code:
Collect the keys that belong to that value
Remove those keys - given that they have still the same value
One might consider some sort of locking in case of multi-threaded access. For the sake of simplicity we put the burden of this to the caller of the method:
public String removeEvent(Event eventObj) {
// phase 1: collect the related keys
List<Integer> keys = eventList.entrySet().stream()
.filter(entry -> eventObj.equals(entry.getValue()))
.map(entry -> entry.getKey())
.collect(Collectors.toList());
// phase 2: remove those keys
for (Integer key : keys) {
// this version of reomve double-checks if that key still has that value
eventList.remove(key, eventObj);
}
// it would be good to know the criteria of failure - when should we return something else?
return "Success";
}
As you cannot iterate, you should call a function that will do that for you internally. Map.replaceAll() should work. This method will go through each entry and replace its value with what is returned by the BiFunction parameter. The first parameter of the function is the key, second is the value. It should return the new value.
Example:
map.replaceAll((key, value) -> {
if ("foo".equals(value)) return null;
return value;
}
Related
This question already has answers here:
Iterating over Key Set vs Iterating over Entry Set
(2 answers)
FindBugs warning: Inefficient use of keySet iterator instead of entrySet iterator
(5 answers)
Closed 28 days ago.
I'm scanning my code through SonarQube and it shows this code smell "Iterate over the "entrySet" instead of "keySet"". I tried, but I can't figure it out.
Sample code:
public Set<Date> getAccountShiftDate(Map<String, Set<String>> shiftDatesMap, List<Groups> shiftSchedule) {
// set that has the account shift dates
Set<Date> accountShiftDatesTemplate = new Hashset<>();
// iterate on accounts
for (String accounts : shiftDatesMap.keySet()) {
//get group of an account
Optional <Groups> shiftOptional = shiftList
.stream()
.filter(g -> StringUtils.equalsIgnoreCase(accounts,g.getLongName()))
.findFirst();
// ...
Can someone give me a reference to understand this.
If you iterate over shiftDatesMap.keySet() and later on call in your loop shiftDatesMap.get(accounts) you execute an unnecessary get operation for each entry.
Try to read an understand the description of the metioned code smell from SonarQube.
Instead you should use shiftDatesMap.entrySet() which gives you an Entry, i.e. as pair of the key and the value. So if you later on in your loop want to access the value for your given accounts key, you must only access the value from your entry, which is cheaper than calling a get operation on the map for each value.
Before:
for (String accounts : shiftDatesMap.keySet()) {
Set<String> shiftDates = shiftDatesMap.get(accounts); // unnecessary operation
}
After:
for (Map.Entry<String, Set<String>> entry: shiftDatesMap.entrySet()) {
String accounts = entry.getKey();
Set<String> shiftDates = entry.getValue(); // access value for free without 'get' on map
}
In the code below , I have added some keys with their values in map. And then I am trying to add value to key "tie" in map. If it is not present in map, then I am setting its value as 1 otherwise and I am adding 1 to its already existing value.
HashMap<String,Integer> map = new HashMap<>();
map.put("shoes",100);
map.put("shirt", 40);
map.put("pants", 200);
int value;
//First approach
value = map.compute("tie",(k,v)->{
//k is of type String and v is of type Integer
v = v == null ? 0 : v;
return v+1;
});
//Second approach
//map.put("tie",map.getOrDefault("tie",0)+1));
There are two ways to achieve that
By using compute() method of map
By using getOrDefault() insiste put() method
Can someone please help me which method have better performance and which one is preferred.
The merge method is simpler to use.
If you are using a concurrent implementation of Map, it may also be safer, because it can implement proper synchronization.
Map<String,Integer> = ... ;
map.merge("key", 1, Integer::sum);
Another approach uses counter and computeIfAbsent(k,v) as follows:
import java.util.concurrent.atomic.*;
HashMap<String,AtomicInteger> map = new HashMap<>();
for (String item : List.of("shoes", "shoes", "shirt", "pants", "tie")) {
map.computeIfAbsent(item, s -> new AtomicInteger()).incrementAndGet();
}
System.out.println("map="+map);
// Prints something like: map={tie=1, shirt=1, pants=1, shoes=2}
Note that HashMap can't store int primitives anyway and it will have to wrap them in an Integer via autoboxing. This is never going to be very efficient if you are updating the value of the integer a lot.
Instead, you might consider using a MutableInt from Apache commons lang instead of Integer. Then you can just get the value and increment it directly. You would need to be careful when handing out references to the value you get as anyone will be able to update the value in the map.
You could also consider using a collections library that implements object-to-int maps directly. For example, fastutils (but there are others): https://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/objects/Object2IntOpenHashMap.html.
Say I have Map<List<String>, List<String>> whatComesNext,
And while in a for loop, for every iteration I want to add the nth element of List<String> text to the value of whatComesNext. Why can I not perform whatComesNext.put(key, whatComesNext.get(key).add(text.get(n)))? The idea would be to retrieve the value from its respective key in the hashmap and add my desired String to it. This is assuming that every key in the hashmap has a value.
Below is my full code:
static void learnFromText(Map<List<String>, List<String>> whatComesNext, List<String> text) {
for (int i=0; i<=text.size()-3; i++) {
if (whatComesNext.containsKey(Arrays.asList(text.get(i),text.get(i+1)))==false) {
whatComesNext.put(Arrays.asList(text.get(i),text.get(i+1)), Arrays.asList(""));
}
whatComesNext.put(Arrays.asList(text.get(i),text.get(i+1)), whatComesNext.get(Arrays.asList(text.get(i),text.get(i+1))).add(text.get(i+2)));
}
}
The Arrays.asList() looks complicated, but it's because I was getting null maps when trying to intialize my own String Lists to try and hold my keys and values, which someone told me was because I was repeatedly clearing the lists that the keys & values were assigned to, leaving them null. I thought I'd solve that problem by referring directly to the original List<String> text, because that remains unchanged. The idea is to first check if a key is not present in the map, and if so assign it an empty List as a value, and then add a String from text to the value of the map.
The error I get when running the code is Error: incompatible types: boolean cannot be converted to java.util.List<java.lang.String> in the line whatComesNext.get(Arrays.asList(text.get(i),text.get(i+1))).add(text.get(i+2)));. I don't understand where this could go wrong, because I don't see which method is returning a boolean.
The error comes from the fact that List.add(Object o) returns a boolean and not the List itself. The Map is declared to contain instances of List<String> as value. If you simply want to add a value to a list, just retrieve it from the map and call add on it. Check the result of the get-process for null and create a new list and put it into the Map if that's the case
I can see a couple of other problems as well:
You call Arrays.asList(...) multiple times creating multiple lists with the same elements. This is a major performance issue and you're just lucky, that the returned list is actually implementing equals, so that your logic is actually working (I expected that to be the problem of your "doesn't work"-description before you updated it.
If the key doesn't exist, you're creating a List containing an empty text. If that should be an empty list, that's not what you're doing and you might run into problems later on, when you work with text-values (that is the empty text as first element) that weren't part of the original input values.
Without changing the type of the key of the Map a - in my eyes - better implementation would look like this:
static void learnFromText(Map<List<String> whatComesNext, List<String>, List<String> text) {
for (int i=0; i<= text.size() - 3; i++) {
List<String> listKey = text.subList(i, i+2);
List<String> value = whatComesNext.get(listKey);
if (value == null) {
value = new ArrayList<>();
whatComesNext.put(listKey, value);
}
value.add(text.get(i+2)));
}
}
The calculation of the list for the keys happens only once, increasing performance and reducing the need of resources. And I think it's more readable that way as well.
The .add() method returns a boolean, your parenthesis are misplaced, replace your last line with this one:
whatComesNext.put(Arrays.asList(text.get(i),text.get(i+1)), whatComesNext.get(Arrays.asList(text.get(i),text.get(i+1)))).add(text.get(i+2));
This question already has answers here:
HashMap with multiple values under the same key
(21 answers)
Closed 6 years ago.
I have 100 entries and I have to have to hash these into a hashtable of a limited size.
I know how to work with the first entry, ht.put(k,v) does the trick.
But as soon as I want to add another value to it, the old one gets overwritten. I don't want to do that, I want to append it in a linkedlist or arraylist.
Hashtable<Integer,Integer> ht = new Hashtable<Integer,Integer>(211);
ht.put(1, 40);
ht.put (1, 60);
System.out.println(ht.get(1));
// output is 60
How to make it both 40 and 60?
You can have List as value type like:
Hashtable<Integer,List<Integer>> ht = new Hashtable<Integer,List<Integer>>(211);
And your put operation would look like:
public static void put(Hashtable<Integer,List<Integer>> ht, int key, int value) {
List<Integer> list = ht.get(key);
if (list == null) {
list = new ArrayList<Integer>();
ht.put(key, list);
}
list.add(value);
}
[UPDATE1]
If you want you can make your one extension of Hashtable like:
public class MyHashtable extends Hashtable<Integer,List<Integer>> {
public MyHashtable(...) { // add params if needed
super(...);
}
// with additional method:
public static void putOne(int key, int value) {
List<Integer> list = this.get(key);
if (list == null) {
list = new ArrayList<Integer>();
this.put(key, list);
}
list.add(value);
}
}
You need linear probing http://www.sanfoundry.com/java-program-implement-hash-tables-linear-probing/
It's not possible to store more than one value in a cell of a hash table
When trying to map a new key to an already occupied cell this is called a collision.
There are a few algorithm schemes to try and work around collision, one is Linear probing - which finds the next most appropriate free space for the key to be stored
The data structure you are looking for is called Multi Map. By definition it has different interface than a map, because it allows multiple values associated with the same key.
There's no standard library implementation for this data structure yet. But you can find good ones in some open source libraries:
Guava
Apache Commons Collections
Multimap (https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Multimap.html) should help if you are allowed to use it.
Alternatively, you could use Map<Integer, List<Integer>>.
You are using same key (1), which is not what you wanted, unless you wanted to add more values to the same key, in that case have hashtable of list of arrays HashMap<Integer,List<Integer>> integerArrayMap.
In Hashtable, the Key MUST be unique, as you are NOT using unique keys, the same value is being replaced. so try to put the values with different keys.
ht.put(1, 40);
ht.put (2, 60);
I suggest you to refer the Hashtable api here:
https://docs.oracle.com/javase/7/docs/api/java/util/Hashtable.html
public void hashcollision(){
Map m=new HashMap();
m.put(1,2);
m.put(1,3);
system.out.println(m.get(1));
}
o/p will be 3 , How to get access to 2.
You need to start by working out what exactly you're wanting to achieve, and whether a HashMap is the right tool for the job.
A HashMap is a type of Map, and the point of a Map is to allow you to assign a value to a key. Think of it as a row of boxes. Each box has a label on it (that's the key) and something inside it (that's the value).
When you put something into the Map, you specify a key and a value:
map.put(1,2);
This means: find the box labelled 1, and throw away whatever's inside it, and replace its contents with 2. If there's no box labelled 1, then label a new box 1, and put 2 inside it.
The point is that because this throws away whatever was already in that box, your code does this:
map.put(1,2); //first time, so label a new box with 1, and put 2 inside it
map.put(1,3); //already a box labelled 1, so throw its contents away and put 3 inside
The 2 is no longer stored at all. It can't be retrieved because it's not there.
If you're trying to get out a value you've overwritten, what that suggests to me is that you didn't really want a HashMap in the first place, and you'd have been better off with something that allows you to store multiple values for a given key. There are things that do that, but they're not so common as a generic Map. It might be that you just need to rethink your code and what you're trying to do.
But forget about hash collisions: they're something that's internal to HashMap, and they get sorted out for you by the HashMap so that you don't really have to worry about them. They're nothing to do with what's going on here.
The previous value associated with the key is returned by the put call. See: The Documentation for HashMap
Thus, if you want the old one:
public void hashCollision() {
Map m = new HashMap();
m.put(1,2);
int oldVal = (int)m.put(1,3);
}
Note that you should check that for null which I'm not doing here to save space and time. If the key was not previously in use, you'll get null back (which won't cast to an int like this). Also, use the generics. That's why they're there.
You can't get access to to '2' because in Java, the first value associated with a given key is over-written if you insert a second value associated with the same key. This technically isn't a hash collision. A hash collision occurs when DIFFERENT keys hash to the same bucket.
You could use ConcurrentHashMap and use putIfAbsent which will put into map if and only if its not present so in your case output will be 2. See here http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentHashMap.html#putIfAbsent%28K,%20V%29
Or try to save value when doing put to get previous value i.e.
int previousValue = map.put(1, 3);
System.out.println(previousValue);
See here for details http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put%28K,%20V%29
Use a multimap:
final Map<Integer, List<Integer>> myMap = new HashMap<>();
public void addToMap(final Integer key, final Integer value) {
if (myMap.get(key) == null) {
myMap.put(key, new ArrayList<Integer>());
}
myMap.get(key).add(value);
}
public List<Integer> getFromMap(final Integer key) {
return myMap.get(key);
}
public Integer getFirstElementFromMap(final Integer key) {
return myMap.get(key).get(0);
}
If you don't want to make your own implementation of a multi-map, you can use the one from commons-collections or google guava (better):
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
//...
final Multimap<Integer, Integer> myMap = LinkedListMultimap.create();
myMap.put(1, 2);
myMap.put(1, 3);
System.out.println(myMap.get(1));
//Prints [2, 3]