从ArrayList中删除重复项

26

我有一个自定义对象的ArrayList。我想要删除重复的条目。

这些对象有三个字段:title, subtitleid。如果一个副标题出现多次,我只需要具有该副标题的第一项(忽略具有该副标题的其余对象)。


2
如果您不想要重复项,一开始使用Set会更简单。 - Peter Lawrey
我使用了TreeSet并在树中添加了子标题。如果add方法返回true,我会将对象添加到另一个ArrayList中。 - Bytecode
14个回答

58

你可以使用自定义比较器将ArrayList的内容放入TreeSet中,如果两个标题相同,则应该返回0。 然后,您可以将Set转换为List,并获得没有“重复项”的List。 这是一个Object的示例,当然你应该使用正确的类和逻辑。

public void removeDuplicates(List<Object> l) {
    // ... the list is already populated
    Set<Object> s = new TreeSet<Object>(new Comparator<Object>() {

        @Override
        public int compare(Object o1, Object o2) {
            // ... compare the two object according to your requirements
            return 0;
        }
    });
            s.addAll(l);
    List<Object> res = Arrays.asList(s.toArray());
}

2
你说得对,我忘记了一行代码,现在代码是正确的。 至于覆盖equals和hashset,作为一般解决方法,我不同意;覆盖它们比我的解决方案更加侵入性,即使您无法修改原始类,我的解决方案也可以工作。 这只是我的个人意见,我接受-1因为发布错误的代码,并且我时刻准备接受批评。 - Riccardo Cossu
1
当然,如果我想要的顺序是自然(通用)顺序,并且我可以修改原始类,我肯定会选择equals、hashCode或者更好的实现Comparable。 但是我曾经看到过业务需求,需要我按照不同于自然顺序的顺序对对象进行排序,使用Comparator总是有效的,即使之前的条件不满足。 总的来说,在这里没有最佳方法,它取决于具体情况。 - Riccardo Cossu
无法保证;我从400个自定义对象中获取到了重复的数据。 - user3402040
1
这个方法可以运行,但是对于CPU和内存来说非常低效,因为它分配了大量的内存,并且由于它是TreeSet,所以它不是本地化的(=高CPU缓存未命中率)。请参见下面我的解决方案,它可以原地排列。 - Agoston Horvath
好的,如果你的输入集非常大,那么这是一个公平的观点;正如先前所述,即使对象没有实现Comparator或唯一性是通过不同于排序的方式计算的(字符串的某些部分可能与排序相关但不适用于唯一性),我的方法仍然有效。 - Riccardo Cossu
显示剩余5条评论

46
List list = (...);

//list may contain duplicates.

//remove duplicates if any
Set setItems = new LinkedHashSet(list);
list.clear();
list.addAll(setItems);

你可能需要重写 "equals()" 方法以便在它们具有相同的副标题(或标题和副标题)时将2个元素视为相等。


但是,如果对象具有用于排序的成员,则无法正常工作,因为即使键相同(如果其他字段不同),哈希也会不同。 - chksr

12
List<Item> result = new ArrayList<Item>();
Set<String> titles = new HashSet<String>();

for(Item item : originalList) {
    if(titles.add(item.getTitle()) {
        result.add(item);
    }
}

Setadd() 方法,如果元素已经存在,则返回 false


你是最棒的 :) 比Riccardo Cossu更好 - user3402040

11
我建议使用Set。由于Set的特性是不允许重复元素存在,您可以使用原始ArrayList创建一个新的Set集合。请参考http://download.oracle.com/javase/6/docs/api/java/util/Set.html
Set myset = new HashSet(myArrayList);

或者,从一开始就使用Set,不要使用ArrayList,因为它无法执行您需要的功能。


在这种情况下,不应该依赖equals和hashcode,因为示例仅在一个属性上寻找相等性。相反,创建一个新列表以获取所需的结果。 - crunchdog
3
如果这个属性是标识对象独特性的关键,那么他为什么不覆盖equals方法,使它仅检查这个属性呢? - Kevin D
是的,如果使用equals()方法很好(例如在某些框架数据集中),并且您想要统一您的ArrayList列表,您可以使用以下代码:myArrayList = new ArrayList(new HashSet(myArrayList)); 但这是重活,只有在未来的代码中必须依赖于列表时(比如Collections.shuffle())才应这样做。 - r00tandy

7

如果我理解正确,你有一个ArrayList<Custom>,我们称其为list。你的Custom类有一个副标题字段,假设有一个getSubtitle()方法返回String。你想保留第一个唯一的副标题并删除所有剩余的副标题副本。以下是如何实现:

Set<String> subtitles = new HashSet<String>();
for (Iterator<Custom> it = list.iterator(); it.hasNext(); ) {
    if (!subtitles.add(it.next().getSubtitle())) {
        it.remove();
    }
}

7
您可以使用O(n^2)的解决方案:使用list.iterator()遍历列表一次,并在每次迭代时再次迭代以检查是否有重复项。如果有——调用iterator.remove()。这种方法的变体是使用Guava的Iterables.filter(list, predicate),其中过滤逻辑在谓词中。

另一种方式(也许更好)是定义equals(..)hashCode(..)方法来处理您的自定义相等逻辑,然后简单地构造一个new HashSet(list)。这将清除重复项。

4

这个函数可以从一个集合中移除重复项,如果这个集合是有序的,则会保留它们的顺序。在大多数情况下,这个函数的效率足够高。

public static <I, T extends Collection<I>> T removeDuplicates(T collection)
{
    Set<I> setItems = new LinkedHashSet<I>(collection);
    collection.clear();
    collection.addAll(setItems);

    return collection;
}

4

Java8更新:

使用Java8流,您也可以轻松地完成此操作。

ArrayList<String> deduped;
deduped = yourArrayList.stream()
             .distinct()
             .collect(Collectors.toCollection(ArrayList::new));

这种方法相比于用 ArrayListSetArrayList 的方式,具有保持顺序的优势。


2
使用Collections.sort()进行排序,然后使用简单的for循环来捕获重复项,例如:
Collections.sort(myList);
A previous = null;
for (A elem: myList) {
    if (elem.compareTo(previous) == 0) continue;
    previous = elem;

    [... process unique element ...]
}

这意味着您需要在类型A中实现Comparable。

更高效但不太灵活;对于大型输入集可能更好。 - Riccardo Cossu

1
private static List<Integer> removeDuplicates(List<Integer> list) {
    ArrayList<Integer> uniqueList = new ArrayList<Integer>();
    for (Integer i : list) {
        if (!inArray(i, uniqueList)) {
            uniqueList.add(i);
        }
    }

    return uniqueList;
}

private static boolean inArray(Integer i, List<Integer> list) {
    for (Integer integer : list) {
        if (integer == i) {
            return true;
        }
    }

    return false;
}

你的解决方案仅适用于 Integer 列表。楼主明确指出该列表包含自定义对象。 - Laf
好的.. 用整数替换那些对象并更改´inArray´中的条件... - urSus

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