Duplicates in java HashSet - java

It seems like duplicates are allowed in HashSets. Why is this, how do I go about removing them, and why doesn't the second remove() work below? One method of removing all duplicates is new HashSet<>(set), but is there a better way that doesn't involve creating a new object?
Set<ArrayList<String>> set = new HashSet<>();
ArrayList<String> a1 = new ArrayList<>();
ArrayList<String> a2 = new ArrayList<>();
a1.add("a");
set.add(a1);
a1.remove("a");
set.add(a2);
System.out.println(set.size());
System.out.println(set);
ArrayList<String> a3 = new ArrayList<>();
for (Object o : set) {
boolean b = o.equals(a3) && (o.hashCode() == a3.hashCode());
if (!b) System.out.println(false);
}
set.remove(new ArrayList<String>());
System.out.println(set);
set.remove(new ArrayList<String>());
System.out.println(set);
set.remove(set.iterator().next());
System.out.println(set);
System.out.println(set.iterator().next() == a1);
Output: set consists of two equal, empty lists, and the one that initially wasn't empty can't be removed.
2
[[], []]
[[]]
[[]]
[[]]
true

The location an element is stored in the HashMap depends on the hashCode of that element at the time it is added.
If after adding the element, you change a property of that element that causes its hashCode to change (in the case of an ArrayList element, removing an element from the list does exactly that), trying to find that element in the HashSet (or to remove it) will fail.

Hashing happens at insertion time for bucketing. If you change the object afterwards, its hashcode will change, but it will already be in its bucket. It will not be (directly) retrievable since you'll be trying to retrieve it with a hashcode different from the one you used to insert it.
a1.add("a");
set.add(a1); // hashed and bucketed
a1.remove("a"); // hash code changes but doesn't affect set
set.add(a2); // hashes to a different place than a1

If you modify the key of a Map or the element of a Set you are effectively corrupting it. There is no way for the collection to know you have changed the element, or to handle it correctly.
If you want to modify a key or element, you have to first remove it, modify it and add it back in.

Related

Concurrent modification of two Set<String> by retainAll

I need some how to modify set during iteration by retainAll
Let's say I have next code:
set.forEach(it - > {
set.retainAll(someMapWithSets.get(it))
});
I've tried approach with iterators:
for (Iterator<String> iterator = set.iterator(); iterator.hasNext();) {
String value = iterator.next();
set.retainAll(someMapWithSets.get(value))
}
It did not work. ConcurrentModificationException was thrown.
How to do that correctly?
Update.
Whole task is the next: 
I have set
{A,B,C}
And I have map of sets which indicates if elements are compatible:
A: B,C - (means A is compatible with B and C and etc)
B: A,E,C
C: A,B
I need to have map of sets where all possible combinations of compatible elements are present:
A,B,C
So as I part of solution I thought I could create method that retains only compatible elements for the given set and chosen element.
public static Set<String> define(Set<String> elements, String rootElement) {
Set<String> result = someMapWithElements.get(rootElement);
result.retainAll(elements);
result.add(rootElement);
result.forEach(it -> result.retainAll(someMapWithElements.get(it)))
return result;
}
But obviously I get ConcurrentModificationException
You cannot change a set using the retainAll method while iterating over that same object. Therefore you will need to create a new set (or other collection) to use as an iteration variable, like:
new HashSet(set).forEach(it - > {
set.retainAll(someMapWithSets.get(it))
});
HashSet can be replaced by TreeSet or any other set object that is suitable.
If actually need to change the set while iterating, you will need to use the remove method in Iterator.

difference between arraylist = arraylist and arraylist.addAll(arraylist)

