ConcurrentModificationException during putting new element into HashMap - java

I have some code:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)),
numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
for (String key : letters.keySet()) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)),
numberOfLettersInWord(input,input.charAt(i)));
} else continue;
System.out.println(letters);
}
System.out.println(1);
}
System.out.println(2);
The main idea in the code - there is some word in String input(not empty, not null, with no non-letter symbols), need to count how many times each letter can be found there. Counting works OK (in the numberOfLettersInWord method) but when I try to add letters and digits to HashMap<String, Integer> some problems happens - it adds all letters and their numbers correctly but some error pops up. For this code it will show:
1
1
{a=4, b=4}
1
1
1
1
{a=4, b=4, c=3}
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1579)
at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1602)
at LetterCounter.count(LetterCounter.java:25)
at LetterCounter.main(LetterCounter.java:11)
Process finished with exit code 1
From what I see there is something happens when there are no new letters to be added. Can you explain why this happens and how to solve this?
It supposed to have some more digit outputs after the {a=4, b=4, c=3} was shown but it ends with the exception (it is not really necessary, just an indicator where it stops working...)
The word used in this run was String input = "aabbabcccba";
numberOfLettersInWord returns Integer value of how many times letter input.charAt(i) was found in word input(this works ok)
line 2 in code fragment was used just to make the HashMap contain at least one line (null and empty checks already done by this moment and work well)
I saw people had problems with hashmap.remove() in Why is a ConcurrentModificationException thrown and how to debug it but I am not sure this is the same-same thing that can be solved with that answer.
Also I am not sure this answer is applicable for my case ConcurrentModificationException - HashMap

ok, i think i solved it:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)),numberOfLettersInWord(input,input.charAt(0)));
for(int i = 0; i < input.length(); i++) {
letters.putIfAbsent(String.valueOf(input.charAt(i)),numberOfLettersInWord(input,input.charAt(i)));
}
i removed some extra code and it started work, even all tests passed

Why the ConcurrentModificationException?
You're getting a ConcurrentModificationException because you are structurally modifying the map while iterating its key set.
Documentation
Here's what the documentation of HashMap says on the subject:
The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Those "collection view methods" it mentions are the following:
HashMap#keySet(), which returns a Set<K>.
HashMap#values(), which returns a Collection<V>.
HashMap#entrySet(), which returns a Set<Map.Entry<K, V>>.
For-Each Loops
If you aren't aware, a for-each loop uses an Iterator behind the scenes. In other words, something like this:
List<String> list = List.of("one", "two", "three");
for (String element : list) {
System.out.println(element);
}
Is compiled down to:
List<String> list = List.of("one", "two", "three");
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
String element = iterator.next();
System.out.println(element);
}
Your Code
You have a for-each loop iterating over the key set of your map. Inside this for-each loop you have a call to put, which is a structurally-modifying operation, on the same map.
for (String key : letters.keySet()) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)),
numberOfLettersInWord(input,input.charAt(i)));
} else continue;
System.out.println(letters);
}
Thus, a ConcurrentModificationException is likely to be thrown. In your case it's all but guaranteed.
Solution
You are apparently trying to count the frequencies of each letter in a string. This does not require you to loop over the key set of the map. The fact you don't actually use the key variable anywhere inside the for-each loop is a good indicator of this. This means you can simply get rid of the for-each loop and your code should work just fine.
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)), numberOfLettersInWord(input,input.charAt(i)));
}
}
Note that call to put if the map does not already contain the key could be replaced with a call to computeIfAbsent. That method takes the key and a Function that computes the value if the key is not already contained in the map (or if the key is currently mapped to null). It would look something like this:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
letters.computeIfAbsent(String.valueOf(input.charAt(i)), key -> numberOfLettersInWord(input, key));
}
Note: The second argument the above computeIfAbsent call is a Function implemented via a lambda expression.
Potential Improvements
There may be a couple of improvements you could make to your code.
Change Key Type to Character
Given you're counting the frequency of characters, it would make sense to represent that in the code by using a Map<Character, Integer> instead of a Map<String, Integer>.
Count as You Go
I can only assume that numberOfLettersInWord loops over the input string and counts how many times the given character occurs in said string. This means you loop over the string for each character in the string, resulting in an inefficient algorithm. Though you do have optimization where you only compute the frequency of a character if you haven't already done so for that character, so that improves things a little.
However, you're already looping over all the characters in the input string. You might as well count the frequency of each character as you go. It could look something like:
String input = ...;
Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
Character key = input.charAt(i);
Integer value = frequencies.get(key);
if (value == null) {
frequencies.put(key, 1);
} else {
frequencies.put(key, value + 1);
}
}
Use compute to Count
The body of that for loop can be replaced with a call to compute:
String input = ...;
Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
frequencies.compute(input.charAt(i), (key, value) -> {
if (value == null) {
return 1;
} else {
return value + 1;
}
});
}
And that lambda expression (implementing a BiFunction) can be "simplified" even more:
(key, value) -> value == null ? 1 : value + 1
Use merge to Count
Another option is to use merge:
frequencies.merge(input.charAt(i), 1, Integer::sum);
Note: The Integer::sum is a method reference implementing a BiFunction.

