web-dev-qa-db-ja.com

Javaを繰り返しながらコレクションからアイテムを削除する

繰り返し処理中に、セットから複数の要素を削除できるようにしたい。最初は、イテレータが以下の素朴なソリューションが機能するのに十分なほどスマートであることを望みました。

_Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> it = set.iterator();
while (it.hasNext()) {
    set.removeAll(setOfElementsToRemove(it.next()));
}
_

しかし、これはConcurrentModificationExceptionをスローします。

Iterator.remove()は、一度に複数のものを削除する必要があるため、表示できる限り機能しないことに注意してください。また、「オンザフライ」で削除する要素を特定することはできませんが、メソッドsetOfElementsToRemove()を記述することは可能です。私の特定のケースでは、反復中に削除するものを決定するために多くのメモリと処理時間がかかります。メモリの制約のため、コピーを作成することもできません。

setOfElementsToRemove()は削除したいSomeClassインスタンスのセットを生成し、fillSet(set)はそのセットをエントリで埋めます。

Stack Overflowを検索した後、この問題に対する適切な解決策を見つけることができませんでしたが、数時間後に休憩すると、次のことがうまくいくことに気付きました。

_Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> outputSet = new HashSet<SomeClass>();
fillSet(set);
while (!set.isEmpty()) {
    Iterator<SomeClass> it = set.iterator();
    SomeClass instance = it.next();
    outputSet.add(instance);
    set.removeAll(setOfElementsToRemoveIncludingThePassedValue(instance));
}
_

setOfElementsToRemoveIncludingThePassedValue()は、渡された値を含む削除する要素のセットを生成します。 setが空になるように、渡された値を削除する必要があります。

私の質問は、誰かがこれを行うより良い方法を持っているかどうか、またはこれらの種類の削除をサポートするコレクション操作があるかどうかです。

また、必要があると思われるため、自分のソリューションを投稿すると思ったので、Stack Overflowという優れたリソースに貢献したいと考えました。

30
nash

通常、コレクションをループしている間にコレクションから要素を削除すると、 同時変更例外 が発生します。 Iterator インターフェースにremove()メソッドがある理由の一部はこれです。反復子を使用することは、要素のコレクションを横断しながら変更する唯一の安全な方法です。

コードは次のようになります。

Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> setIterator = set.iterator();
while (setIterator.hasNext()) {
    SomeClass currentElement = setIterator.next();
    if (setOfElementsToRemove(currentElement).size() > 0) {
        setIterator.remove();
    }
}

これにより、setOfElementsToRemove()から削除セットを生成するすべての要素を安全に削除できます。

[〜#〜] edit [〜#〜]

別の答えへのコメントに基づいて、これはあなたが望むものであるかもしれません:

Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> removalSet = new HashSet<SomeClass>();
fillSet(set);

for (SomeClass currentElement : set) {
    removalSet.addAll(setOfElementsToRemove(currentElement);
}

set.removeAll(removalSet);
40
Peter

セット内のすべての要素を繰り返して必要な要素を削除する代わりに、実際にGoogleコレクションを使用して(自分でできることではありません)、Predicateをmaskに適用できます不要なもの。

package com.stackoverflow.q1675037;

import Java.util.HashSet;
import Java.util.Set;

import org.junit.Assert;
import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;


public class SetTest
{
public void testFilter(final Set<String> original, final Set<String> toRemove, final Set<String> expected)
{

    Iterable<String> mask = Iterables.filter(original, new Predicate<String>()
    {
        @Override
        public boolean apply(String next) {
        return !toRemove.contains(next);
        }
    });

    HashSet<String> filtered = Sets.newHashSet(mask);

    Assert.assertEquals(original.size() - toRemove.size(), filtered.size());
    Assert.assertEquals(expected, filtered);        
}


@Test
public void testFilterNone()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet();

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");                
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}

@Test
public void testFilterAll()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    HashSet<String> expected = new HashSet<String>();
    this.testFilter(original, toRemove, expected);
}    

@Test
public void testFilterOne()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}    


