如何从 Java ArrayList 中删除重复对象?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11232857/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 04:19:35  来源:igfitidea点击:

How to remove Duplicate objects from Java ArrayList?

javaobjectcollectionsarraylistduplicates

提问by divine

I need your help with some sample code for a situation I could not get free from. I have a simple list of objects. My class is like this:

我需要你的一些示例代码的帮助,以解决我无法摆脱的情况。我有一个简单的对象列表。我的课是这样的:

class MyClass {
    String str;
    Integer intgr;
}

And the list contains elements like:

该列表包含以下元素:

[{a1  5}, {b2  3}, {g1  1}, {b5  1}, {c9  11}, {g2  3}, {d1  4}, {b3  19}... ... ...]

I need to check if any element contain the same prefix in string (here suffix is the last single character) then keep that element which have greater value in integer. The expected output from the above example list will be:

我需要检查是否有任何元素在字符串中包含相同的前缀(这里后缀是最后一个单个字符),然后保留该元素在整数中具有更大的值。上述示例列表的预期输出将是:

[{a1  5}, {c9  11}, {g2  3}, {d1  4}, {b3  19}... ... ...]

Strings will have unique values but could have matches in prefix. I'm not that good in java. So can anybody help me out from this? Here is the code I'm trying but getting IndexOutOfBoundsException. This code has faults, so need some help from you.

字符串将具有唯一值,但可以在前缀中匹配。我在java方面不是那么好。那么有人可以帮我解决这个问题吗?这是我正在尝试但得到的代码IndexOutOfBoundsException。此代码有错误,因此需要您的帮助。

Thanks!

谢谢!

        int size = list.size();
        for (int j = 0; j < size; j++) {
        if (list.get(j).str.substring(0, list.get(j).str.length()-1).compareTo(list.get(j+1).str.substring(0, list.get(j+1).str.length()-1)) == 0) {
            if (list.get(j).intgr > list.get(j+1).intgr)
                list.remove(list.get(j+1));
                size--;
            else {
                list.remove(list.get(j));
                j--;
                size--;
            }
        }
    }

采纳答案by Ted Hopp

There are two problems with your code. First, when j == size - 1(the last iteration), you are calling list.get(j+1), which is what is causing the exception. Just change your loop condition to j < size - 1and the exception should go away. (Alternatively, start at j = 1and compare to the previous element.)

您的代码有两个问题。首先,当j == size - 1(最后一次迭代)时,您正在调用 list.get(j+1),这就是导致异常的原因。只需将循环条件更改为j < size - 1,异常就会消失。(或者,j = 1从前一个元素开始并进行比较。)

Second, you are only comparing each element with its immediate successor element. From your description, it doesn't sound like that's what you want to do.

其次,您只是将每个元素与其直接后继元素进行比较。从你的描述来看,这听起来不像是你想要做的。

I'd suggest capturing the logic of comparison in a separate method. It might be part of MyClass:

我建议在单独的方法中捕获比较的逻辑。它可能是以下内容的一部分MyClass

class MyClass {
    String str;
    Integer intgr;
    /**
     * Returns the relationship between this and another MyClass instance.
     * The relationship can be one of three values:
     * <pre>
     *   -1 - This object should be discarded and other kept
     *    0 - There is no relationship between this and other
     *    1 - This object should be kept and other discarded
     * </pre>
     *
     * @param other The other instance to evaluate
     *
     * @return 0 if there is no relationship.
     */
    public int relationTo(MyClass other) {
        final String myPrefix = str.substring(0, str.length() - 1);
        final String otherPrefix = other.str.substring(0, other.str.length() - 1);
        if (myPrefix.equals(otherPrefix)) {
            return intgr < other.intgr ? -1 : 1;
        } else {
            return 0;
        }
    }
}

(This can easily be transformed into a method with two arguments that would be outside MyClass.) Then you can use the method to decide what to keep. You need to do a double iteration to find non-adjacent objects:

(这可以很容易地转换为带有两个参数的方法,这些参数将在MyClass.之外。)然后您可以使用该方法来决定要保留什么。您需要进行两次迭代才能找到不相邻的对象:

int size = list.size();
for (int i = 0; i < size; ++i) {
    final MyClass current = list.get(i);
    for (int j = 0; j < i; ++j) {
        final MyClass previous = list.get(j);
        final int relation = previous.relationTo(current);
        if (relation < 0) {
            // remove previous (at index j)
            list.remove(j);
            --i;
            --j;
            --size;
        } else if (relation > 0) {
            // remove current (at index i)
            list.remove(i);
            --i;
            --size;
            break; // exit inner loop
        }
        // else current and previous don't share a prefix
    }
}

回答by Francisco Spaeth

You could iterate over your collection of elements adding them into a Mapassociating the key (prefix) to the value (object). Every time an element is added you check if the element stored with the same prefix is bigger than the one being added.

您可以迭代您的元素集合,将它们添加到Map将键(前缀)关联到值(对象)中。每次添加元素时,您都会检查使用相同前缀存储的元素是否大于添加的元素。

