Search code examples
javahashmapiteration

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


Solution

  • 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;
        }
    }