letters.keySet() is returning a set which is backed by the keys of the HashMap, which you then alter by calling put(). So the conflict here is between the keySet and the keys of the map. You would need to use an iterator, or extract the keys into a separate collection, by copying the keySet each time through the outer loop. Honestly, the algorithm is sounding kind of expensive, though I haven't really tried to work out a better approach...

Related

Generating a TreeMap of Frequencies for each Number in a List

I have a method which is meant to return a TreeMap with the keys being the numbers and the values being the frequencies of these numbers in the given array:
public TreeMap<Integer, Integer> getFrequencyCount(List<Integer> array) {
Collections.sort(array);
TreeMap<Integer, Integer> a = new TreeMap<>();
for (int i = 0; i < array.size(); i++){
if (array.indexOf(array.get(i)) == 0 || array.get(i) != array.get(i - 1)){
a.put(array.get(i), 1);
} else if (array.get(i) == array.get(i - 1)) {
int votes = a.get(array.get(i));
a.put(array.get(i), votes + 1);
}
}
return a;
}
Every value is correct except for the first value of the map, which is always 1. I can't figure out why it's happens.
the first value of the map, which is always 1
That happens because when an element is equal to the first element in the sorted list - indexOf() == 0 in the snippet below, the value associated with it will always be 1. The second part of this condition is also fishy.
if (array.indexOf(array.get(i)) == 0 || array.get(i) != array.get(i - 1)) {
a.put(array.get(i), 1);
}
I guess instead the condition should be if (!a.containsKey(array.get(i)))
There's a few more issues with your code:
Don't compare objects using == or != unless you don't need to make sure that two references point to the same object. Instances of Integer are objects. To compare values of the objects, use equals() method.
Don't confuse array and ArrayList (and avoid using confusing names). Array is the simplest data container built-in into the language, it has no behavior. On the other hand, ArrayList is a Collection, backed by an array (and there are some others apart from ArrayList), which offers lots of useful methods.
Write your code against interfaces. We have a contract NavigableMap, use it as a type when you need TreeMap.
By sorting the list data, you're not getting a performance advantage. If your goal was to try implementing this task by iterating over the sorted list - that's fine. But mind that method get() and put() have a time complexity of O(n) for TreeMap. If you want to go this route (meaning to sort data in the list) it would be chipper to store the results into LinkedHashMap and then dump it into TreeMap (method putAll() is capable of taking advantage of the previously sorted data of the map passed into it).
Here's an alternative way of implementing this task using Java 8 method merge():
public NavigableMap<Integer, Integer> getFrequencyCount(List<Integer> list) {
Map<Integer, Integer> frequencies = new HashMap<>();
for (Integer next: list) {
frequencies.merge(next, 1, Integer::sum);
}
return new TreeMap<>(frequencies);
}
And if you're comfortable with streams, you can make use of one of the flavors of Collectors.toMap():
public NavigableMap<Integer, Integer> getFrequencyCount(List<Integer> list) {
return list.stream()
.collect(Collectors.toMap(
Function.identity(),
i -> 1,
Integer::sum,
TreeMap::new
));
}

