交叉比较ArrayList元素并删除重复项。

3
我有一个可能包含MyObject重复项的ArrayList<MyObject>,我需要从列表中删除它们。如何以一种方式做到只遍历一次列表,而不是使用两个循环来交叉检查每个项目?只需比较A:B就足够了,因为我不想再比较B:A,因为我已经这样做过了。
此外,我能否在循环时直接从列表中移除重复项?或者那会破坏列表和我的循环吗?
编辑:好吧,我忘了一个重要部分,查看第一个答案:对于MyObject重复不仅意味着Java中的Object.equals(Object),而且我需要使用自己的算法比较对象,因为MyObject的相等性是使用特殊方式检查对象的字段计算的,我需要实现这一点!
此外,我不能简单地在MyObject中覆盖equals,因为有几个不同的算法实现不同的策略来检查两个MyObject的相等性-例如,存在一个简单的HashComparer和一个更复杂的EuclidDistanceComparer,两者都是实现不同算法的AbstractComparers,如下所示:public abstract boolean isEqual(MyObject obj1, MyObject obj2);

使用自定义比较器的TreeSet应该可以解决问题。 - ante
5个回答

4

创建一个集合,如果顺序不重要,它会自动为您删除重复项。

Set<MyObject> mySet = new HashSet<MyObject>(yourList);

4
排序列表后,重复元素就会相邻,这使得它们易于识别和删除。只需遍历该列表,记住前一个项目的值,以便将其与当前项目进行比较。如果它们是相同的,则删除当前项目。
如果您使用普通的for循环遍历该列表,则可以控制当前位置。这意味着,当您删除一个项目时,可以将位置减小(n--),以便下一次循环将访问同一位置(现在是下一个项目)。
需要在排序中提供自定义比较吗?这并不难:
Collections.sort(myArrayList, new Comparator<MyObject>() {

    public int compare(MyObject o1, MyObject o2) {
        return o1.getThing().compareTo(o2.getThing());
    }
});

我编写了这个示例,其中getThing().compareTo()代表你想做的比较两个对象的操作。你必须返回一个整数,如果它们相同则为零,如果o1大于o2则大于1,如果o1小于o2则为-1。如果getThing()返回一个StringDate,那么你就可以使用它们自带的compareTo方法了。但是你也可以在自定义的Comparator中加入任何你需要的代码。


这取决于 o1 是否应该在 o2 之前。请参阅 Comparator 的 javadoc。 - ante
@ante - 这里所需的仅是相等的对象彼此相邻。请看问题! :) - Daniel Earwicker
@ante 我理解这个概念 - 只是我无法决定一个对象应该放在另一个对象之前还是之后,因为在我的情况下排序的问题是无效的。只有相等或不相等 - 我无法回答“之前还是之后”的问题。 - F.P
@Florian Peschka - 你在对象之间进行了什么比较? - Daniel Earwicker
如果你已经有了一个框架,其中只有一个布尔值 isEqual,而且你不想重写它,那么你可以编写一个自定义的比较函数,首先检查 isEqual,如果为真则返回0,否则回退到比较两个对象的 System.identityHashCode 值(正如 @ante 所建议的)。 - Daniel Earwicker
显示剩余7条评论

2

实例化一个基于集合的新HashSet。不要忘记为MyObject实现equals和hashcode。

祝好运!


2

如果对象的顺序不重要

如果顺序不重要,您可以将列表元素放入 Set 中:

Set<MyObject> mySet = new HashSet<MyObject>(yourList);

重复内容将会被自动删除。

如果对象顺序很重要

如果顺序很重要,那么您可以手动检查重复内容,例如使用以下代码片段:

// Copy the list.
ArrayList<String> newList = (ArrayList<String>) list.clone();

// Iterate
for (int i = 0; i < list.size(); i++) {
    for (int j = list.size() - 1; j >= i; j--) {
        // If i is j, then it's the same object and don't need to be compared.
        if (i == j) {
            continue;
        }
        // If the compared objects are equal, remove them from the copy and break
        // to the next loop
        if (list.get(i).equals(list.get(j))) {
            newList.remove(list.get(i));
            break;
        }
        System.out.println("" + i + "," + j + ": " + list.get(i) + "-" + list.get(j));
    }
}

这将删除所有重复项,只保留最后一个重复值作为原始条目。此外,它仅检查每个组合一次。

使用Java 8

Java Streams使它变得更加优雅:

List<Integer> newList = oldList.stream()
    .distinct()
    .collect(Collectors.toList());

如果您需要根据自己的定义将两个对象视为相等,则可以执行以下操作:
public static <T, U> Predicate<T> distinctByProperty(Function<? super T, ?> propertyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(propertyExtractor.apply(t));
}

(by Stuart Marks)

然后你可以这样做:

List<MyObject> newList = oldList.stream()
    .filter(distinctByProperty(t -> {
        // Your custom property to use when determining whether two objects
        // are equal. For example, consider two object equal if their name
        // starts with the same character.
        return t.getName().charAt(0);
    }))
    .collect(Collectors.toList());

此外

在使用 Iterator(通常在 for-each 循环中使用)遍历数组时,您不能修改列表。这会导致 ConcurrentModificationException 异常。如果您使用 for 循环遍历数组,则可以修改该数组。然后,您必须控制迭代器的位置(在删除条目时将其递减)。


0

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