Storing arrays in Set and avoiding duplicates
HashSet<String[]> boog = new HashSet<String[]>();
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a"开发者_JAVA百科, "b", "d"});
results in
[a, b, c]
[a, b, d]
[a, b, c]
where [a,b,c]
is repeated, so the hash function is not working as expected. How would I go about overriding the Hash method for String arrays. Or for that matter, a generic array? Is there a better way to accomplish what I'm trying to do?
You can't. arrays use the default identity-based Object.hashCode() implementation and there's no way you can override that. Don't use Arrays as keys in a HashMap / HashSet!
Use a Set of Lists instead.
The "better way" is to use collections. Use a List
instead of a String[]
:
// TreeSet and other comparison based sets will not work as ArrayList does not implement Comparable
Set<List<String>> boog = new HashSet<>();
boog.add(Arrays.asList("a", "b", "c"));
boog.add(Arrays.asList("a", "b", "c"));
boog.add(Arrays.asList("a", "b", "d"));
System.out.println(boog.size()); // 2
Edit
If you absolutely needed to use arrays as keys, you could build a transparent wrapper around each key and put that in the map. Some libraries help you with that. For example, here's how you could do a Set<String[]>
using Trove:
Set<String[]> boog = new TCustomHashSet<String[]>(new ArrayHashingStrategy());
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a", "b", "d"});
System.out.println(boog.size()); // 2
//...
public class ArrayHashingStrategy extends HashingStrategy<Object[]> {
public int computeHashCode(Object[] array) {
return Arrays.hashCode(array);
}
public boolean equals(Object[] arr1, Object[] arr2) {
return Arrays.equals(arr1, arr2);
}
}
hashCode()
of arrays uses the default implementation, which doesn't take into account the elements, and you can't change that.
You can use a List
instead, with a hashCode()
calculated based on the hashcodes of its elements. ArrayList
(as most implementations) uses such function.
Alternatively (but less preferable, unless you are forced somehow to use arrays), you can use a 'special' HashSet
where instead of invoking key.hashCode()
invoke Arrays.hashCode(array)
. To implement that extend HashMap
and then use Collections.newSetFromMap(map)
Actually, you can. You can use TreeSet
with provided Comparator
.
In your case it will be something like:
Set<String[]> boog = new TreeSet<>((o1, o2) -> {
for (int i = 0; i < o1.length; i++){
int cmp = o1[i].compareTo(o2[i]);
if (cmp != 0) {
return cmp;
}
}
return o1.length - o2.length;
});
Under the hood it will looks like alphabetic sorted tree.
You are actually using the default hashCode
method returning different values for all your different arrays!
The best way to solve this is either to use a Collection
(such as a List
or a Set
) or to define your own wrapper class such as:
public class StringArray {
public String[] stringArray;
[...] // constructors and methods
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
for(String string : stringArray){
result = prime * result + ((string == null) ? 0 : string.hashCode());
}
}
}
This class actually uses pretty much the same hashCode
method as the one for the List
.
You now handle:
HashSet<StringArray> boog = new HashSet<StringArray>();
You can use TreeSet
.
TreeSet
uses Comparable.compareTo()
or Comparator.compare()
instead of hashCode()
and equals()
to compare elements. It can be specified in the constructor.
public static void main(String[] args) {
Set<String[]> boog = new TreeSet<>(Arrays::compare);
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a", "b", "c"});
boog.add(new String[]{"a", "b", "d"});
for (String[] e : boog)
System.out.println(Arrays.toString(e));
}
output:
[a, b, c]
[a, b, d]
Arrays::compare
is a Comparator
that compares arrays in lexicographical order.
精彩评论