Removing duplicate elements & count repetitions in ArrayList

This is more difficult than I expected. I have a sorted ArrayList of Strings (words), and my task is to remove the repetitions and print out a list of each word, followed by the number of the word's repetitions. Suffice it to say that it's more complex than I expected. After trying different things, I decided to use a HashMap to store the words (key), value(repetitions).
This is the code. Dictionary is the sorted ArrayList and Repetitions that HashMap.
public void countElements ()
{
String word=dictionary.get(0);
int wordCount=1;
int count=dictionary.size();
for (int i=0;i<count;i++)
{
word=dictionary.get(i);
for (int j=i+1; j<count;j++)
{
if(word.equals(dictionary.get(j)))
{
wordCount=wordCount+1;
repetitions.put(word, wordCount);
dictionary.remove(j--);
count--;
}
}
}
For some reason that I do not understand (I'm a beginner), after I call the dictionary.remove(j--) method, variable j decrements by 1, even though it should be i+1. What am I missing? Any ideas on how to do this properly would be appreciated. I know that it would be best to use an iterator, but that can become even more confusing.
Many thanks.
A version which uses streams:
final Map<String, Long> countMap = dictionary.stream().collect(
Collectors.groupingBy(word -> word, LinkedHashMap::new, Collectors.counting()));
System.out.println("Counts follow");
System.out.println(countMap);
System.out.println("Duplicate-free list follows");
System.out.println(countMap.keySet());
Here we group (using Collectors.groupingBy) the elements of the list using each element (i.e. each word) as a key in the resulting map, and counting this word occurrences (using Collectors.counting()).
Outer collector (groupingBy) uses counting collector as a downstream collector that collects (here, counts) all the occurrences of a single word.
We're using LinkedHashMap here to build the map because it maintains the order in which key-value pairs were added to it as we want to maintain the same order that words had in your initial list.
And one more thing: countMap.keySet() is not a List. If you want to get a List in the end, it's just new ArrayList(countMap.keySet()).
This code will serve your purpose. Now dictionary would contain the unique words and hashmap would contain the frequency count of each word.
public class newq {
public static void main(String[] args)
{
ArrayList<String> dictionary=new ArrayList<String>();
dictionary.add("hello");
dictionary.add("hello");
dictionary.add("asd");
dictionary.add("qwet");
dictionary.add("qwet");
HashMap<String,Integer> hs=new HashMap<String,Integer>();
int i=0;
while(i<dictionary.size())
{
String word=dictionary.get(i);
if(hs.containsKey(word)) // check if word repeated
{
hs.put(word, hs.get(word)+1); //if repeated increase the count
dictionary.remove(i); // remove the word
}
else
{
hs.put(word, 1); //not repeated
i++;
}
}
Iterator it = hs.entrySet().iterator();
while(it.hasNext())
{
HashMap.Entry pair = (HashMap.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
it.remove();
}
for(String word: dictionary)
{
System.out.println(word);
}
}
}
If you don't want 'j' to decrement you should use j-1.
Using j--, --j, j++, or ++j changes the value of the variable.
This link has a good explanation and simple examples about post- en pre-incrementing.

Using the class methods of something in a TreeMap

I know there are already topics on this exact thing but none of them actually answer my question. is there a way to do this?
if I have a TreeMap that uses strings as the keys and objects of the TreeSet class as the values, is there a way that I can add some int to a set that is associated with a specific key?
Well what I'm supposed to do is make a concordance from a text file using the TreeMap and TreeSet class. my plan is this use the TreeMap keys as the words in the text file and the values will be sets of line numbers on which the word appears. So you step through the text file and every time you get a word you check the TreeMap to see if you already have that key and if you don't you add it in and create a new TreeSet of line numbers starting with the one you are on. If you already have it then you just add the line number to the set. So you see what I need to do is access the .add() function of the set
something like
map.get(identifier).add(lineNumber);
I know that doesn't work but how do I do it?
I mean if there is an easier way to do what I'm trying to do I'd be happy to do that instead, but I would still like to know how to do it this way just for you know learning and experience and all that.
Consider the following logic (I assume the input words are in an array):
TreeMap<String, TreeSet<Integer>> index = new TreeMap<String, TreeSet<Integer>>();
for (int pos = 0; pos < input.length; pos++) {
String word = input[pos];
TreeSet<Integer> wordPositions = index.get(word);
if (wordPositions == null) {
wordPositions = new TreeSet<Integer>();
index.put(word, wordPositions);
}
wordPositions.add(pos);
}
This results in the index you need, which maps from strings to the set of positions where the string appears. Depending on your specific needs, the outer/inner data structure can be changed to HashMap/HashSet respectively.
Why not to use a Map of String and ArrayList<int>, something like:
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();
And then always when you get a word you check if it already exists in the Map and if it does exist you add the line number to the List and if not you create a new entry in the Map for the given word and the given line number.
if (map.get(word ) != null) {
map.get(word).add(line);
}
else{
final List<Integer> list = new ArrayList<Integer>();
list.add(line);
map.put(word, list);
}
If I understand correctly, you want to have a treemap with each key referring to a treeset for storing line number on which the key has appeared. It is definitely doable and implementation is quiet simple. I am not sure why your map.get(identifier).add(lineNumber); is not working. This is how I would do it:
TreeMap<String, TreeSet<Integer>> map = new TreeMap<String, TreeSet<Integer>>();
TreeSet<Integer> set = new TreeSet<Integer>();
set.add(1234);
map.put("hello", set);
map.get("hello").add(123);
It all works fine.
The only reason your construct won't work is because the result of map.get(identifier) can be null. Personally, I like the lazy initialization solution that #EyalSchneider answered with. But there is an alternative if you know all your identifiers ahead of time: for example, if you preload your Map with all known English words. Then you can do something like:
for (String word : allEnglishWords) {
map.put(word, new LinkedList<Integer>);
}
for (int pos = 0; pos < input.length; pos++) {
String word = input[pos];
map.get(word).add(pos);
}

For each to For Loop Conversion

May I know how do I convert the following for each loop to a normal for loop?
for (SortedMap.Entry<Integer, String> entry : mapDefect.entrySet())
I have a count variable as the starting point and the end of the map as the end point. So accordingly how may I convert it into a normal for loop?
Section 14.14.2 of the JLS gives the translation. In this case, it would be roughly:
for (Iterator<SortedMap.Entry<Integer, String>> iterator
= mapDefect.entrySet().iterator();
iterator.hasNext(); )
{
SortedMap.Entry<Integer, String> entry = iterator.next();
// ...
}
Alternatively, use Guava's Iterables class to take a section of the sorted set:
Iterable<SortedMap.Entry<Integer, String>> section = Iterables.limit(
Iterables.skip(mapDefect.entrySet(), start), end - start);
for (SortedMap.Entry<Integer, String> entry : section) {
// ...
}
Or if it's just from count (with the clarifying comment):
for (SortedMap.Entry<Integer, String> entry :
Iterables.skip(mapDefect.entrySet(), count)) {
// ...
}
You say the task is to skip the first count elements, and process the rest.
This can be done with either a "for" loop, or a "for each" loop. In this case, I'd keep this as a "for each" loop:
int i = 0;
for (SortedMap.Entry<Integer, String> entry : mapDefect.entrySet()) {
if (i++ < count) continue;
...
}
The recommended way to iterate of a map is using an iterator or a for-each loop (which uses an iterator).
Converting your for each loop to a "normal" loop can work in your case, because you are using Integers as map keys:
for (int i = 0; i < mapDefect.size(); i++) {
String value = mapDefect.get(i)
// do something with value
}
But note that this only works if you are using map keys as you would use array/list indices (which makes the map useless).
To use this kind of loop you have to use consecutive positive integers as map keys starting at 0

Counting occurrences of words in an array

I've been working on something which takes a stream of characters, forms words, makes an array of the words, then creates a vector which contains each unique words and the number of times it occurs (basically a word counter).
Anyway I've not used Java in a long time, or much programming to be honest and I'm not happy with how this currently looks. The part I have which makes the vector looks ugly to me and I wanted to know if I could make it less messy.
int counter = 1;
Vector<Pair<String, Integer>> finalList = new Vector<Pair<String, Integer>>();
Pair<String, Integer> wordAndCount = new Pair<String, Integer>(wordList.get(1), counter); // wordList contains " " as first word, starting at wordList.get(1) skips it.
for(int i= 1; i<wordList.size();i++){
if(wordAndCount.getLeft().equals(wordList.get(i))){
wordAndCount = new Pair<String, Integer>(wordList.get(i), counter++);
}
else if(!wordAndCount.getLeft().equals(wordList.get(i))){
finalList.add(wordAndCount);
wordAndCount = new Pair<String, Integer>(wordList.get(i), counter=1);
}
}
finalList.add(wordAndCount); //UGLY!!
As a secondary question, this gives me a vector with all the words in alphabetical order (as in the array). I want to have it sorted by occurrence, the alphabetical within that.
Would the best option be:
Iterate down the vector, testing each occurrence int with the one above, using Collections.swap() if it was higher, then checking the next one above (as its now moved up 1) and so on until it's no longer larger than anything above it. Any occurrence of 1 could be skipped.
Iterate down the vector again, testing each element against the first element of the vector and then iterating downwards until the number of occurrences is lower and inserting it above that element. All occurrences of 1 would once again be skipped.
The first method would doing more in terms of iterating over the elements, but the second one requires you to add and remove components of the vector (I think?) so I don't know which is more efficient, or whether its worth considering.
Why not use a Map to solve your problem?
String[] words // your incoming array of words.
Map<String, Integer> wordMap = new HashMap<String, Integer>();
for(String word : words) {
if(!wordMap.containsKey(word))
wordMap.put(word, 1);
else
wordMap.put(word, wordMap.get(word) + 1);
}
Sorting can be done using Java's sorted collections:
SortedMap<Integer, SortedSet<String>> sortedMap = new TreeMap<Integer, SortedSet<String>>();
for(Entry<String, Integer> entry : wordMap.entrySet()) {
if(!sortedMap.containsKey(entry.getValue()))
sortedMap.put(entry.getValue(), new TreeSet<String>());
sortedMap.get(entry.getValue()).add(entry.getKey());
}
Nowadays you should leave the sorting to the language's libraries. They have been proven correct with the years.
Note that the code may use a lot of memory because of all the data structures involved, but that is what we pay for higher level programming (and memory is getting cheaper every second).
I didn't run the code to see that it works, but it does compile (copied it directly from eclipse)
re: sorting, one option is to write a custom Comparator which first examines the number of times each word appears, then (if equal) compares the words alphabetically.
private final class PairComparator implements Comparator<Pair<String, Integer>> {
public int compareTo(<Pair<String, Integer>> p1, <Pair<String, Integer>> p2) {
/* compare by Integer */
/* compare by String, if necessary */
/* return a negative number, a positive number, or 0 as appropriate */
}
}
You'd then sort finalList by calling Collections.sort(finalList, new PairComparator());
How about using google guava library?
Multiset<String> multiset = HashMultiset.create();
for (String word : words) {
multiset.add(word);
}
int countFoo = multiset.count("foo");
From their javadocs:
A collection that supports order-independent equality, like Set, but may have duplicate elements. A multiset is also sometimes called a bag.
Simple enough?

Categories

Resources