在Java中从集合中移除重复元素

11

我有一组字符串数组,想要从中删除重复的元素...

    String[] arr1 = {"a1","b1"};
    String[] arr2 = {"a2","b2"};
    Set<String[]> mySet = new HashSet<String[]>();
    mySet.add(arr1);
    mySet.add(arr2);
    mySet.add(new String[] {"a1","b1"});
    System.out.print(mySet.size());

目前,mySet看起来像这样:

[{"a1","b1"},{"a2","b2"},{"a1","b1"}]

但我想要这样:

[{"a1","b1"},{"a2","b2"}]

我知道一些方法...

  1. 每次我需要运行内部循环并检查其是否重复。
  2. 我能否重写集合的行为?(哈希码或等同性)(我不知道如何...)
  3. 我需要更改数据结构吗?(linkedhashset或list或任何其他适合此情况的数据结构?)

好的..谢谢...Sotirios Delimanolis - Manan Shah
我正在阅读并尝试所有的解决方案...一旦我尝试完所有方法,我肯定会接受答案... - Manan Shah
8个回答

11

数组继承自Object,不会覆盖hashCodeequals方法。一个HashSet使用Map实现,Map再使用hashCodeequals来避免重复元素。

您可以使用带有自定义ComparatorTreeSet来比较String数组的相等性。

Set<String[]> mySet = new TreeSet<>(new Comparator<String[]>() {

  @Override
  public int compare(String[] o1, String[] o2) {
    return Arrays.equals(o1, o2)? 0 : Arrays.hashCode(o1) - Arrays.hashCode(o2);
  }

});

请注意,这只会忽略具有相同对应元素的重复数组。如果元素的顺序不同,则不会被视为重复。

如果您想要丢弃无序的重复项,例如{a1,b1}{b1,a1},请使用以下内容:

@Override
public int compare(String[] o1, String[] o2) {
    int comparedHash = o1.hashCode() - o2.hashCode();
    if(o1.length != o2.length) return comparedHash;
    List<String> list = Arrays.asList(o1);
    for(String s : o2) {
        if(!list.contains(s)) return comparedHash;
    }
    return 0;
}

谢谢,它有效了。我已经接受了答案。 - Manan Shah
那个 TreeSet 无法正常工作。Comparator 必须实现全序;特别地,compare(o1, o2) 的结果必须是 compare(o2, o1) 的相反数。 - user2357112
@user2357112,感谢您指出错误。我已经修改了代码,使用hashCode进行比较,以便compare(o1, o2) + compare(o2, o1) == 0。顺便问一下,“不正常工作”是指TreeSet的迭代器可能会漏掉一些元素或者搜索操作(contains)可能会失败,因为所有节点最终都会在树的实现的右侧吗? - c.P.u1
那仍然不会起作用。由于“相等”的数组通常具有不同的哈希码,您可能会陷入这样一种情况:比较器认为o1 < o2o2 < o3o3 == o1。至于“无法正常工作”,很可能插入和搜索都无法检测到元素已经存在。 - user2357112
@user2357112,我同意第二个解决方案无法使用contains(array),除非array是对添加到Set的相同array的引用。尽管如此,additerator仍能正常工作。而第一个解决方案也适用于搜索操作。 - c.P.u1

10
array 的哈希码与 array 内容无关(它继承了 Object 的哈希码,使用的是数组的引用)。
然而,List 可以实现你想要的功能。它使用基于 List 元素的哈希码。来自 Java 文档的链接:http://docs.oracle.com/javase/7/docs/api/java/util/List.html#hashCode%28%29
int hashCode = 1;
for (E e : list)
    hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

抱歉,我的语言模型只支持以英文作为输入和输出。
List<String> list1 = Arrays.asList("a1","b1");
List<String> list2 = Arrays.asList("a2","b2");
Set<List<String>> mySet = new HashSet<List<String>>();
mySet.add(list1);
mySet.add(list2);
mySet.add(Arrays.asList("a1","b1"));   // duplicate won't be added
System.out.print(mySet.size());        // size = 2

谢谢伙计。这个解决方案也可行。(:( 抱歉,我只能接受一个答案...) - Manan Shah
+1 是为了你创造性和不同的问题解决方式... 也是为了你的描述和链接... - Manan Shah
1
你不需要显式地为 Arrays.asList 创建一个数组;你可以直接使用 Arrays.asList("a1", "b1") - user2357112
哇,不知怎么的我从来没有意识到。谢谢! - bcorso
@MananShah,感谢user2357112的建议,你可能会发现这个解决方案更适合你的需求。 - bcorso
显示剩余2条评论

3

数组使用基于身份的 Object.hashCode() 实现,没有简单的方法来检查它们是否相等。如果您仍然想继续解决问题,我建议您使用带有 Comparator 的 TreeSet

虽然不是完美无缺的方法,但您应该能够从我的示例中构建出一个优化的解决方案。

public static void main(String[] args) {
          String[] arr1 = {"a1","b1"};
            String[] arr2 = {"a2","b2"};
            Set<String[]> mySet = new TreeSet<String[]>(new ArrayComparator());
            mySet.add(arr1);
            mySet.add(arr2);
            mySet.add(new String[] {"a1","b1"});
            System.out.println(mySet.size());
            for(String[] aa: mySet){
                System.out.println(aa[0]+" , "+aa[1]);
            }
    }
}

 class ArrayComparator implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        String[] ar1 =(String[]) o1;
        String[] ar2 =(String[]) o2;
        if(ar1.length!=ar2.length){
            return -1;
        }
        for(int count=0;count<ar1.length;count++){
            if(!ar1[count].equals(ar2[count])){
                return -1;
            }
        }
        return 0;
    }

