What is better to iterate over an EnumMap in java? - java

Suppose I have declared an enum and corresponding emummap as:
enum MyEnum {
CONSTANT1, CONSTANT2, CONSTANT3;
}
EnumMap<MyEnum, String> MyEnumMap = new EnumMap<MyEnum, String>(MyEnum.class);
I want to iterate over MyEnumMap, for example, just to print each Entry one by one.
What is the best approach(fastest) to iterate over keys in the following cases:
When it is ensured that each constant in MyEnum is a key in MyEnumMap
When each constant in MyEnum may or may not be a key in MyEnumMap
I want to choose between foreach loop using MyEnumMap.keySet() or MyEnum.values(). Any other approach is most welcomed.

It does not matter. Internally, EnumMap is implemented with a pair of arrays of the same length as the number of enum entries. One array has enum elements, while the second array has objects mapped to them, or NULL placeholders. Any iteration over EnumMap is therefore equivalent to a for loop on an integer index that traverses the entire range of enum ordinals, so you should pick the approach that makes your code most readable to you.

If you take a look at code of EnumMap#keySet()
381 public Set<K> keySet() {382 Set<K> ks = keySet;383 if (ks != null)384 return ks;385 else386 return keySet = new KeySet();387 }
you will notice that it returns keySet used internally by EnumMap to store keys.
Now each time we call MyEnum.values() we are getting different array filled with all enum elements. This means that first empty array is created which later needs to be filled with all enums which requires some iteration.
So in first approach you are skipping iterating over enums already stored by map, while insecond approach we simply creating some temporary array which involves additional iteration over all MyEnum elements.

Perhaps, you just want another way of writing the code....
Since keys are always unique
for(MyEnum myEnum: MyEnum.values()){
String value = map.get(myEnum);
If(value != null){
//use the value here
}
}
Just another way to write it.
Or you could also try
for (Map.Entry<MyEnum, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}

It depend on your application logic, but here are some hints:
for 1)
// it is a bit faster to iterate over plain array, than over Set
// And you can also get here information about entries that are in enum, but not in hashMap, so you can have logic for those cases.
for (MyEnum e: MyEnum.values()) {
// you can get here information what is contained and not contained in your map
}
for 2) But it is still better to use 1) because you can have there information of enum values not contained in Map.
for (MyEnum e: MyEnumMap.keySet()) {
// you can check here all that is in your map, but you cant tell what is in enum but not in your map
}

Related

How can I iterate through a hashset within a hashmap?

