Search code examples
javanestedhashmapiteratorhashset

Is nesting iterators inside each other expectable where for loops would break


Would you consider it good or bad to nest iterators like this?

Context: for loop version breaks because I am appending synonyms at same time as using it in a for loop.

// HashSet inside HashMap
HashMap<String, HashSet<String>> synonyms = new HashMap<String, HashSet<String>>();

// loops through synonyms 
Iterator word = synonyms.keySet().iterator();

while(word.hasNext()) {
    // loops through synonyms 
    Iterator line = synonyms.get(word.next().toString()).iterator();

    while(line.hasNext()) {
        // adds to Synonyms, this breaks for loop version
        addToSynonyms(word, line.next())
    }
}

Alternatively in the for loop versions I could make a copy of the HashMap<String, HashSet> and feed the copy to the for loop and append only the original but would the above be cleaner?

Thank you

Edited hopefully a better example below.

import java.util.Iterator;
import java.util.HashSet;
import java.util.HashMap;


// examples of looping through HashMap<String, HashSet<String>> numbersAndWords;
// 
public class Example {
    
    HashMap<String, HashSet<String>> numbersAndWords;

    public Example() {
        numbersAndWords = new HashMap<String, HashSet<String>>();
        
        // data
        numbersAndWords.put("five", new HashSet<>());
        numbersAndWords.get("five").add(("1 2 3 4 5"));
    }

    
    /** 
     * uses two iterators to loop through numbersAndWords and adds to numbersAndWords without breaking loops
     */
    public void exampleOne() {

        // loops through HashMap Strings of words "five"
        Iterator words = numbersAndWords.keySet().iterator();
        
        while(words.hasNext()) {

            // loops through HashSet Strings of numbers "1 2 3 4 5"
            Iterator numbers = numbersAndWords.get(words.next().toString()).iterator();
            while(numbers.hasNext()) {

                // data is arbitrary reason for exmaple is this appends
                // numbersAndWords which breaks the for loop 
                // Their for I have to use an iterator but should I be using to
                // or have things in a different format
                numbersAndWords.put("three", new HashSet<>());
                numbersAndWords.get("three").add(("1, 2, 3"));
                
                numbers.next();
            }
        }
    }
    
    public static void main(String[] args) {
            Example example = new Example();
            example.exampleOne();
        }

}

Currently looking into replacing the first itr with a for loop using HashMap.size() so it wont break when I append HashMap later.

Thank you


Solution

  • The purpose of this code is still a bit vague even after the latest updates.

    If some new data need to be created and appended to the original map numbersAndWords, and they depend somehow on the current state of the original map, then a new temporary map has to be created and populated (using enhanced for loops, Stream API with map/flatMap, whatever). When done, the contents of this new map may be added to the original one, using Map::putAll:

    public void exampleOne() {
        Map<String, HashSet<String>> toAppend = new HashMap<>();
    
        for (Map.Entry<String, HashSet<String>> me : numbersAndWords.entrySet()) {
            for (String str : me.getValue()) {
                System.out.println("Appending for key: " + me.getKey() + "; value=" + str);
                toAppend.computeIfAbsent("three", k -> new HashSet<>())
                        .add("1 2 3"); // or whatever is really needed
            }
        }
        numbersAndWords.putAll(toAppend);
    
        System.out.println(numbersAndWords);
    }
    

    Assuming that the original map is set up like this:

    public Example() {
        numbersAndWords = new HashMap<String, HashSet<String>>();
            
        // data
        numbersAndWords.computeIfAbsent("five", k -> new HashSet<>()).add("1 2 3 4 5");
        numbersAndWords.computeIfAbsent("five", k -> new HashSet<>()).add("5 4 3 2 1");
        numbersAndWords.computeIfAbsent("four", k -> new HashSet<>()).add("1 2 3 4");
    }
    

    The output is as follows when invoking new Example().exampleOne();:

    Appending for key: four; value=1 2 3 4
    Appending for key: five; value=1 2 3 4 5
    Appending for key: five; value=5 4 3 2 1
    {four=[1 2 3 4], five=[1 2 3 4 5, 5 4 3 2 1], three=[1 2 3]}
    

    However, such population of hardcoded values is pretty meaningless because no duplicate entries can be created in the map for a hardcoded key as well as adding the same values to a set is useless.