Reputation: 19854
Why cannot I retrieve an element from a HashSet?
Consider my HashSet
containing a list of MyHashObjects
with their hashCode()
and equals()
methods overridden correctly. I was hoping to construct a MyHashObject
myself, and set the relevant hash code properties to certain values.
I can query the HashSet
to see if there "equivalent" objects in the set using the contains()
method. So even though contains()
returns true for the two objects, they may not be ==
true.
How come then there isn’t any get()
method similar to how the contains()
works?
What is the thinking behind this API decision?
Upvotes: 73
Views: 321889
Reputation: 161
You can use HashMap<MyHashObject, MyHashObject>
instead of HashSet<MyHashObject>
.
Calling containsKey()
on your "reconstructed" MyHashObject
will first hashCode()
- check the collection, and if a duplicate hashcode is hit, finally equals()
- check your "reconstructed" against the original, at which you can retrieve the original using get()
Complexity is O(1) but the downside is you will likely have to override both equals()
and hashCode()
methods.
Upvotes: 16
Reputation: 22884
If you know what element you want to retrieve, then you already have the element. The only question for a Set to answer, given an element, is whether it contains()
it or not.
If you want to iterator over the elements, just use a Set.iterator()
.
It sounds like what you're trying to do is designate a canonical element for an equivalence class of elements. You can use a Map<MyObject,MyObject>
to do this. See this Stack Overflow question or this one for a discussion.
If you are really determined to find an element that .equals()
your original element with the constraint that you must use the HashSet
, I think you're stuck with iterating over it and checking equals()
yourself. The API doesn't let you grab something by its hash code. So you could do:
MyObject findIfPresent(MyObject source, HashSet<MyObject> set)
{
if (set.contains(source)) {
for (MyObject obj : set) {
if (obj.equals(source))
return obj;
}
}
return null;
}
It is brute-force and O(n) ugly, but if that's what you need to do...
Upvotes: 58
Reputation: 11
First of all, convert your set to an array. Then, get the item by indexing the array.
Set uniqueItem = new HashSet();
uniqueItem.add("0");
uniqueItem.add("1");
uniqueItem.add("0");
Object[] arrayItem = uniqueItem.toArray();
for(int i = 0; i < uniqueItem.size(); i++) {
System.out.println("Item " + i + " " + arrayItem[i].toString());
}
Upvotes: 1
Reputation: 116392
If you want to have a reference to the real object using the same performance as HashSet, I think the best way is to use HashMap.
Example (in Kotlin, but similar in Java) of finding an object, changing some field in it if it exists, or adding it in case it doesn't exist:
val map = HashMap<DbData, DbData>()
val dbData = map[objectToFind]
if(dbData!=null){
++dbData.someIntField
}
else {
map[dbData] = dbData
}
Upvotes: 0
Reputation: 9
If I know for sure in my application that the object is not used in search in any of the list or hash data structure and not used equals method elsewhere except the one used indirectly in hash data structure while adding. Is it advisable to update the existing object in set in equals method. Refer the below code. If I add the this bean to HashSet, I can do group aggregation on the matching object on key (id). By this way I am able to achieve aggregation functions such as sum, max, min, ... as well. If not advisable, please feel free to share me your thoughts.
public class MyBean {
String id,
name;
double amountSpent;
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj!=null && obj instanceof MyBean ) {
MyBean tmpObj = (MyBean) obj;
if(tmpObj.id!=null && tmpObj.id.equals(this.id)) {
tmpObj.amountSpent += this.amountSpent;
return true;
}
}
return false;
}
}
Upvotes: 1
Reputation: 512
One of the easiest ways is to convert to Array:
for(int i = 0; i < set.size(); i++) {
System.out.println(set.toArray()[i]);
}
Upvotes: 0
Reputation: 487
The idea that you need to get the reference to the object that is contained inside a Set object is common. It can be archived by 2 ways:
Use HashSet as you wanted, then:
public Object getObjectReference(HashSet<Xobject> set, Xobject obj) {
if (set.contains(obj)) {
for (Xobject o : set) {
if (obj.equals(o))
return o;
}
}
return null;
}
For this approach to work, you need to override both hashCode() and equals(Object o) methods In the worst scenario we have O(n)
Second approach is to use TreeSet
public Object getObjectReference(TreeSet<Xobject> set, Xobject obj) {
if (set.contains(obj)) {
return set.floor(obj);
}
return null;
}
This approach gives O(log(n)), more efficient. You don't need to override hashCode for this approach but you have to implement Comparable interface. ( define function compareTo(Object o)).
Upvotes: 4
Reputation: 666
If you could use List
as a data structure to store your data, instead of using Map
to store the result in the value of the Map, you can use following snippet and store the result in the same object.
Here is a Node class:
private class Node {
public int row, col, distance;
public Node(int row, int col, int distance) {
this.row = row;
this.col = col;
this.distance = distance;
}
public boolean equals(Object o) {
return (o instanceof Node &&
row == ((Node) o).row &&
col == ((Node) o).col);
}
}
If you store your result in distance variable and the items in the list are checked based on their coordinates, you can use the following to change the distance to a new one with the help of lastIndexOf method as long as you only need to store one element for each data:
List<Node> nodeList;
nodeList = new ArrayList<>(Arrays.asList(new Node(1, 2, 1), new Node(3, 4, 5)));
Node tempNode = new Node(1, 2, 10);
if(nodeList.contains(tempNode))
nodeList.get(nodeList.lastIndexOf(tempNode)).distance += tempNode.distance;
It is basically reimplementing Set
whose items can be accessed and changed.
Upvotes: 0
Reputation: 26821
If you know the order of elements in your Set, you can retrieve them by converting the Set to an Array. Something like this:
Set mySet = MyStorageObject.getMyStringSet();
Object[] myArr = mySet.toArray();
String value1 = myArr[0].toString();
String value2 = myArr[1].toString();
Upvotes: 6
Reputation: 26502
It sounds like you're essentially trying to use the hash code as a key in a map (which is what HashSets do behind the scenes). You could just do it explicitly, by declaring HashMap<Integer, MyHashObject>
.
There is no get
for HashSets because typically the object you would supply to the get
method as a parameter is the same object you would get back.
Upvotes: 7