如何从ArrayList中删除重复元素?

574
我有一个 ArrayList<String>,我想从中删除重复的字符串。如何做到这一点?
40个回答

21

可以在不使用HashSet另一个arraylist的情况下从arraylist中删除重复项。

尝试这段代码...

    ArrayList<String> lst = new ArrayList<String>();
    lst.add("ABC");
    lst.add("ABC");
    lst.add("ABCD");
    lst.add("ABCD");
    lst.add("ABCE");

    System.out.println("Duplicates List "+lst);

    Object[] st = lst.toArray();
      for (Object s : st) {
        if (lst.indexOf(s) != lst.lastIndexOf(s)) {
            lst.remove(lst.lastIndexOf(s));
         }
      }

    System.out.println("Distinct List "+lst);

输出结果为

Duplicates List [ABC, ABC, ABCD, ABCD, ABCE]
Distinct List [ABC, ABCD, ABCE]

它很慢,而且你可能会遇到ConcurrentModificationException异常。 - maaartinus
@maaartinus 你试过那段代码吗?它不会产生任何异常。而且它运行速度很快。在发布之前我已经试过了这段代码。 - CarlJohn
5
你说得对,如果你遍历数组而不是列表,代码确实没有问题。但是,这个方法处理数百万个元素的速度非常慢。试着和下面的代码进行比较:ImmutableSet.copyOf(lst).toList() - maaartinus
这是我在面试中被问到的问题的答案。如何在不使用set的情况下从ArrayList中删除重复的值。谢谢。 - Aniket Paul
在内部,indexOf 使用 for 循环迭代 lst - Patrick M
是的,这里的时间复杂度将会非常庞大且不必要。 - Szymon Kowaliński

12

可能有点过度,但我喜欢这种孤立的问题。 :)

这段代码使用一个临时Set(用于唯一性检查),但是直接从原始列表中删除元素。由于在ArrayList内部进行元素移除可能会引起大量的数组复制,因此避免使用remove(int)方法。

public static <T> void removeDuplicates(ArrayList<T> list) {
    int size = list.size();
    int out = 0;
    {
        final Set<T> encountered = new HashSet<T>();
        for (int in = 0; in < size; in++) {
            final T t = list.get(in);
            final boolean first = encountered.add(t);
            if (first) {
                list.set(out++, t);
            }
        }
    }
    while (out < size) {
        list.remove(--size);
    }
}

在此同时,这里是LinkedList的版本(更加优美!):
public static <T> void removeDuplicates(LinkedList<T> list) {
    final Set<T> encountered = new HashSet<T>();
    for (Iterator<T> iter = list.iterator(); iter.hasNext(); ) {
        final T t = iter.next();
        final boolean first = encountered.add(t);
        if (!first) {
            iter.remove();
        }
    }
}

使用标记接口来提供一个统一的List解决方案:
public static <T> void removeDuplicates(List<T> list) {
    if (list instanceof RandomAccess) {
        // use first version here
    } else {
        // use other version here
    }
}

编辑:我想泛型相关的内容在这里并没有增加任何价值... 哦,好吧。 :)


1
为什么在参数中使用ArrayList?为什么不只用List?那样行不行? - Shervin Asgari
一个List绝对可以作为第一个方法的输入参数。但是,该方法被优化用于随机访问列表,例如ArrayList,因此如果传递LinkedList,则性能将较差。例如,在LinkedList中设置第n个元素需要O(n)时间,而在随机访问列表(例如ArrayList)中设置第n个元素需要O(1)时间。再次强调,这可能是过度设计...如果您需要这种专业代码,它将希望处于孤立的情况下。 - volley

12
public static void main(String[] args){
    ArrayList<Object> al = new ArrayList<Object>();
    al.add("abc");
    al.add('a');
    al.add('b');
    al.add('a');
    al.add("abc");
    al.add(10.3);
    al.add('c');
    al.add(10);
    al.add("abc");
    al.add(10);
    System.out.println("Before Duplicate Remove:"+al);
    for(int i=0;i<al.size();i++){
        for(int j=i+1;j<al.size();j++){
            if(al.get(i).equals(al.get(j))){
                al.remove(j);
                j--;
            }
        }
    }
    System.out.println("After Removing duplicate:"+al);
}