In order to have this behavior:

为了有这种行为:

provides this: [{a1 5}, {b2 3}, {g1 1}, {b5 1}, {c9 11}, {g2 3}, {d1 4}, {b3 19}]
results this: [{a1 5}, {c9 11}, {g2 3}, {d1 4}, {b3 19}]

You could implement something like this:

你可以实现这样的东西:

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class TestSystemOut {

    public static void main(final String[] a) {
        List<MyObj> list = prepareList();
        System.out.println("provides this: " + list);

        Map<String, MyObj> map = new LinkedHashMap<String, MyObj>(); // if result order doesn't matter this can be a simple HashMap

        String strTmp;
        for (MyObj obj : list) {

            strTmp = obj.str;
            strTmp = strTmp.substring(0, strTmp.length() - 1);

            if (map.get(strTmp) == null || map.get(strTmp).integer < obj.integer) {
                map.remove(strTmp); // this could be removed if order of result doesn't matter
                map.put(strTmp, obj);
            }
        }

        list.clear();
        list.addAll(map.values());

        System.out.println("results this: " + list);
    }

    public static class MyObj {
        String str;
        Integer integer;

        public MyObj(final String str, final Integer integer) {
            super();
            this.str = str;
            this.integer = integer;
        }

        @Override
        public String toString() {
            return "{" + str + " " + integer + "}";
        }

    }

    private static List<MyObj> prepareList() {
        List<MyObj> list = new ArrayList<MyObj>();
        list.add(new MyObj("a1", 5));
        list.add(new MyObj("b2", 3));
        list.add(new MyObj("g1", 1));
        list.add(new MyObj("b5", 1));
        list.add(new MyObj("c9", 11));
        list.add(new MyObj("g2", 3));
        list.add(new MyObj("d1", 4));
        list.add(new MyObj("b3", 19));
        return list;
    }
}

回答by assylias

Another (possibly easier to read & debug) way would be to:

另一种(可能更容易阅读和调试)方法是:

  1. put your items in a List
  2. iterate over the list and populate a Map where String is the letter and MyClass is the minimum (i.e. if the map already contains something for "a", check if your new MyClass is bigger or smaller and replace accordingly)
  1. 把你的物品放在一个列表中
  2. 遍历列表并填充一个 Map,其中 String 是字母,MyClass 是最小值(即,如果地图已经包含“a”的内容,请检查您的新 MyClass 是更大或更小并相应地替换)

Simple implementation:

简单的实现:

public class Test {

    public static void main(String[] args) throws InterruptedException {
        List<MyClass> list = Arrays.asList(new MyClass("a1", 5),
                new MyClass("b2",  3),
                new MyClass("g1",  1),
                new MyClass("b5",  1),
                new MyClass("c9",  11),
                new MyClass("g2",  3),
                new MyClass("d1",  4),
                new MyClass("b3",  19));

        Map<String, MyClass> map = new HashMap<String, MyClass>();

        for (MyClass mc : list) {
            MyClass current = map.get(mc.getLetter());
            if (current == null || mc.intgr > current.intgr) {
                map.put(mc.getLetter(), mc);
            }
        }
        System.out.println(map);
    }

    static class MyClass  {

        String str;
        Integer intgr;

        MyClass(String str, Integer intgr) {
            this.str = str;
            this.intgr = intgr;
        }
        String getLetter() {
            return str.substring(0,1);
        }

        @Override
        public String toString() {
            return "[" + str + " " + intgr + "]";
        }
    }
}

回答by Rossiar

/*
 * For every string in the list, look at all strings in the
 * list and compare the substring from 0 to length-1, if they
 * match and the id is not the same as the current s (i.e. s and
 * list.get(i) are at the same address) then remove that string.
 */
public ArrayList remdup(ArrayList<String> list) {

    for (String s : list) {
        for (int i=0; i<list.size();i++) {
            if (s.substring(0, s.length()-1).compareTo(list.get(i).substring(0, list.get(i).length()-1)) == 0
                    && list.indexOf(list.get(i)) != list.indexOf(s)) {
                list.remove(list.get(i));
            }
        }
    }
    return list;
}

Maybe give this a try, haven't tested yet but figure it should work. If it doesn't maybe try using something different to compareTo(), maybe use equals() as an alternative.

也许试一试,还没有测试过,但认为它应该可以工作。如果它不尝试使用与 compareTo() 不同的东西,则可以使用 equals() 作为替代方法。

Also, you are getting an IndexOutOfBoundsException because you are subtracting off of size every time you remove an element, making your search area smaller. You have not taken into consideration that remove() does this for you.

此外,您会收到 IndexOutOfBoundsException,因为每次删除元素时都会减去大小,从而使搜索区域变小。您没有考虑到 remove() 为您执行此操作。

I would consider using a Map for this, instead of a class. That way you don't have to go about re-inventing the wheel.

我会考虑为此使用 Map 而不是类。这样你就不必重新发明轮子了。