谢谢,我已经尝试了,你的解决方案对我很有效。 - Manan Shah
+1 for ArrayComparator.... - Manan Shah
抱歉,我只能接受一个答案……但是谢谢伙计。 - Manan Shah

2
为什么不使用列表实现呢?list.equals将比较每个列表中的元素并确定相等性。
List<String> arr1 = new ArrayList<String>();
arr1.add("a1");
arr1.add("b1");
List<String> arr2 = new ArrayList<String>();
arr2.add("a2");
arr2.add("b2");
Set<List<String>> mySet = new HashSet<List<String>>();
mySet.add(arr1);
mySet.add(arr2);

List<String> arr3 = new ArrayList<String>();
arr3.add("a1");
arr3.add("b1");
mySet.add(arr3);
System.out.print(mySet.size());

您建议重写equals和hashcode方法。HashSet由使用hashcode函数作为其键的哈希映射支持。因此,实际上需要重写hashcode来表示您的equals条件。

但是这样会有一个问题。我相信String和因此String[]被声明为final,所以您无法扩展它们 :(


谢谢,+1个赞同这个想法(使用List<string>代替string[])。 - Manan Shah

2

你可以创建一个类来代替使用字符串数组,代码如下:

public class String1 implements Comparable<String1>{

String str1;
String str2;

public String1(String a, String b) {
    str1 = a;
    str2 = b;
}

public String getStr1() {
    return str1;
}
}

public String getStr2() {
    return str2;
}

@Override
public String toString() {
    return "String1 [str1=" + str1 + ", str2=" + str2
            + "]";
}

@Override
public int compareTo(String1 o) {
    if(str1.contentEquals(o.getStr1()) && str2.contentEquals(o.getStr2()))  return 0 ; 
    return 1;

}


}

然后你可以使用类对象代替字符串。将HashSet替换为TreeSet。像这样。

     String1 arr1 =new String1("a1","b1");
     String1 arr2 =new String1("a2","b2");
     Set<String1> mySet = new TreeSet<String1>();
     mySet.add(arr1);
     mySet.add(arr2);
     mySet.add(new String1("a1","b1"));
     System.out.print(mySet.size());
     System.out.println(mySet.toString());

所以这个功能将会进行排序并检查重复项。


1
感谢Pankaj的分享,这个干净的示例值得点赞。 - Manan Shah

2

尝试运行下面这段代码.............

import java.util.HashSet;
import java.util.Set;

public class setDemo {
static Set<String[]> mySet = new HashSet<String[]>();
static Set tempSet = new HashSet();
public static void main(String[] args) {

      String[] arr1 = {"a1","b1"};
      String[] arr2 = {"a2","b2"};

        addObject(arr1);
        addObject(arr2);
        addObject(new String[] {"a1","b1"});
        System.out.print(mySet.size());
       // System.out.println(tempSet);
}
public static void addObject(String[] o){
    StringBuffer sb = new StringBuffer();
    for(Object obj:o){
        sb.append(obj.toString());
    }
    if(!tempSet.contains(sb.toString())){
        tempSet.add(sb.toString());
        mySet.add(o);
    }
}
}

谢谢Ravi...对于StringBuffer的+1,它是一种与传统(覆盖行为)方法非常不同的问题解决方法。 - Manan Shah
这段代码是多个数组对象输入,且允许不同大小的数组。 - Ravi Parsania
请尽量不要使用StringBuffer,除非必须,因为它已经被StringBuilder替代了将近十年。 - Peter Lawrey
Peter Lawrey,谢谢你,你是对的。StringBuffer 已经过时了,这是我的错误。 - Ravi Parsania

1
尝试像这样做...
public static void main(String... args) {
        String[] arr1 = {"a1","b1"};
        String[] arr2 = {"a2","b2"};
        Set<String[]> mySet = new HashSet<String[]>();
        mySet.add(arr1);
        mySet.add(arr2);
        String str[] =new String[] {"a1","b1"}; 
        long t1 = System.nanoTime();
        boolean b =checkContains(str,mySet);
        long t2=System.nanoTime();
        long t = t2-t1;
        System.out.println("time taken : " + t );
        System.out.println(b);
        
        if(!b)
        {
            mySet.add(str);
         
        }

        
    }

    public static boolean checkContains(String[] str, Set mySet)
    {  
        Iterator it = mySet.iterator();
        while(it.hasNext())
        {
            String[] arr = (String[])it.next();
            if(arr[0].equals(str[0]) && arr[1].equals(str[1]) )
            {
                return true;
            }
        }
    
        
         return false;
    }

OP :

耗时:184306

true

(保留了HTML标签)

+1 对于不覆盖任何行为的解决方案... - Manan Shah
1
欢迎您...但我必须承认...与其他方法相比,这种方法更慢(效率更低...)我已经检查了时间差异... - TheLostMind

1

在这里,你可以使用Set<SomeClass>,并重写SomeClass类的哈希和相等方法来解决问题。


你能给一个示例或一些链接作为参考吗? - Manan Shah
感谢Alpesh的建议,使用类的想法很棒!+1 - Manan Shah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接