由于最后的j--,此实现在列表中不返回任何元素。 - neo7
1
这个实现非常好,没有任何问题。对于这个任务,我只使用了一个ArrayList。所以这个答案是完全正确的。在给出负面反馈之前,您应该添加测试用例,以便每个人都可以理解结果。谢谢,Manash。 - Manash Ranjan Dakua

6

如果你正在使用模型类型List<T>/ArrayList<T>,希望这可以帮到你。

以下是我的代码,没有使用任何其他数据结构,如set或hashmap。

for (int i = 0; i < Models.size(); i++){
for (int j = i + 1; j < Models.size(); j++) {       
 if (Models.get(i).getName().equals(Models.get(j).getName())) {    
 Models.remove(j);
   j--;
  }
 }
}

谢谢!我想知道为什么其他人在不必要的情况下还要使用额外的数据结构。 - undefined

5
如果您愿意使用第三方库,您可以在Eclipse Collections(曾经的GS Collections)中使用distinct()方法。
ListIterable<Integer> integers = FastList.newListWith(1, 3, 1, 2, 2, 1);
Assert.assertEquals(
    FastList.newListWith(1, 3, 2),
    integers.distinct());

使用 distinct() 的优势在于它保留了原始列表的顺序,并且保留了每个元素的第一个出现,而不是将列表转换成 Set 再转回 List。它通过同时使用 Set 和 List 来实现。
MutableSet<T> seenSoFar = UnifiedSet.newSet();
int size = list.size();
for (int i = 0; i < size; i++)
{
    T item = list.get(i);
    if (seenSoFar.add(item))
    {
        targetCollection.add(item);
    }
}
return targetCollection;

如果您无法将原始List转换为Eclipse Collections类型,可以使用ListAdapter获取相同的API。

MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();

注意:我是 Eclipse Collections 的贡献者。


3
如果您想保留顺序,则最好使用LinkedHashSet。因为如果您想通过迭代将此列表传递给插入查询,那么顺序将得到保留。
试试这个。
LinkedHashSet link=new LinkedHashSet();
List listOfValues=new ArrayList();
listOfValues.add(link);

当您想返回一个列表而不是一个集合时,这种转换非常有帮助。


3
这三行代码可以从ArrayList或任何集合中删除重复元素。
List<Entity> entities = repository.findByUserId(userId);

Set<Entity> s = new LinkedHashSet<Entity>(entities);
entities.clear();
entities.addAll(s);

2

代码:

List<String> duplicatList = new ArrayList<String>();
duplicatList = Arrays.asList("AA","BB","CC","DD","DD","EE","AA","FF");
//above AA and DD are duplicate
Set<String> uniqueList = new HashSet<String>(duplicatList);
duplicatList = new ArrayList<String>(uniqueList); //let GC will doing free memory
System.out.println("Removed Duplicate : "+duplicatList);

注意: 肯定会有内存开销。


2
当您填充ArrayList时,为每个元素使用条件。例如:
    ArrayList< Integer > al = new ArrayList< Integer >(); 

    // fill 1 
    for ( int i = 0; i <= 5; i++ ) 
        if ( !al.contains( i ) ) 
            al.add( i ); 

    // fill 2 
    for (int i = 0; i <= 10; i++ ) 
        if ( !al.contains( i ) ) 
            al.add( i ); 

    for( Integer i: al )
    {
        System.out.print( i + " ");     
    }

我们将得到一个数组{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}。

2

您可以使用嵌套循环如下:

ArrayList<Class1> l1 = new ArrayList<Class1>();
ArrayList<Class1> l2 = new ArrayList<Class1>();

        Iterator iterator1 = l1.iterator();
        boolean repeated = false;

        while (iterator1.hasNext())
        {
            Class1 c1 = (Class1) iterator1.next();
            for (Class1 _c: l2) {
                if(_c.getId() == c1.getId())
                    repeated = true;
            }
            if(!repeated)
                l2.add(c1);
        }

完美 - 只是在“如果(!repeated)l2.add(c1);”后面的内部循环中缺少“repeated = false;”,否则它会返回一个短列表。 - kfir

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