I am trying to iterate through a hashmap which contains 8 entries. However one of these entries is a hashset 'balloon' with 2 objects within it. I want to add this to an array list so i can then iterate through it in a for loop/
First part of the code below works, I loop through the hashmap and look for the key I require which is 'balloon'. I need help to add the hashset to an array list.
I am getting a casting error when using Collectors.list and stream
//This is the hashmap I am looping through to find the balloon key
Map<String, Object> types = System.getPartyItems();
for (Map.Entry<String, Object> entry : types.entrySet()) {
if (StringUtils.contains(entry.getKey().toString(), "balloon")) {
//This is where I need to add the balloon hashset to a list to access the entries and values from within.
List<PartyItem> myPartyList = new ArrayList<>();
myPartyList.add (hash set of balloon objects)
Do i need to assign the hash set to a variable before i can set it to the list? Anything I've tried I am getting a casting error eg "class java.util.stream.ReferencePipeline$Head cannot be cast to class java.util.ArrayList"
Any help appreciated.
Thanks
Test if a value is a Set and if it is, add all items to your list.
if (StringUtils.contains(entry.getKey().toString(), "balloon")
&& entry.getValue() instanceof Set) {
myPartyList.addAll((Set)entry.getValue());
}
You can iterate like this:
for(String key: Map.keySet()){
if(StringUtils.contains(key, "balloon")){
Iterator<String> it = hashMap.get("balloon").iterator();
while(it.hasNext()){
// your code here
}
}
}
Instead of iterating through entry just iterate through keys and when you find the balloon get the hashset to iterate through it.
for(String key: types.keySet()){
if(StringUtils.contains(key, "balloon")){
for(Object object: types.get(key)){
//do what you need with object
}
}
}
After your edit it should be like this
for(String key: types.keySet()){
if(StringUtils.contains(key, "balloon")){
ArrayList<Set<PartyItem>> myList = new ArrayList();
myList.add(types.get(key));
}
}
Usually you structure your hashmap as <key, value> and you access your values via their corresponding keys. But they have to match exactly.
In your case your hashmap would look like this:
Map<String, Object> partyItems = myPartyList.getPartyItems();
// or maybe even
Map<String, PartyItem> partyItems = myPartyList.getPartyItems();
And getting the value is as easy as:
Object partyItem = partyItems.get("baloon");
If you are not sure if your paryItems contain a value for your key baloon you can check that first:
if (partyItems.contains("baloon")) {
Object partyItem = partyItems.get("baloon");
}
If you are looking for a part of the key matching baloon:
List<PartyItem> myFilteredPartyItems = partyItems.entrySet().stream()
.filter(e -> e.getKey().contains("baloon"))
.collect(Collectors.toList()))
This is called stream oriented programming (take a look at the Java Stream API), and if your run at least Java 8 you can use those.
And what it does, is turn the entries of the List to a stream, then remove everything which does not contain baloon in the key, and turn the resulting stream, which was not removed back to a list.
Here you also find a very informative tutorial on how to use streams in Java.

Iterate over key-range of HashMap

Is it possible to iterate over a certain range of keys from a HashMap?
My HashMap contains key-value pairs where the key denotes a certainr row-column in Excel (e.g. "BM" or "AT") and the value is the value in this cell.
For example, my table import is:
startH = {
BQ=2019-11-04,
BU=2019-12-02,
BZ=2020-01-06,
CD=2020-02-03,
CH=2020-03-02,
CM=2020-04-06
}
endH = {
BT=2019-11-25,
BY=2019-12-30,
CC=2020-01-27,
CG=2020-02-24,
CL=2020-03-30,
CP=2020-04-27
}
I need to iterate over those two hashmap using a key-range in order to extract the data in the correct order. For example from "BQ" to "BT".
Explanation
Is it possible to iterate over hashmap but using its index?
No.
A HashMap has no indices. Depending on the underlying implementation it would also be impossible. Java HashMaps are not necessarily represented by a hashing-table. It can switch over to a red-black tree and they do not provide direct access at all. So no, not possible.
There is another fundamental flaw in this approach. HashMap does not maintain any order. Iterating it yields random orders that can change each time you start the program. But for this approach you would need insertion order. Fortunately LinkedHashMap does this. It still does not provide index-based access though.
Solutions
Generation
But, you actually do not even want index based access. You want to retrieve a certain key-range, for example from "BA" to "BM". A good approach that works with HashMap would be to generate your key-range and simply using Map#get to retrieve the data:
char row = 'B';
char columnStart = 'A';
char columnEnd = 'M';
for (char column = columnStart; columnStart <= columnEnd; column++) {
String key = Chararcter.toString(row) + column;
String data = map.get(key);
...
}
You might need to fine-tune it a bit if you need proper edge case handling, like wrapping around the alphabet (use 'A' + (column % alphabetSize)) and maybe it needs some char to int casting and vice versa for the additions, did not test it.
NavigableMap
There is actually a variant of map that offers pretty much what you want out of the box. But at higher cost of performance, compared to a simple HashMap. The interface is called NavigableMap. The class TreeMap is a good implementation. The problem is that it requires an explicit order. The good thing though is that you actually want Strings natural order, which is lexicographical.
So you can simply use it with your existing data and then use the method NavigableMap#subMap:
NavigableMap<String, String> map = new TreeMap<>(...);
String startKey = "BA";
String endKey = "BM";
Map<String, String> subMap = map.subMap(startKey, endKey);
for (Entry<String, String> entry : subMap.entrySet()) {
...
}
If you have to do those kind of requests more than once, this will definitely pay off and it is the perfect data-structure for this use-case.
Linked iteration
As explained before, it is also possible (although not as efficient) to instead have a LinkedHashMap (to maintain insertion order) and then simply iterate over the key range. This has some major drawbacks though, for example it first needs to locate the start of the range by fully iterating to there. And it relies on the fact that you inserted them correctly.
LinkedHashMap<String, String> map = ...
String startKey = "BA";
String endKey = "BM";
boolean isInRange = false;
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
if (!isInRange) {
if (key.equals(startKey)) {
isInRange = true;
} else {
continue;
}
}
...
if (key.equals(endKey)) {
break;
}
}
// rangeLower and rangeUpper can be arguments
int i = 0;
for (Object mapKey : map.keySet()) {
if (i < rangeLower || i > rangeUpper) {
i++;
continue;
}
// Do something with mapKey
}
The above code iterates by getting keyset and explicitly maintaining index and incrementing it in each loop. Another option is to use LinkedHashMap, which maintains a doubly linked list for maintaining insertion order.
I don't believe you can. The algorithm you propose assumes that the keys of a HashMap are ordered and they are not. Order of keys is not guaranteed, only the associations themselves are guaranteed.
You might be able to change the structure of your data to something like this:
ranges = {
BQ=BT,
BU=BY,
....
}
Then the iteration over the HashMap keys (start cells) would easily find the matching end cells.

