【问题标题】:How to remove Duplicate objects from Java ArrayList?如何从 Java ArrayList 中删除重复的对象?
【发布时间】:2012-06-27 18:36:30
【问题描述】:

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

class MyClass {
    String str;
    Integer intgr;
}

列表包含以下元素:

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

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

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

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

谢谢!

        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--;
            }
        }
    }

【问题讨论】:

  • 我会给你一个建议:不要像那样链接方法,而是尝试一次只做一个并将它们读入临时变量。更容易看出你的程序逻辑哪里出错了。

标签: java object collections arraylist duplicates


【解决方案1】:

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

为了有这种行为:

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}]

你可以这样实现:

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;
    }
}

【讨论】:

    【解决方案2】:

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

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

    我建议在单独的方法中捕获比较逻辑。它可能是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;
            }
        }
    }
    

    (这可以很容易地转换为具有两个参数的方法,该方法将在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
        }
    }
    

    【讨论】:

    • 感谢您的回答!但想知道为什么我从匹配的字符串中得到 intgr 中具有更小值的值,而不是我需要的更大值!
    • @divine - 这是因为我在最后一个代码块中根据current 和previous 之间的关系搞砸了要删除的内容的逻辑。我更新了我对(我希望)现在有效的答案。
    【解决方案3】:

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

    1. 将您的项目放入列表中
    2. 遍历列表并填充一个 Map,其中 String 是字母,MyClass 是最小值(即,如果地图已经包含“a”的内容,请检查您的新 MyClass 是更大或更小并相应地替换)

    简单实现:

    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 + "]";
            }
        }
    }
    

    【讨论】:

      【解决方案4】:
      /*
       * 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;
      }
      

      也许试一试,尚未测试,但认为它应该可以工作。如果不能尝试使用与 compareTo() 不同的方法,则可以使用 equals() 作为替代方法。

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

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

      【讨论】:

        猜你喜欢
        • 2015-08-25
        • 2017-08-02
        • 2013-12-24
        • 2021-08-08
        • 1970-01-01
        • 1970-01-01
        • 2020-12-17
        • 2011-11-24
        • 1970-01-01
        相关资源
        最近更新 更多