【问题标题】:Java - Removing duplicates in an ArrayListJava - 删除 ArrayList 中的重复项
【发布时间】:2011-01-26 23:15:03
【问题描述】:

我正在开发一个使用ArrayList 存储Strings 的程序。该程序通过菜单提示用户并允许用户选择要执行的操作。此类操作是将字符串添加到列表、打印条目等。我想要做的是创建一个名为removeDuplicates() 的方法。此方法将搜索 ArrayList 并删除所有重复值。我想在列表中保留一个重复值的实例。我还希望此方法返回已删除的重复项总数。

我一直在尝试使用嵌套循环来完成此操作,但我遇到了麻烦,因为当条目被删除时,ArrayList 的索引会被更改,并且事情无法正常工作。我从概念上知道我需要做什么,但我无法在代码中实现这个想法。

这是一些伪代码:

从第一个条目开始; 检查列表中的每个后续条目,看看它是否与第一个条目匹配; 删除列表中与第一个条目匹配的每个后续条目;

检查完所有条目后,转到第二个条目; 检查列表中的每个条目,看看它是否与第二个条目匹配; 删除列表中与第二个条目匹配的每个条目;

重复进入列表

这是我目前的代码:

public int removeDuplicates()
{
  int duplicates = 0;

  for ( int i = 0; i < strings.size(); i++ )
  {
     for ( int j = 0; j < strings.size(); j++ )
     {
        if ( i == j )
        {
          // i & j refer to same entry so do nothing
        }

        else if ( strings.get( j ).equals( strings.get( i ) ) )
        {
           strings.remove( j );
           duplicates++;
        }
     }
 }

   return duplicates;
}

更新:Will 似乎正在寻找一种家庭作业解决方案,该解决方案涉及开发用于删除重复项的算法,而不是使用 Sets 的实用解决方案。见他的评论:

感谢您的建议。这是作业的一部分,我相信老师的本意是不包括集合。换句话说,我要提出一个解决方案,在不实现HashSet 的情况下搜索和删除重复项。老师建议使用嵌套循环,这是我正在尝试做的,但在删除某些条目后,ArrayList 的索引出现了一些问题。

【问题讨论】:

  • 如果无法通过 Set 运行它们(人们已经建议过),那么了解是否有任何其他限制会很有帮助,例如 O(?)。您当前的解决方案是 O(n^2),这在 CS 课程中很常见,被认为对于这样的事情来说太重了。
  • 如果你的老师让你用 Java 做作业,那么用Set 给他那个务实的解决方案=)

标签: java algorithm arraylist


【解决方案1】:

为什么不使用像Set 这样的集合(以及像HashSet 这样的实现)自然可以防止重复?