Remove a specific value from a key (HashMaps)

I have the following HashMap (HashMap<String, String[]>) and was wondering, if there is a method to remove a specific String from the array of a specific key.
I've found only methods to remove one key basing on a value, but for example, I have:
("key1", new String[]{"A", "B", "C"})
How can I remove only B?
Here's s plain Java solution:
map.computeIfPresent("key1", (k, v) -> Arrays.stream(v)
.filter(s -> !s.equals("B")).toArray(String[]::new));
You would get the values for the specific key and remove the given value from it, then put it back into the map.
public void <K> removeValueFromKey(final Map<K, K[]> map, final K key, final K value) {
K[] values = map.get(key);
ArrayList<K> valuesAsList = new ArrayList<K>(values.length);
for (K currentValue : values) {
if (!currentValue.equals(value)) {
valuesAsList.add(currentValue);
}
}
K[] newValues = new K[valuesAsList.size()];
newValues = valuesAsList.toArray(newValues);
map.put(key, newValues);
}
Be aware that the runtime of course is linear to the size of the given array. There is no faster way, because you need to iterate over each element of the array to find all values that are equal to the given value.
However, you could do a faster implementation with other data structures, if that is practicable. For example sets would be better than arrays, or any other data structure that implements contains is faster than O(n).
The same holds for space complexity; you have a peak where you need to hold both arrays in the memory. This is because the size of an array cannot be changed; the method will construct a new array. Thus you will have two arrays in the memory, O(2n).
A Collection<String> may be a better solution, depending on how often you'll call the method, compared to how many elements a map holds.
Another thing is that you can speed up the progress by guessing a good initial capacity for the ArrayList.

How to return keys of a map according to a condition?

