67

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DJ180
  • 18,724
  • 21
  • 66
  • 117

10 Answers10

54

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...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
andersoj
  • 22,406
  • 7
  • 62
  • 73
  • 10
    But it's not the element that I want. I want the element in the set, not the one that I have that is hashCode equivalent. Does that make sense? – DJ180 Aug 18 '12 at 00:10
  • 1
    no, what if there are 3 different objects with the same hashcode? which of these objects do you want? – Absurd-Mind Aug 18 '12 at 00:14
  • 5
    It's a HashSet. I presumed only one object would exist in the HashSet with that hash code? – DJ180 Aug 18 '12 at 00:20
  • 1
    Suggest you use an Interner, as described here: http://stackoverflow.com/a/3008667/83695 – andersoj Aug 18 '12 at 00:24
  • 2
    contains(Object): "returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e))". As a result from this, hashCode is only a helper to find the object faster. – Absurd-Mind Aug 18 '12 at 00:28
  • nicely explained. – Tahir Hussain Mir Nov 14 '18 at 08:47
15

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.

Johnny Five
  • 987
  • 1
  • 14
  • 29
MVM
  • 151
  • 1
  • 5
7

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.

Jon Newmuis
  • 25,722
  • 2
  • 45
  • 57
  • I think MVM's answer is better (though it has fewer votes). The difference is that if you use the hash code as the index in HashMap, you will lose data if there is a collision (when two MyHashObjects have the same hash code). If you use HashMap, two objects with the same hash code may share a bucket but not collide. – pabl0rg Jun 13 '16 at 20:33
  • 4
    If someone wants to use a `get` method for HashSets, it can be the case that `hashcode` and `equals` method might not use **ALL** the attributes of an object to calculate and process the results. Maybe remaining attributes contain some other values. – vkrishna17 Jul 04 '16 at 20:38
6

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();
IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147
4

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:

  1. 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)

  1. 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)).

svarog
  • 9,477
  • 4
  • 61
  • 77
V.Tran
  • 457
  • 1
  • 5
  • 13
1

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;
    }
}
lue
  • 449
  • 5
  • 16
1

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());
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • Documentation: [Set](https://docs.oracle.com/javase/8/docs/api/java/util/Set.html). [HashSet](https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html) – Peter Mortensen Dec 16 '22 at 00:07
1

One of the easiest ways is to convert to Array:

for(int i = 0; i < set.size(); i++) {
    System.out.println(set.toArray()[i]);
}
lue
  • 449
  • 5
  • 16
Crenguta S
  • 526
  • 9
  • 11
0

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.

Habib Karbasian
  • 556
  • 8
  • 18
0

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
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
android developer
  • 114,585
  • 152
  • 739
  • 1,270