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.
Related
I have a LinkedHashMap which maps strings to string arrays.
The keys have the format of something like this: "xxx (yyy(0.123))"
Basically, I want to be able to sort the entry set in such a way that it sorts it by the decimal part, and not the beginning of the string. What I have done so far is converting the entry set to an ArrayList so that I can try calling Arrays.sort on it, but obviously that's going to just sort by the beginning of the string.
What I'm currently thinking is that I would have to go through this array, convert each key in the pair to a custom class with a comparator that compares the way I want it to (with the regular expression .*\((.*)\)\) to find the decimal). However, that sounds like a bunch of unnecessary overhead, so I was wondering if there was a simpler way. Thanks in advance.
First, you cannot "sort" a LinkedHashMap. LinkedHashMap maintain the iteration order based on the order of insertion.
If you means creating another LinkedHashMap by inserting using values from the original map, with order based on sorted order: You need to be aware of any new entries added after your initial construction will be unsorted. So you may want to create an unmodifiable Map.
For the Comparator implementation, you do not need to make it to your custom class. Just create a comparator that do the comparison is good enough.
Like this:
(haven't compiled, just to show you the idea)
// assume the key is in format of "ABCDE,12345", and you want to sort by the numeric part:
Map<String, Foo> oldMap = ....; // assume you populated something in it
Map<String, Foo> sortedMap
= new TreeMap((a,b) -> {
// here I use split(), you can use regex
int aNum = Integer.valueOf(a.split(",")[1]);
int bNum = Integer.valueOf(b.split(",")[1]);
if (aNum != bNum ) {
return aNum - bNum;
} else {
return a.compareTo(b);
});
sortedMap.addAll(oldMap);
// now sortedMap contains your entries in sorted order.
// you may construct a new LinkedHashMap with it or do whatever you want
Your solution sounds fine.
If you run into performance issues, you could look buffering the decimal value by replacing your strings with an object that contains the string and the decimal value. Then it does not need to be recalculated multiple times during the sort.
There are trade offs for the buffered solution as above and figuring out which technique is optimal will really depend on your entire solution.
Is there a reason you need to use LinkedHashMap? The javadoc specifically states
This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order)
TreeMap seems a better fit for what you're trying to achieve, which allows you to provide a Comparator at construction. Using Java 8, this could be achieved with something like:
private static final String DOUBLE_REGEX = "(?<value>\\d+(?:\\.\\d+)?)";
private static final String FIND_REGEX = "[^\\d]*\\(" + DOUBLE_REGEX + "\\)[^\\d]*";
private static final Pattern FIND_PATTERN = Pattern.compile(FIND_REGEX);
private static final Comparator<String> COMPARATOR = Comparator.comparingDouble(
s -> {
final Matcher matcher = FIND_PATTERN.matcher(s);
if (!matcher.find()) {
throw new IllegalArgumentException("Cannot compare key: " + s);
}
return Double.parseDouble(matcher.group("value"));
});
private final Map<String, List<String>> map = new TreeMap<>(COMPARATOR);
Edit: If it has to be a LinkedHashMap (yours), you can always:
map.putAll(yours);
yours.clear();
yours.putAll(map);
Imagine a situation like this:
I have a HashMap<Integer, String>, in which I store the connected clients. It is HashMap, because the order does not matter and I need speed. It looks like this:
{
3: "John",
528: "Bob",
712: "Sue"
}
Most of the clients disconnected, so this is why I have the large gap.
If I want to add a new client, I need a key and obviously the usage of _map.size() to get a key is incorrect.
So, currently I use this function to get he lowest available key:
private int lowestAvailableKey(HashMap<?, ?> _map) {
if (_map.isEmpty() == false) {
for (int i = 0; i <= _map.size(); i++) {
if (_map.containsKey(i) == false) {
return i;
}
}
}
return 0;
}
In some cases, this is really slow.
Is there any faster or more professional way to get the lowest free key of a HashMap?
Any reason to use a HashMap? If you used TreeMap instead, the map would be ordered by key automatically. Yes, you end up with O(log n) access instead of O(1), but it's the most obvious approach.
Of course you could always maintain both a HashMap and a TreeSet, making sure you add entries and remove entries from both together, if you really needed to. The TreeSet would just act as an ordered set of keys for the map.
I have data stored in a HashMap, which I want to access via multiple threads simultaneously, to split the work done on the items.
Normally (with a List for example) I would just give each thread an index to start with and could easily split the work like this:
for(int i = startIndex; i < startIndex+batchSize && i < list.size(); i++)
{
Item a = list.get(i);
// do stuff with the Item
}
Of course this doesnt work with a HashMap, because I can't access it via an index.
Is there an easy way to iterate only over a part of the map? Should I rather use another data structure for this case?
I read about SortedMap, but it has too much overhead I dont need (sorting the items). I have a lot of data and performance is crucial.
Any tips would be highly appreciated.
Firstly, you shouldn't be using a HashMap, because iteration order is undefined. Either use a LinkedHashMap, whose iteration order is the same as insertion order (at least it's defined), or use a TreeMap, whose iteration order is the natural sorting order. I would recommend the LinkedHashMap, because inserting an entry will make slicing the map up unpredictable.
To carve up a map, use this code:
LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();
for (Map.Entry<Integer, String> entry : new ArrayList<Map.Entry<Integer,String>>(map.entrySet()).subList(start, end)) {
Integer key = entry.getKey();
String value = entry.getValue();
// Do something with the entry
}
I have in-lined the code, but expanded out it is equivalent to:
List<Map.Entry<Integer, String>> entryList = new ArrayList<Map.Entry<Integer,String>>();
entryList.addAll(map.entrySet());
entryList = entryList.subList(start, end); // You provide the start and end index
for (Map.Entry<Integer, String> entry : entryList) ...
If you only do the traversal a few times, or if the map doesn't change you could get a Set of keys, and then send that to an array. From there its pretty much your normal method. But obviously if the HashMap changed then you would have to do those two operations over again which could get very costly.
With HashMap#keySet -> Set#toArray you would get an array of the keys.
With this array you could procede as before, keep the array of keys and pass them to your threads. Then each thread would access only the keys it had been assigned and finally you could access the entries of a given partition of the HashMap with only those keys.
Unless your map is enormous, the cost of iterating over a map is small compared with the cost of starting a task on another thread and trivial compared with the work you intend to do.
For this reason, the simplest way to divide up your work is likely to be turn the Map into an Array and break that up.
final Map<K, V> map =
final ExecutorServices es =
final int portions = Runtime.getRuntime().availableProcessors();
final Map.Entry<K,V>[] entries = (Map.Entry<K,V>[]) map.entrySet().toArray(new Map.Entry[map.size()]);
final int portionSize = (map.size() + portions-1)/ portions;
for(int i = 0; i < portions; i++) {
final int start = i * portionSize;
final int end = Math.min(map.size(), (i + 1) * portionSize);
es.submit(new Runnable() {
public void run() {
for(int j=start; j<end;j++) {
Map.Entry<K,V> entry = entries[j];
// process entry.
}
}
});
}
In java if I am looping over the keySet() of a HashMap, how do I (inside the loop), get the numerical index of that key?
Basically, as I loop through the map, I want to be able to get 0,1,2...I figure this would be cleaner than declaring an int and incrementing with each iteration.
Thanks.
Use LinkedHashMap instead of HashMap
It will always return keys in same order (as insertion) when calling keySet()
For more detail, see Class LinkedHashMap
Not sure if this is any "cleaner", but:
List keys = new ArrayList(map.keySet());
for (int i = 0; i < keys.size(); i++) {
Object obj = keys.get(i);
// do stuff here
}
The HashMap has no defined ordering of keys.
If all you are trying to do is get the value out of the hashmap itself, you can do something like the following:
for (Object key : map.keySet()) {
Object value = map.get(key);
//TODO: this
}
Or, you can iterate over the entries of a map, if that is what you are interested in:
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
//TODO: other cool stuff
}
As a community, we might be able to give you better/more appropriate answers if we had some idea why you needed the indexes or what you thought the indexes could do for you.
You can't - a set is unordered, so there's no index provided. You'll have to declare an int, as you say. Just remember that the next time you call keySet() you won't necessarily get the results in the same order.
Simply put, hash-based collections aren't indexed so you have to do it manually.
Posting this as an equally viable alternative to #Binil Thomas's answer - tried to add it as a comment, but was not convinced of the readability of it all.
int index = 0;
for (Object key : map.keySet()) {
Object value = map.get(key);
++index;
}
Probably doesn't help the original question poster since this is the literal situation they were trying to avoid, but may aid others searching for an easy answer.
I was recently learning the concepts behind Hashmap and it was clear that there was no definite ordering of the keys.
To iterate you can use:
Hashmap<String,Integer> hs=new Hashmap();
for(Map.Entry<String, Integer> entry : hs.entrySet()){
String key=entry.getKey();
int val=entry.getValue();
//your code block
}
I don't know is this what you're looking for
List keys = new ArrayList(map.keySet());
int index = keys.indexOf(element);
You can directly get the keys in Set<K> keySet().
val teamMember = hashMapOf(
"Coach" to "manager",
"Player Coach" to "editor",
"Player" to "member",
"Supporter" to "readonly"
)
teamMember.keys.forEachIndexed { index, key ->
// Here is your key in string
}
I have a method that gets a SortedMap as input, this map holds many SortedMap objects, the output of this method should be one SortedMap containing all elements of the maps held in the input map. the method looks like this:
private SortedMap mergeSamples(SortedMap map){
SortedMap mergedMap = new TreeMap();
Iterator sampleIt = map.values().iterator();
while(sampleIt.hasNext())
{
SortedMap currMap = (SortedMap) sampleIt.next();
mergedMap.putAll(currMap);
}
return mergedMap;
}
This is a performance killer, what can I improve here?
I don't see anything wrong with your code; all you can really do is try alternative implementations of SortedMap. First one would be ConcurrentSkipListMap and then look at Commons Collections, Google Collections and GNU Trove. The latter can yield very good results especially if your maps' keys and values are primitive types.
Is it a requirement for the input to be a SortedMap? To me it would seem easier if the input was just a Collection or List. That might speed up creating the input, and might make iteration over all contained maps faster.
Other than that I believe the most likely source of improving the performance of this code is by improving the speed of the compareTo() implementation of the values in the the sorted maps being merged.
Your code is as good as it gets. However, it seems to me that the overall design of the data structure needs some overhaul: You are using SortedMap<?, SortedMap<?, ?>, yet the keys of the parent map are not used.
Do you want to express a tree with nested elements with that and your task is it to flatten the tree? If so, either create a Tree class that supports your approach, or use an intelligent way to merge the keys:
public class NestedKey implements Comparable<NestedKey> {
private Comparable[] entries;
public NestedKey(Comparable... entries) {
assert entries != null;
this.entries = entries;
}
public int compareTo(NestedKey other) {
for(int i = 0; i < other.entries.length; i++) {
if (i == entries.length)
return -1; // other is longer then self <=> self is smaller than other
int cmp = entries[i].compareTo(other.entries[i]);
if (cmp != 0)
return cmp;
}
if (entries.length > other.entries.length)
return 1; // self is longer than others <=> self is larger than other
else
return 0;
}
}
The NestedKey entry used as a key for a SortedMap compares to other NestedKey objects by comparing each of its entries. NestedKeys that are in all elements present, but that have more entries are assumed to be larger. Thus, you have a relationship like this:
NestedKey(1, 2, 3) < NestedKey(1, 2, 4)
NestedKey(1, 3, 3) < NestedKey(2, 1, 1)
NestedKey(1, 2, 3) < NestedKey(2)
If you use only one SortedMap that uses NestedKey as its keys, then its .values() set automatically returns all entries, flattened out. However, if you want to use only parts of the SortedMap, then you must use .subMap. For example, if you want all entries wite NestedKeys between 2 and 3 , use .subMap(new NestedKey(2), new NestedKey(3))