@Test
public void testFilterSome()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

   Set<String> toRemove = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    this.testFilter(original, toRemove, expected);
}    
}
9
qnoid

イテレータ作成時のセットのスナップショットであるイテレータを提供する_Java.util.concurrent.CopyOnWriteArraySet_を試すことができます。セットに加えた変更(つまりremoveAll()を呼び出して)はイテレータには表示されませんが、セット自体を見ると表示されます(そしてremoveAll()は表示されません)スロー)。

6
joe p

イテレータを使用せずに、イテレーション中にイテレートしているセットから削除する必要があるソリューションは、まったく機能しません。おそらく1つを除いて:Collections.newSetFromMap(new ConcurrentHashMap<SomeClass, Boolean>(sizing params))を使用できます。キャッチは、イテレータが弱一貫性になったことです。つまり、まだ遭遇していない要素を削除するたびに、その要素がイテレーションの後の方に現れるかどうかは未定義です。それが問題ではない場合、これはあなたのために働くかもしれません。

もう1つできることは、代わりにtoRemoveセットを作成し、最後にのみset.removeAll(itemsToRemove);を作成することです。または、開始する前にセットをコピーして、一方のコピーを繰り返しながらもう一方のコピーを削除できるようにします。

編集:おっと、ピーター・ニックスはすでにtoRemoveのアイデアを提案していたようです(ただし、不必要に手動でロールされたremoveAllで)。

6

これには簡単な答えがあります-Iterator.remove()メソッドを使用します。

2
Andrzej Doyle

セットの1つのコピーに十分なメモリがある場合、2つのコピーに十分なメモリがあると仮定します。あなたが引用するカフカ風のルールはそれを禁じているようには見えません:)

私の提案:

fillSet(set);
fillSet(copy);
for (Object item : copy) {
   if (set.contains(item)) { // ignore if not
     set.removeAll(setOfStuffToRemove())
   }
}

セットは削除されますが、コピーはそのまま残り、ループするものだけを提供します。その間にセットから削除されたものは無視されます。

2
Carl Smotricz

削除するオブジェクトで イテレータのremoveメソッド を使用しないのはなぜですか?

反復子が導入された主な理由は、列挙子が列挙中に削除を処理できなかったためです。

1
Ben S

Iterator.removeメソッドを呼び出す必要があります。

また、ほとんどのJava.utilコレクションでは、コレクションの内容が変更された場合、removeメソッドが例外を生成することに注意してください。そのため、コードがマルチスレッドの場合は、特に注意するか、並行コレクションを使用してください。

Java API からコピー:

Listインターフェースは、ListIteratorと呼ばれる特別なイテレーターを提供します。これにより、Iteratorインターフェースが行う通常の操作に加えて、要素の挿入と置換および双方向アクセスが可能になります。提供します。リスト内の指定された位置から始まるリスト反復子を取得するメソッドが提供されます。

特別な種類のIteratorであるListIteratorが置換用に構築されていることを指摘しようと思いました。

0
Siri

繰り返し処理中に要素を削除できるSetを実装することができます。

標準の実装(HashSet、TreeSetなど)では、より効率的なアルゴリズムを使用できることを意味するため、これを許可していませんが、それは難しくありません。

Googleコレクションを使用した不完全な例を次に示します。

_import Java.util.Iterator;
import Java.util.Map;
import Java.util.Set;
import Java.util.concurrent.ConcurrentHashMap;

import com.google.common.base.Predicates;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;

public class ConcurrentlyModifiableSet<E>
extends ForwardingSet<E> {
 /** Create a new, empty set */
 public ConcurrentlyModifiableSet() {
  Map<E, Boolean> map = new ConcurrentHashMap<E, Boolean>();
  delegate = Sets.newSetFromMap(map);
 }

 @Override
 public Iterator<E> iterator() {
  return Iterators.filter(delegate.iterator(), Predicates.in(delegate));
 }

 @Override
 protected Set<E> delegate() {
  return this.delegate;
 }

 private Set<E> delegate;
}
_

注:イテレーターはremove()操作をサポートしません(ただし、質問の例では必要ありません)。

0
finnw