What the difference between assigning an arraylist to another and using method addAll between two arraylists?
1 > arrayList = arrayList; //should assign value of later arrayList to the first.
2> arrayList.addAll(arrayList) //add all the data of the later list to first.
the first completely replaces the data in the list ?
the second one for appending data in the list(if it already has any) ???
if i do arrayList.add(arrayList) without assigning any data to the first list, will it insert any data ?
I did the following code for testing and found results that i do'not really know.
secondList.add("1");
secondList.add("2");
firstList = secondList;
Log.i("CHECK","first list = "+ firstList);
firstList.addAll(secondList);
Log.i("CHECK","Firs list add : "+firstList);
firstList.clear();
firstList.addAll(secondList);
Log.i("CHECK","Firs list add 2 : "+firstList);
Result were :
CHECK: first list = [1, 2]
CHECK: Firs list add : [1, 2, 1, 2]
CHECK: Firs list add 2 : []
i was expecting the last log to have result like : [1,2]
as mentioned in docs.oracle.com
addAll- Appends all of the elements in the specified collection to the
end of this list, in the order that they are returned by the specified
collection's Iterator.
and if there's no data in the list ? then what will addAll DO ?
When you do:
firstList = secondList;
What you are saying is actually "to make firstList and secondList refer to the same list". After the line is executed, there will only be one list and two variables both refer to that list.
This is why after you cleared firstList, secondList lost all the elements as well. They refer to the same thing. This has nothing to do with addAll. When you called firstList.addAll(secondList), you are basically adding appending an empty list to another empty list, which results in an empty list.
when you use arrayList = arrayList2; then you are assigning the reference of arrayList2 in first list. That means they are referring to the same list.
and when you use arrayList.addAll(arrayList2) then they are two different list reference.
Now come back to your code (lets denote firstlist as f, second as s)
secondList.add("1"); // f={}, s = {1}
secondList.add("2"); // f={}, s = {1,2}
firstList = secondList; // f= s = {1, 2}
Log.i("CHECK","first list = "+ firstList); // so printing 1,2
firstList.addAll(secondList);// it is actually adding itself.. so f= s = {1,2,1,2}
Log.i("CHECK","Firs list add : "+firstList);
firstList.clear(); // clear boths as s = f
firstList.addAll(secondList); // as boths are blank so overall blank
Log.i("CHECK","Firs list add 2 : "+firstList);
I learned about this in class, Java doesnt really specify when it passes by value or passes by reference, but for the sake of arrayList's, they are pass by reference unless you specifically create new elements. When you say
firstArray = secondArray;
firstArray gets the memory address of the second array, therefore when you cleared the first array, you actually cleared the memory which the second array also shares.
Good luck!

Frequency of a value in a list of entities

I have an entity named Elementfisa, which contains as values (id,Post,Sarcina). Now, Post(Int Id,String Nume,String Tip) and Sarcina(Int Id,String Desc) are also entities. I have a List of all the elements I added as Elementfisa, and I want to get in a separate list the frequency of every Sarcina that every Elementfisa contains. This is my code right now:
int nr=0;
List<Integer> frecv=new ArrayList<Integer>();
List<Sarcina> sarcini = new ArrayList<>();
List<Elementfisa> efuri=findEFAll();
for (Elementfisa i : efuri)
{
nr=0;
for (Sarcina s : sarcini)
if (s.equals(i.getSarcina()))
nr=1;
if (nr==0)
{
int freq = Collections.frequency(efuri, i.getSarcina());
sarcini.add(i.getSarcina());
frecv.add(freq);
}
}
(findEFAll() returns every element contained in a Hashmap from a repository)
But for some reason, while the sarcini list contains all the Sarcina from every Elementfisa, the frequency list will show 0 on every position. What should I change so every position should show the correct number of occurrences?
You're using Collections.frequency() on efuri, a List<Elementfisa>. But you're passing i.getSarcina() to it, a Sarcina object. A List of Elementfisa cannot possibly contain a Sarcina object, so you get zero. You may have passed the wrong list to the method.
Edit:
To look at all Sarcinas in efuri, you can do this using Java 8 streams:
efuri.stream().map(element -> element.getSarcina())
.collect(Collectors.toList()).contains(i.getSarcina())
Breakdown:
efuri.stream() //Turns this into a stream of Elementfisa
.map(element -> element.getSarcina()) //Turns this into a stream of Sarcina
.collect(Collectors.toList()) //Turn this into a list
.contains(i.getSarcina()) //Check if the list contains the Sarcina
Are you sure you do not need to override equals() of Elementisa? (and hashcode() too). The default Java equals() does not seem to get what you want because it would be checking the identity (not the value) of two Elementisa objects, while in your logic, two such objects with the same values may be considered as equivalent.
For more information on equals(), see
What issues should be considered when overriding equals and hashCode in Java?