I'm learning Java using BlueJ, I have made a class that has a HashMap of (Integer, String) that contains an ID number of somebody and their name.
I want a method to return a collection of all the keys that satisfy a condition, like if their ID number begins with 3 for example. I can't figure out how to do this.
And then another method that returns a collection of the values if they satisfy a condition, I was thinking it would be very similar to the previous method.
I know I need to loop through the map but I am not sure how to write the condition to populate the new map.
Here's an example that returns all the odd keys, in a Collection. Lists and Sets are Collections, in the same way that ArrayLists are Lists. You could change Collection to List (or even ArrayList) in this example and it would do the same thing.
public Collection<Integer> getOddKeys() {
// keySet is a method of Map that returns a Set containing all the keys (and no values).
Collection<Integer> result = new ArrayList<Integer>();
for(Integer key : map.keySet()) {
if((key % 2) == 0) // if the key is odd...
result.add(key); // ... then add it to the result
}
return result;
}
You should be able to modify this example to check the values instead - I won't just give you that code, because it's very similar, and easy to figure out if you understand how this example works.
You need to use the values method, which returns a collection of the values, in the same way that keySet returns a collection (more specifically, a set) of the keys. If you're wondering about why keySet returns a set and values doesn't, it's because you can use the same value twice in a map, but you can't use the same key twice.
You could do the following:
Create a holder list
Iterator over your map keys using map.keySet().iterator();
Check if the key start with 3, if yes add it to the key list.
return the keys list.
In your case (if the map is not too big), I'll get all keys of the map, then process them one by one to math my criteria:
Map<Integer, String> myMap=getFromSomeWhere();
for(Integer i : myMap.keySet() {
String k=String.valueOf(i);
if(k.startsWith("3")) {
//do what you want
}
}
public void CountryAbbriviationMap(String input)
{
map<string ,string> countrymap =new map<string ,string>{'Australia'=>'AUS','Argentina'=>'ARG', 'India'=>'IND'};
for(string key : countrymap.keySet())
{
if(key.startsWithIgnoreCase('A') && input.startsWithIgnoreCase('A'))
{
system.debug(key); //TO GET KEYS
system.debug(countrymap.get(key)); //TO GET VALUES
}
}
}

How to get position of key/value in LinkedHashMap using its key

I have a LinkedHashMap (called info) that contains name/age (string/int) pairs. How can I get the position of the key/value if I input the key? For example, if my LinkedHashMap looked like this {bob=12, jeremy=42, carly=21} and I was to search jeremy, it should return 1 as its in position 1. I was hoping I can use something like info.getIndex("jeremy").
HashMap implementations in general are un-ordered for Iteration.
LinkedHashMap is predictablely ordered for Iteration ( insertion order ) but does not expose the List interface and a LinkedList ( which is what mirrors the key set insertion order ) does not track index position itself either, it is very in-efficient to find the index as well. The LinkedHashMap doesn't expose the reference to the internal LinkedList either.
The actual "Linked List" behavior is implementation specific. Some
may actually use an instance of LinkedList some many just have
Entry track a previous and next Entry and use that as its
implementation. Don't assume anything without looking at the source.
The KeySet that contains the keys does not guarantee order as well because of the hashing algorithms used for placement in the backing data structure of the inherited HashMap. So you can't use that.
The only way to do this, without writing your own implementation, is to walk the Iterator which uses the mirroring LinkedList and keep a count where you are, this will be very in-efficient with large data sets.
Solution
What it sounds like you want is original insertion order index positions, you would have to mirror the keys in the KeySet in something like an ArrayList, keep it in sync with updates to the HashMap and use it for finding position. Creating a sub-class of HashMap, say IndexedHashMap and adding this ArrayList internally and adding a .getKeyIndex(<K> key) that delegates to the internal ArrayList .indexOf() is probably the best way to go about this.
This is what LinkedHashMap does but with a LinkedList mirroring the KeySet instead of an ArrayList.
int pos = new ArrayList<String>(info.keySet()).indexOf("jeremy")
I saw a suggestion from one of the duplicates of this question at
How get value from LinkedHashMap based on index not on key?
and I liked the suggestion as described as pseudo code from #schippi in the comments. I thought some working Java code might be useful to others on this approach
import java.util.ArrayList;
import java.util.LinkedHashMap;
public class IndexedLinkedHashMap<K,V> extends LinkedHashMap<K,V> {
/**
*
*/
private static final long serialVersionUID = 1L;
ArrayList<K> al_Index = new ArrayList<K>();
#Override
public V put(K key,V val) {
if (!super.containsKey(key)) al_Index.add(key);
V returnValue = super.put(key,val);
return returnValue;
}
public V getValueAtIndex(int i){
return (V) super.get(al_Index.get(i));
}
public K getKeyAtIndex(int i) {
return (K) al_Index.get(i);
}
public int getIndexOf(K key) {
return al_Index.indexOf(key);
}
}
Considering that LinkedHashMap keep the order of insertion, you can use the keySet() and List.copyOf() (since Java 10) methods like this:
List<String> keys = List.copyOf( yourLinkedHashMap.keySet() );
System.out.println( keys.indexOf("jeremy") ); // prints '1'
LinkedHashMap has "predictable iteration order" (javadoc). Items don't know their location, though, so you'll have to iterate the collection to get it. If you're maintaining a large map you may want to use a different structure for storage.
Edit: clarified iteration
You can use com.google.common.collect.LinkedListMultimap from the Google Guava library. You don't need the multimap behaviour of this class what you want is that the keys() method guarantees they are returned in insertion order and can then be used to construct a List, you can use the indexOf() to find the required index position
I do extract the positons of the key into a concurent map like this:
Here for a Map, someListOfComplexObject() would be entrySet()
and getComplexStringKeyElem() would be getKey()
might come from
final int[] index = {0};
Stream<ComplexObject> t = someListOfComplexObject.stream();
ConcurrentMap<String, List<Integer>> m =
t.collect(Collectors.groupingBy(
e -> e.getComplexStringKeyElem(),
Collectors.mapping(
e -> index[0]++,
Collectors.toList()
),
ConcurrentSkipListMap::new));

Categories