If I iterate over a standard HashMap and attempt to add elements to it while iterating I get ConcurrentModificationException.
So I try an HashMap allowing concurrent adds:
ConcurrentHashMap<String, Integer> cMap = new ConcurrentHashMap<>();
cMap.put("one", 1);
cMap.forEach((key, value) -> cMap.put(key + key, value + value));
System.out.println(cMap);
However, the resulting map is a bit weird:
{oneoneoneoneoneoneoneone=8, one=1, oneone=2, oneoneoneone=4}
and if changing the key to zx (cMap.put("zx", 1)), the result is now:
{zxzx=2, zx=1}
Questions:
1) Why this happens? The two concurrent operations (iterating and adding) should not conflict.
2) How to fix the inconsistency?
As opposite to Collections, when changing a String while iterating over the chars of that String, this issue is not being met:
String str = scanner.next();
for (int i = 1; i < str.length(); i++) {
if (str.charAt(i) == str.charAt(i-1)) {
str = str.substring(0, i-1) + str.substring(i+1);
i = 0;
}
}
if (str.length() == 0) {
System.out.println("Empty String");
} else {
System.out.println (str);
}
}
note that in the above loop, the source String isn't actually changed but re-assigned because String is immutable and can not be modified.
The above code works fine and consistently. Is this an example of why Strings are thread safe?
It comes down to which hash bucket your new elements get added. In your first example, your new elements get added to a later hash bucket than the one you're working on, and which the iterator has yet to reach. In your second example, your new elements get added to an earlier hash bucket, which the iterator has already read.
You should be very wary about modifying collections in the middle of an iteration. In your case, it might be better for you to add your new entries into a new map, and then merge them together.
Related
I have a list of custom objects. I need to get/remove a specific object from that list but the equals implemented would not work based on what I need to search.
The following would work:
int index = -1;
for(int i = 0; i < list.size(); i++) {
if(list.get(i).getAttr().equals(arg)) {
index = i;
break;
}
}
CustomObject = list.remove(index);
// use CustomObject here
I was wondering if I could do the list.remove inside the for loop despite not using an iterator since the loop breaks immediately
Using the delete(int) method in your loop will work just fine.
Your loop is closed so you have full control on i and you can use the list as you please. You don't use i after having deleted the first element that matches, so there are no caveat. If you were to reuse it, you would have to not increment it.
To avoid any trouble, the following if both more readable and expressive. Also, it's totally implementation-agnostic.
CustomObject deletedObject = null;
for (Iterator<CustomObject> i = list.iterator(); i.hasNext(); ) {
CustomObject candidate = i.next();
if (candidate.getAttr().equals(arg)) {
deletedObject = candidate;
i.remove();
break;
}
}
if (deletedObject != null) {
// Do something with deletedObject
}
There is no special program state associated with “being inside a for loop”. What matters, are the actions your program performs.
So
int index = -1;
for(int i = 0; i < list.size(); i++) {
if(list.get(i).getAttr().equals(arg)) {
index = i;
break;
}
}
CustomObject o = list.remove(index);
// use CustomObject here
is identical to
for(int i = 0; i < list.size(); i++) {
if(list.get(i).getAttr().equals(arg)) {
CustomObject o = list.remove(i);
// use CustomObject here
break;
}
}
as it performs the same actions (letting aside that the first variant will throw when no match has been found). The differences regarding local variables defined in these code snippets are, well, local and do not affect anything outside the containing method.
That said, the rule that you must not modify a collection (except through the iterator) while iterating over it, applies to iterator-based loops, where you are not in control of the iterator’s internal state. When you are using an index based loop and fully understand the implications of removing an object at a particular index (of a random access list), you can even continue iterating. The important aspects, to do it correctly, are that the indices of all subsequent elements decrease by one when removing an element, further the size decreases so you must either, reread the size or decrement a previously cached size value.
E.g., the following loop is valid
for(int i = 0; i < list.size(); i++) {// rereads size on each iteration
if(list.get(i).getAttr().equals(arg)) {
CustomObject o = list.remove(i--); // decrease index after removal
// use CustomObject here
// continue
}
}
But, of course, it’s more idiomatic to use an Iterator or removeIf, as these approaches are not only easier to handle, they also work with other collections than random access lists. And especially removeIf may be more efficient when you remove more than one element.
Just another way using streams,
List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("D");
str1.add("D");
Optional<Object> foundVal = str1.stream().filter(s ->
s.contains("D")).findFirst().map(val -> {
str1.remove(val);
return val;
});
System.out.println(str1);
System.out.print(" " + foundVal.get());
Output
[A, B, D] D
Given the following datatype Testcase (XQuery, Testpath, FirstInputFile, SecondInputFile, Expected)
how can I properly delete duplicates.
Definition of duplicates:
If FirstInputFile already in the list as SecondInputFile vice versa.
Here is the Testdata
tcs.add(new HeaderAndBodyTestcase("XQ 1", "/1", "FAIL", "FAIL2", "FAILED"));
tcs.add(new HeaderAndBodyTestcase("XQ 1", "/1", "FAIL2", "FAIL", "FAILED"));
tcs.add(new HeaderAndBodyTestcase("XQ 2", "/2", "FAIL4", "FAIL3", "FAILED2"));
tcs.add(new HeaderAndBodyTestcase("XQ 2", "/2", "FAIL3", "FAIL4", "FAILED2"));
and here is the function
protected void deleteExistingDuplicatesInArrayList(final ArrayList<HeaderAndBodyTestcase> list) {
for (int idx = 0; idx < list.size() - 1; idx++) {
if (list.get(idx).firstInputFile.equals(list.get(idx).secondInputFile)
|| (list.get(idx + 1).firstInputFile.equals(list.get(idx).firstInputFile)
&& list.get(idx).secondInputFile.equals(list.get(idx + 1).secondInputFile)
|| (list.get(idx).firstInputFile.equals(list.get(idx + 1).secondInputFile)
&& list.get(idx).secondInputFile.equals(list.get(idx + 1).firstInputFile)))) {
list.remove(idx);
}
}
}
This solution is already working, but seems very crappy, so is there a better solution to this?
put everything in a Set using a comparator if necessary, and create a list from this set if you really need a List (and not a Collection)
Set<HeaderAndBodyTestcase> set = new Hashset<>(list);
Given your rather peculiar "equality" constraints, I think the best way would be to maintain two sets of already seen first- and second input files and a loop:
Set<String> first = new HashSet<>();
Set<String> second = new HashSet<>();
for (HeaderAndBodyTestcase tc : tcs) {
if (! first.contains(tc.getSecondInputFile()) &&
! second.contains(tc.getFirstInputFile())) {
first.add(tc.getFirstInputFile());
second.add(tc.getSecondInputFile());
System.out.println(tc); // or add to result list
}
}
This will also work if "equal" elements do not appear right after each other in the original list.
Also note that removing elements from a list while iterating the same list, while working sometimes, will often yield unexpected results. Better create a new, filtered list, or if you have to remove, create an Iterator from that list and use it's remove method.
On closer inspections (yes, it took me that long to understand your code), the conditions in your current working code are in fact much different than what I understood from your question, namely:
remove element if first and second is the same (actually never checked for the last element in the list)
remove element if first is the same as first on last, and second the same as second on last
remove if first is same as last second and vice versa
only consider consecutive elements (from comments)
Given those constraints, the sets are not needed and also would not work properly considering that both the elements have to match (either 'straight' or 'crossed'). Instead you can use pretty much your code as-is, but I would still use an Iterator and keep track of the last element, and also split the different checks to make the whole code much easier to understand.
HeaderAndBodyTestcase last = null;
for (Iterator<HeaderAndBodyTestcase> iter = list.iterator(); iter.hasNext();) {
HeaderAndBodyTestcase curr = iter.next();
if (curr.firstInputFile.equals(curr.secondInputFile)) {
iter.remove();
}
if (last != null) {
boolean bothEqual = curr.firstInputFile.equals(last.firstInputFile)
&& curr.secondInputFile.equals(last.secondInputFile);
boolean crossedEqual = curr.secondInputFile.equals(last.firstInputFile)
&& curr.firstInputFile.equals(last.secondInputFile);
if (bothEqual || crossedEqual) {
iter.remove();
}
}
last = curr;
}
Right now I have an array of "Dragon"s. Each item has two values. An ID and a Count. So my array would look something like this:
Dragon[] dragons = { new Dragon(2, 4),
new Dragon(83, 199),
new Dragon(492, 239),
new Dragon(2, 93),
new Dragon(24, 5)
};
As you can see, I have two Dragons with the ID of 2 in the array. What I would like to accomplish is, when a duplicate is found, just add the count of the duplicate to the count of the first one, and then remove the duplicate Dragon.
I've done this sort of successfully, but I would end up with a null in the middle of the array, and I don't know how to remove the null and then shuffle them.
This is what I have so far but it really doesn't work properly:
public static void dupeCheck(Dragon[] dragons) {
int end = dragons.length;
for (int i = 0; i < end; i++) {
for (int j = i + 1; j < end; j++) {
if (dragons[i] != null && dragons[j] != null) {
if (dragons[i].getId() == dragons[j].getId()) {
dragons[i] = new Item(dragons[i].getId(), dragons[i].getCount() + dragons[j].getCount());
dragons[j] = null;
end--;
j--;
}
}
}
}
}
You should most probably not maintain the dragon count for each dragon in the dragon class itself.
That aside, even if you are forced to use an array, you should create an intermeditate map to store your dragons.
Map<Integer, Dragon> idToDragon = new HashMap<>();
for (Dragon d : yourArray) {
// fetch existing dragon with that id or create one if none present
Dragon t = idToDragon.computeIfAbsent(d.getId(), i -> new Dragon(i, 0));
// add counts
t.setCount(t.getCount() + d.getCount());
// store in map
idToDragon.put(d.getId(), t);
}
Now the map contains a mapping between the dragons' ids and the dragons, with the correct counts.
To create an array out of this map, you can just
Dragon[] newArray = idToDragon.values().toArray(new Dragon[idToDragon.size()]);
You may be force to store the result in an array but that doesn't mean that you're force to always use an array
One solution could be using the Stream API, group the items adding the count and save the result into an array again. You can get an example of how to use the Stream API to sum values here. Converting a List<T> into a T[] is quite straightforward but anyways, you have an example here
The size of an array cannot be changed after it's created.
So you need to return either a new array or list containing the merged dragons.
public static Dragon[] merge(Dragon[] dragonArr) {
return Arrays.stream(dragonArr)
// 1. obtain a map of dragon IDs and their combined counts
.collect(groupingBy(Dragon::getId, summingInt(Dragon::getCount)))
// 2. transform the map entries to dragons
.entrySet().stream().map(entry -> new Dragon(entry.getKey(), entry.getValue()))
// 3. collect the result as an array
.toArray(Dragon[]::new);
}
I am looking at a possibility of unrolling the loop which is written to iterate the elements in a hash map.Below posted is the code.
for (final Object key : map.keySet())
{
if (input_map.containsKey(key))
{
System.out.println("Matching key: " + key);
if (map.get(key).equals(input_map.get(key)))
{
System.out.println("hii!done");
}
else
{
System.out.println(key);
final String values =
key.
toString().
substring(key.toString().lastIndexOf("\\") + 1);
System.out.println("input_map" +
input_map.get(key));
System.out.println("map" + map.get(key));
}
}
}
Explanation:
Currently, comparison in the loop is being done based on one element at a time i.e "key".I am looking at a possibility where i can retrieve the next successive keys in one single iteration i.e(key,key+1,key+2).
Any Suggestions would be highly helpful.
Use the KeySet or EntrySet iterator() method and while loop through with hasNext() and next(). You need to handle cases where it has not 3 repeating elements. Then you have the 3 keys and should be able to easily access the values in the Map.
Iterator<Integer> it = myMap.keySet().iterator();
while(it.hasNext())
{
int first = it.next();
int second = it.next();
int third = it.next();
}
So I have a hashmap which contains key as Strings and value as Integers of the count of those strings occurring in my Set
for eg I would have a hashMap as follows
Key Value
abcd 4 (meaning there are 4 duplicate strings of abcd in my Set defined someplace)
----- 13
b-b- 7
and so on..
Now what I am trying to do is remove all the empty strings entries from my HashMap. So in the above example I would want to remove all the empty strings with value 13. So my resulting HashMap would be
Key Value
abcd 4
b-b- 7
This is my code that tries to do the same. generateFeedbackMap() is function which returns the HashMap in consideration StringIterator is a class which I have defined which iterates over through each character of my Strings.
for(String key : generateFeedbackMap().keySet()) {
StringIterator it = new StringIterator(key);
int counter = 0;
while(it.hasNext()){
String nextChar = it.next();
if(nextChar.equals("-")){
counter++;
}
Iterator<Map.Entry<String, Integer>> mapIterator = generateFeedbackMap().entrySet().iterator();
if(counter >= key.length()){
while(mapIterator.hasNext()){
Map.Entry<String, Integer> entry = mapIterator.next();
if(entry.getKey().equals(key)){
mapIterator.remove();
}
}
}
}
}
So I increment the counter wherever I find a "-" character. When the counter equals my key string length which means it is an empty string, I remove it using Map Iterator but this does not remove the entry from my Map. What am I doing wrong?
generateFeedbackMap() makes it sound like you’re getting a copy of the underlying map, in which case removing a key from the copy won’t affect the underlying map. If you’re actually getting the map, then you should rename your method.
Regardless, the following would accomplish the same as your original code (but will only remove from the copy).
Map<String,Integer> feedbackMap = generateFeedbackMap();
for ( String key : feedbackMap.keySet() ) {
if ( key.matches("-+") ) {
feedbackMap.remove(key);
}
}
If you’re stuck getting a copy of the underlying map, then you do need to create your new helpfulMap. But you can still use a regular expression and other Map functions to speed things up:
Map<String,Integer> helpfulMap = new HashMap<>();
for ( Map.Entry<String,Integer> entry : generateFeedbackMap().entrySet() ) {
if ( ! entry.getKey().matches("-+") ) {
helpfulMap.put(entry.getKey(),entry.getValue());
}
}
Okay guys, I think I figured out a solution. I just copied all my current entries from oldMap to a new defined HashMap which would contain at least one letter in their keys. So essentially I got rid of all the removing and iterating over strings and just use another HashMap instead as below
Map<String, Integer> HelpfulMap = new HashMap<String,Integer>();
for(String key : generateFeedbackMap().keySet()) {
StringIterator it = new StringIterator(key);
while(it.hasNext()){
String nextChar = it.next();
if(!nextChar.equals("-")){
HelpfulMap.put(key, generateFeedbackMap().get(key));
}
}
}
I don't know what I was doing previously. I went for a good shower and came up with this idea and it worked. I love programming!
Thanks everyone for your inputs!