从列表中删除(并计算)重复项

Posted

技术标签:

【中文标题】从列表中删除(并计算)重复项【英文标题】:remove (and count) duplicates from a list 【发布时间】:2022-01-20 22:45:34 【问题描述】:

是否可以在同一个列表的两个迭代器之间进行迭代并删除嵌套迭代器中的一个项目

版本 1(不起作用):

var i = all.iterator();
while (i.hasNext()) 
    var a = i.next();
    var j = all.iterator();
    while (j.hasNext()) 
        var b = j.next();
        if (!a.shouldBRemoved(b)) 
            a.setDuplicates(a.getDuplicates + 1);
            // I want to remove the element on the fly 
            // because on each iteration the iterated elements will have a decreased size and will iterate faster (because of fewer elements)
            // However: this does NOT work because of ConcurrentModificationException:
            j.remove();  
        
    

我得到一个java.util.ConcurrentModificationException,因为我在同一个迭代器中修改了一个元素..

我可以通过使用另一个列表removableItems 来解决这个问题,并将这些项目放入其中:

第 2 版(有效):

for (var a : all) 
    for (var b : all) 
        if (!a.shouldBRemoved(b)) 
            a.setDuplicates(a.getDuplicates + 1);
            // this works, 
            // however I must use an additation list to keep track of the items to be removed
            // it's also not more performant than removing the elements on the fly 
            // because on each iteration the iterated elements has the same size
            removableItems.add(b);
        
    

all.removeAll(removableItems);
    

有没有办法解决这个问题不需要需要中间列表removableItems我想即时删除元素。

【问题讨论】:

我不确定你想问什么。 没有任何代码 sn-ps 证明需要嵌套循环/迭代。如果方法 a.shouldBeRemoved 仅在 a 上调用而不使用/传递 b,则可以将其从外部迭代器中删除。 @AlexRudenko 好点。我确实忘记将b 传递给a.shouldBeRemoved()。我编辑了上面的代码。 在使用嵌套迭代器进行迭代时,您还应该检查 a == b 是否相等。你能澄清哪些项目应该被删除(重复?)以及哪些值应该保留在all中? 【参考方案1】:

尝试使用支持并发修改的CopyOnWriteArrayList

List < String > myList = new CopyOnWriteArrayList < String > ();
    myList.add("1");
    myList.add("2");
    myList.add("3");
    myList.add("4");
    myList.add("5");
    for (int i = 0; i < myList.size(); i++) 
      System.out.println("List value: " + myList.get(i));
      if (myList.get(i).equals("3")) 
        myList.remove(i);
        i--;
        myList.add("6");
      
    
    System.out.println("List Size:" + myList.size());

【讨论】:

这不是一个高效的解决方案:CopyOnWriteArrayList 将所有元素复制到新的内部副本中。所以它的行为类似于使用我的 version2【参考方案2】:

到目前为止,我找到了一个很好的解决方案(版本 3):

List<Item> removeDuplicates(List<Item> all) 
        var uniqueResults = new ArrayList<Item>();
        for (var a : all) 
            for (var b : all) 
                // check if "a" and "b" is not the same instance, but have equal content
                if (!a.equals(b) && a.isDeepEqualTo(b)) 
                    if (a.duplicates == 0 && b.duplicates == 0) 
                        // "a" has duplicates: 
                        // Add only "a" and discard "b" for the rest of the loops.
                        uniqueResults.add(a);
                    
                    // count the number of duplicates
                    a.duplicates = a.duplicates + 1;
                
            
            // "a" has no duplicates, add it.
            if (a.duplicates == 0 && !uniqueResults.contains(a)) 
                uniqueResults.add(a);
            
        
        return uniqueResults;

到目前为止它有效 - 我没有看到任何会错误(不)删除的边缘情况。

它也比使用 version 2(带有 removableItems()-list)更好,因为它的性能更高(特别是对于大型列表),因为我们不使用 removeremovAll,我们只添加项目(O(1))。

【讨论】:

这里是否曾经从all 中删除?如果重复的数量增加,它应该如何变为 0 ?另外,为什么删除索引i(即a)处的元素? @AlexRudenko 感谢您的帮助。我编辑了我的解决方案,这个解决方案有效,是迄今为止最好的。如果您能找到更好的解决方案或至少有改进版本 4 的建议,我会很高兴。 !a.equals(b) &amp;&amp; a.isDeepEqualTo(b) 永远为真时,软件设计存在根本问题。两个对象可以同时“深度相等”但“不相等”的逻辑迟早会出问题。 @Holger 相同和相等是有区别的。如果我使用equal()/hashCode(),那么我无法区分具有相同内容的两个不同实例。也许deepEqualTo()这个词有点不对,但这里的意思是:“比较两个不同的实例,如果内容(但不是实例)彼此相等,则返回true。” 当你的意思是“同一个实例”时,你应该使用==,这对读者来说很明显。 “比较两个不同的实例,如果内容(但不是实例)彼此相等,则返回 true”正是equals 的合同(当然,必须为您的自己的课程)

以上是关于从列表中删除(并计算)重复项的主要内容,如果未能解决你的问题,请参考以下文章

从列表中删除重复项?

C#从列表中删除重复的对象并首先增加

从 Vue 中的 v-for 列表中删除重复项

从 Python 列表中删除重复项

如何使用列表理解从列表中删除重复项? [复制]

从 Ocaml 中的列表列表中删除重复项?