【讨论】:

  • +1,使用 Set 是最好的选择。如果要统计删除的重复次数,和之前一样存储在List中,然后通过将List传入构造函数来构造Set,然后比较两者的大小差,得到重复次数。
  • +1 表示解决方案 -1 表示不适合 homework = 0 分的解决方案。 :(@Will 并没有把它标记为如此艰难
  • 如果保持秩序很重要怎么办?
  • @Carl - 然后使用 LinkedHashSet。
  • 要使用 set,您必须实现 Equals 才能使 Set 在用户创建的对象上正常工作。
【解决方案2】:

您可以毫无问题地使用嵌套循环:

public static int removeDuplicates(ArrayList<String> strings) {

    int size = strings.size();
    int duplicates = 0;

    // not using a method in the check also speeds up the execution
    // also i must be less that size-1 so that j doesn't
    // throw IndexOutOfBoundsException
    for (int i = 0; i < size - 1; i++) {
        // start from the next item after strings[i]
        // since the ones before are checked
        for (int j = i + 1; j < size; j++) {
            // no need for if ( i == j ) here
            if (!strings.get(j).equals(strings.get(i)))
                continue;
            duplicates++;
            strings.remove(j);
            // decrease j because the array got re-indexed
            j--;
            // decrease the size of the array
            size--;
        } // for j
    } // for i

    return duplicates;

}

【讨论】:

  • 没有测试,这看起来很理想。请注意,内部索引在外部索引之后开始(您不需要每次都从列表的开头检查,因为您已经检查了外部索引值是否存在重复项)。最重要的是,它似乎确实回答了所提出的问题!
  • @Azder - 它真的会抛出 IndexOutOfBoundsException 吗?你的条件 j
  • 是的,可能是这样,这样就避免了 i 一个额外的不需要的循环
【解决方案3】:

您可以尝试使用这种方法来获取字符串保留顺序的副本。

List<String> list;
List<String> dedupped = new ArrayList<String>(new LinkedHashSet<String>(list));

这种方法也是 O(n) 摊销而不是 O(n^2)

【讨论】:

  • 用set,运行时间应该是O(n)
【解决方案4】:

只是为了澄清我对 matt b 的回答的评论,如果您真的想计算删除的重复项的数量,请使用以下代码:

List<String> list = new ArrayList<String>();

// list gets populated from user input...

Set<String> set = new HashSet<String>(list);
int numDuplicates = list.size() - set.size();

【讨论】:

  • 好吧,我考虑过哈希集,但这是作业的一部分,老师没有提到哈希集是一种可能的解决方案。我认为我们应该在不使用 hashset 的情况下提出一个实现。
  • 好的,你的理解是,这是一个任务,看看你是否可以开发出适当的算法来删除重复项,而不仅仅是“完成它”?我会澄清你最初的问题/帖子。
【解决方案5】:
List<String> lst = new ArrayList<String>();

lst.add("one");
lst.add("one");
lst.add("two");
lst.add("three");
lst.add("three");
lst.add("three");
Set se =new HashSet(lst);
lst.clear();
lst = new ArrayList<String>(se);
for (Object ls : lst){
    System.out.println("Resulting output---------" + ls);   
}

【讨论】:

    【解决方案6】:

    我一直在尝试使用嵌套循环来完成此操作,但我遇到了麻烦,因为当条目被删除时,ArrayList 的索引被改变事情并没有按应有的方式进行

    为什么不每次删除条目时减少计数器。

    当您删除条目时,元素也会移动:

    ej:

    String [] a = {"a","a","b","c" }
    

    职位:

    a[0] = "a";
    a[1] = "a";    
    a[2] = "b";
    a[3] = "c";
    

    删除第一个“a”后,索引为:

    a[0] = "a";
    a[1] = "b";
    a[2] = "c";
    

    因此,您应该考虑到这一点并减小j (j--) 的值以避免“跳过”某个值。

    看这个截图:

    【讨论】:

    【解决方案7】:
    public Collection removeDuplicates(Collection c) {
    // Returns a new collection with duplicates removed from passed collection.
        Collection result = new ArrayList();
    
        for(Object o : c) {
            if (!result.contains(o)) {
                result.add(o);
            }
        }
    
        return result;
    }
    

    public void removeDuplicates(List l) {
    // Removes duplicates in place from an existing list
        Object last = null;
        Collections.sort(l);
    
        Iterator i = l.iterator();
        while(i.hasNext()) {
            Object o = i.next();
            if (o.equals(last)) {
                i.remove();
            } else {
                last = o;
            }
        }
    }
    

    两者都未经测试。

    【讨论】:

    • 我喜欢第一种方法;它很容易理解,并利用了“contains()”中编码的所有可能的优化
    • 我认为方法声明应该是: public Collection removeDuplicates(Collection c) 以便返回与输入相同的集合。在您的示例中,对于传递的 Collection,将返回 Collection。但是基本的想法很好!
    • Collections.sort() 要求项目具有可比性。
    【解决方案8】:

    从数组列表中删除重复字符串的一种非常简单的方法

    ArrayList al = new ArrayList();
    // add elements to al, including duplicates
    HashSet hs = new HashSet();
    hs.addAll(al);
    al.clear();
    al.addAll(hs);
    

    【讨论】:

      【解决方案9】:

      假设您不能像您所说的那样使用 Set,解决问题的最简单方法是使用临时列表,而不是尝试就地删除重复项:

      public class Duplicates {
      
          public static void main(String[] args) {
              List<String> list = new ArrayList<String>();
              list.add("one");
              list.add("one");
              list.add("two");
              list.add("three");
              list.add("three");
              list.add("three");
      
              System.out.println("Prior to removal: " +list);
              System.out.println("There were " + removeDuplicates(list) + " duplicates.");
              System.out.println("After removal: " + list);
          }
      
          public static int removeDuplicates(List<String> list) {
              int removed = 0;
              List<String> temp = new ArrayList<String>();
      
              for(String s : list) {
                  if(!temp.contains(s)) {
                      temp.add(s);
                  } else {
                      //if the string is already in the list, then ignore it and increment the removed counter
                      removed++;
                  }
              }
      
              //put the contents of temp back in the main list
              list.clear();
              list.addAll(temp);
      
              return removed;
          }
      
      }
      

      【讨论】:

      • 一个临时列表使列表的内存占用加倍。
      【解决方案10】:

      你可以做这样的事情,上面的人回答是一种选择,但这是另一种选择。

      for (int i = 0; i < strings.size(); i++) {
          for (int j = j + 1; j > strings.size(); j++) {
            if(strings.get(i) == strings.get(j)) {
                  strings.remove(j);
                  j--;
             }`
          }
        }
      
      return strings;
      

      【讨论】:

        【解决方案11】:

        使用集合是删除重复项的最佳选择:

        如果您有一个数组列表,您可以删除重复项并仍然保留数组列表功能:

         List<String> strings = new ArrayList<String>();
         //populate the array
         ...
         List<String> dedupped = new ArrayList<String>(new HashSet<String>(strings));
         int numdups = strings.size() - dedupped.size();
        

        如果不能使用集合,则对数组进行排序 (Collections.sort()) 并遍历列表,检查当前元素是否等于前一个元素,如果是,则删除它。

        【讨论】:

          【解决方案12】:

          使用集合是最好的选择(正如其他人所建议的那样)。

          如果你想比较一个列表中的所有元素,你应该稍微调整一下你的 for 循环:

          for(int i = 0; i < max; i++)
              for(int j = i+1; j < max; j++)
          

          这样您就不会只比较每个元素一次而不是两次。这是因为与第一个循环相比,第二个循环从下一个元素开始。

          在迭代它们时从列表中删除时(即使您使用 for 循环而不是迭代器),请记住您会减小列表的大小。一个常见的解决方案是保留另一个要删除的项目列表,然后在确定要删除的项目后,将它们从原始列表中删除。

          【讨论】:

            【解决方案13】:
            public ArrayList removeDuplicates(ArrayList <String> inArray)
            {
                ArrayList <String> outArray = new ArrayList();
                boolean doAdd = true;
                for (int i = 0; i < inArray.size(); i++)
                {
                    String testString = inArray.get(i);
                    for (int j = 0; j < inArray.size(); j++)
                    {
                        if (i == j)
                        {
                            break;
                        }
                        else if (inArray.get(j).equals(testString))
                        {
                            doAdd = false;
                            break;
                        }
            
                    }
                    if (doAdd)
                    {
                        outArray.add(testString);
                    }
                    else
                    {
                        doAdd = true;
                    }
            
                }
                return outArray;
            
            }
            

            【讨论】:

              【解决方案14】:

              您可以用空字符串* 替换重复项,从而使索引保持完整。然后在你完成后你可以去掉空字符串。

              *但仅当空字符串在您的实现中无效时。

              【讨论】:

                【解决方案15】:

                您在代码中看到的问题是您在迭代期间删除了一个条目,从而使迭代位置无效。

                例如:

                {"a", "b", "c", "b", "b", "d"} 
                       i         j  
                

                现在您正在删除字符串[j]。

                {"a", "b", "c", "b", "d"} 
                       i         j  
                

                内循环结束,j递增。

                {"a", "b", "c", "b", "d"} 
                       i              j
                

                仅检测到一个重复的“b”...哎呀。

                在这些情况下的最佳做法是存储必须删除的位置,并在您完成对数组列表的迭代后删除它们。 (一个好处,你或编译器可以在循环之外优化 strings.size() 调用)

                提示,您可以在 i+1 处使用 j 开始迭代,您已经检查了 0 - i!

                【讨论】:

                  【解决方案16】:

                  内部for 循环无效。如果你删除一个元素,你不能增加j,因为j现在指向你删除元素之后的元素,你需要检查它。

                  换句话说,您应该使用while 循环而不是for 循环,并且仅在ij 的元素不匹配时增加j。如果它们匹配,则删除j 处的元素。 size() 将减少 1,j 现在将指向以下元素,因此无需增加 j

                  此外,没有理由检查内部循环中的所有元素,只检查i 之后的元素,因为i 之前的重复元素已被先前的迭代删除。

                  【讨论】:

                    【解决方案17】:
                    public <Foo> Entry<Integer,List<Foo>> uniqueElementList(List<Foo> listWithPossibleDuplicates) {
                      List<Foo> result = new ArrayList<Foo>();//...might want to pre-size here, if you have reliable info about the number of dupes
                      Set<Foo> found = new HashSet<Foo>(); //...again with the pre-sizing
                      for (Foo f : listWithPossibleDuplicates) if (found.add(f)) result.add(f);
                      return entryFactory(listWithPossibleDuplicates.size()-found.size(), result);
                    }
                    

                    然后是一些entryFactory(Integer key, List&lt;Foo&gt; value) 方法。如果您想改变原始列表(可能不是一个好主意,但无论如何):

                    public <Foo> int removeDuplicates(List<Foo> listWithPossibleDuplicates) {
                      int original = listWithPossibleDuplicates.size();
                      Iterator<Foo> iter = listWithPossibleDuplicates.iterator();
                      Set<Foo> found = new HashSet<Foo>();
                      while (iter.hasNext()) if (!found.add(iter.next())) iter.remove();
                      return original - found.size();
                    }
                    

                    对于您使用字符串的特殊情况,您可能需要处理一些额外的等式约束(例如,大写和小写版本是相同还是不同?)。

                    编辑:啊,这是作业。在 Java Collections 框架中查找 Iterator/Iterable 以及 Set,看看您是否得出与我提供的相同的结论。泛型部分只是肉汁。

                    【讨论】:

                      【解决方案18】:

                      我加入这个问题有点晚了,但我已经有了一个更好的解决方案,使用 GENERIC 类型。以上提供的所有解决方案都只是一个解决方案。它们增加了整个运行时线程的复杂性。

                      RemoveDuplicacy.java

                      我们可以使用一种在加载时应该完成所需的技术来最小化它。

                      示例:假设您使用类类型的数组列表为:

                      ArrayList<User> usersList = new ArrayList<User>();
                              usersList.clear();
                      
                              User user = new User();
                              user.setName("A");
                              user.setId("1"); // duplicate
                              usersList.add(user);
                      
                              user = new User();
                              user.setName("A");
                              user.setId("1"); // duplicate
                              usersList.add(user);
                      
                              user = new User();
                              user.setName("AB");
                              user.setId("2"); // duplicate
                              usersList.add(user);
                      
                              user = new User();
                              user.setName("C");
                              user.setId("4");
                              usersList.add(user);
                      
                              user = new User();
                              user.setName("A");
                              user.setId("1"); // duplicate
                              usersList.add(user);
                      
                              user = new User();
                              user.setName("A");
                              user.setId("2"); // duplicate
                              usersList.add(user);
                      
                      
                      }
                      

                      上面使用的arraylist的基础类:用户类

                      class User {
                          private String name;
                          private String id;
                      
                          /**
                           * @param name
                           *            the name to set
                           */
                          public void setName(String name) {
                              this.name = name;
                          }
                      
                          /**
                           * @return the name
                           */
                          public String getName() {
                              return name;
                          }
                      
                          /**
                           * @param id
                           *            the id to set
                           */
                          public void setId(String id) {
                              this.id = id;
                          }
                      
                          /**
                           * @return the id
                           */
                          public String getId() {
                              return id;
                          }
                      

                      }

                      现在在java中有两个对象(父)类的重写方法,这可以帮助我们更好地服务于我们的目的。它们是:

                      @Override
                          public int hashCode() {
                      
                              final int prime = 31;
                              int result = 1;
                              result = prime * result + ((id == null) ? 0 : id.hashCode());
                              return result;
                      
                          }
                      
                          @Override
                          public boolean equals(Object obj) {
                      
                              if (this == obj)
                                  return true;
                      
                              if (obj == null)
                                  return false;
                      
                              if (getClass() != obj.getClass())
                                  return false;
                      
                              User other = (User) obj;
                      
                              if (id == null) {
                                  if (other.id != null)
                                      return false;
                      
                              } else if (!id.equals(other.id))
                                  return false;
                      
                              return true;
                      
                          }
                      

                      您必须在 User 类中重写这些方法

                      这里是完整的代码:

                      https://gist.github.com/4584310

                      如果您有任何疑问,请告诉我。

                      【讨论】:

                        【解决方案19】:

                        您可以将列表添加到 HashSet 中,然后再次将该哈希集转换为列表以删除重复项。

                        public static int removeDuplicates(List<String> duplicateList){
                            List<String> correctedList = new ArrayList<String>();
                            Set<String> a = new HashSet<String>();
                            a.addAll(duplicateList);
                            correctedList.addAll(a);
                            return (duplicateList.size()-correctedList.size());
                        }
                        

                        在这里它将返回重复的数量。您还可以将正确列表与所有唯一值一起使用

                        【讨论】:

                          【解决方案20】:

                          下面是从列表中删除重复元素而不更改列表顺序、不使用临时列表和不使用任何设置变量的代码。此代码节省内存并提高性能。

                          这是一种适用于任何类型列表的通用方法。

                          这是其中一次采访中提出的问题。 在许多论坛中搜索了解决方案,但没有找到,所以认为这是发布代码的正确论坛。

                              public List<?> removeDuplicate(List<?> listWithDuplicates) {
                              int[] intArray = new int[listWithDuplicates.size()];
                              int dupCount = 1;
                              int arrayIndex = 0;
                              int prevListIndex = 0; // to save previous listIndex value from intArray
                              int listIndex;
                          
                              for (int i = 0; i < listWithDuplicates.size(); i++) {
                                  for (int j = i + 1; j < listWithDuplicates.size(); j++) {
                                      if (listWithDuplicates.get(j).equals(listWithDuplicates.get(i)))
                                          dupCount++;
                          
                                      if (dupCount == 2) {
                                          intArray[arrayIndex] = j; // Saving duplicate indexes to an array
                                          arrayIndex++;
                                          dupCount = 1;
                                      }
                                  }
                              }
                          
                              Arrays.sort(intArray);
                          
                              for (int k = intArray.length - 1; k >= 0; k--) {
                                  listIndex = intArray[k];
                                  if (listIndex != 0 && prevListIndex != listIndex){
                                      listWithDuplicates.remove(listIndex);
                                      prevListIndex = listIndex;
                                  }
                              }
                              return listWithDuplicates;
                          }
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2012-08-25
                            • 1970-01-01
                            • 2018-05-29
                            • 2014-04-22
                            • 2015-12-12
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多