Collections.sort() affects all ArrayLists. How do I sort only one list and not the others?

I have three ArrayLists:
One is used for storing user input in the order they were entered, located in the main class.
Second one is exactly the same as the first one, but it is passed into a method called remTrip to be copied and will return the result.
Third one is list1 in the code below, which is the one being processed.
public static ArrayList<String> remTrip( ArrayList<String> a){
//code here
ArrayList<String> list1 = a;
Collections.sort(list1);
//code to remove triplicates from the list and returns the result
}
I wanted to keep the first ArrayList<String> in the same order it was (i.e. {"dog", "cat" , "tiger", "cat", "cat"} ), but apparently the Collections.sort() sorts all of the ArrayLists.
How do I sort only one of the list and not the others?
The problem is not how Collections.sort() works. The problem is that instead of creating a copy of your list, you set the new list equal to your list. This means that they both point to the same object, sorting one will sort the other because they are the same thing. To solve this set list1 to a copy of a instead of setting them equal.
You should be able to do
ArrayList<String> list1 = new ArrayList<String>(a);
Three arralists you're talking about are not 3 different arraylists. They're just three different references to the same arraylist.
What you're doing essentially is -
List list01 = new ArrayList();
List list02 = list01;
List list03 = list01;
What you want is -
List list01 = new ArrayList();
List list02 = new ArrayList(list01);
List list03 = new ArrayList(list01);
But you should remember, this way will give you a copy of your List, not all it's elements. So, if you change one of the elements in your copied List, it will be changed in your original List too.
How to solve it - Hint copy constructor.

Maintain order in ArrayList after sorting and removing duplicates

Hello I'd like to add Strings to an ArrayList and then sort it to remove duplicates. The order should remain the same way I added those Strings though.
What I want: [randomtext, testtext, anothertext]
What I get: [anothertext, randomtext, testtext]
Is this possible or is there an easier way?
ArrayList<String> abc = new ArrayList();
abc.add("randomtext");
abc.add("testtext");
abc.add("anothertext");
abc.add("randomtext");
abc.add("testtext");
abc.add("anothertext");
abc.add("randomtext");
abc.add("testtext");
abc.add("anothertext");
Collections.sort(abc);
for (int i = 1; i < abc.size() ; i++)
{
if(abc.get(i) == abc.get(i-1))
{
abc.remove(i);
i -= 1;
}
}
System.out.print(abc);
The best way is to ensure you don't add duplicates whenever you add something to the list.
if(!myList.contains(item)){
myList.add(item);
}
If you are receiving a List from outside the scope of your method/class, then the easiest may be adding the contents to a LinkedHashSet to eliminate duplicates and then getting them back out. LinkedHashSet maintains order.
LinkedHashSet<String> set = new LinkedHashSet<>();
set.addAll(myList); // assuming myList is List<String>
myList.clear();
myList.addAll(set);
EDIT: My answer is based on your statements (bold by me for emphasis)
Hello I'd like to add Strings to an ArrayList and then sort it to remove duplicates. The order should remain the same way I added those Strings though.
So you're only sorting to remove duplicates. My answer avoids the sort and puts the burden on LinkedHashSet.
Try this (convert your List into LinkedHashSet)
Set<String> a = new LinkedHashSet<String>(abc);
System.out.println(a);

Categories

Resources