反復中にコレクションに要素を追加する


82

コレクションを反復処理しながら要素をコレクションに追加することは可能ですか?

具体的には、コレクションを繰り返し処理したいと思います。要素が特定の条件を満たす場合は、コレクションに他の要素を追加し、これらの追加された要素も繰り返し処理されるようにします。(私はこれが可能であることを理解していますループが終了しない性があるていますが、私の場合はそうではないと確信しています。)

Javaチュートリアル日からはこれが不可能であることを示唆している:「メモIterator.removeであるだけに反復中にコレクションを変更する安全な方法を、繰り返し処理の進行中に、基礎となるコレクションは他の方法で変更された場合の動作は不定です。」

では、イテレータを使用してやりたいことができない場合は、どうすればよいでしょうか。

回答:


62

繰り返したい要素を使ってキューを作成するのはどうですか。要素を追加する場合は、キューの最後にキューに入れ、キューが空になるまで要素を削除し続けます。これは、幅優先探索が通常どのように機能するかです。


2
これは、OPがコーディングしているモデルに適合する場合に、物事を行うための良い方法です。この方法では、イテレータを使用せず、whileループだけを使用します。キューに要素がある間に、最初の要素を処理します。ただし、リストを使用してこれを行うこともできます。
エディ

31
ListIterator iter = list.listIterator()add()remove()メソッドの 両方があるため、反復中に要素を追加および削除できます
soulmachine 2015

4
@soulmachineこれについてよろしいですか?そうしようとすると、ConcurrentModificationExceptionが発生します。
Nieke Aerts 2016年

あなたは正しいと思いますが、別のオプションがあります。次のようなスレッドセーフなコレクションを使用してくださいLinkedBlockingQueue
soulmachine 2016年

ここにはまだギャップがあると思います。たとえば、バックトラッキングアルゴリズムがある場合、@ soulmachineが提案するように、Listインターフェイスの兄弟を使用する必要がない限り、Setを使用してそれを処理することはできません(またはどのように?)。
stdout 2016年

46

ここには2つの問題があります。

最初の問題は、が返されたCollection後に追加することIteratorです。前述のCollectionように、以下のドキュメントに記載されているように、基礎が変更された場合の動作は定義されていませんIterator.remove

...このメソッドを呼び出す以外の方法で反復の進行中に基になるコレクションが変更された場合、イテレータの動作は指定されません。

2番目の問題は、Iteratorが取得できた後、同じ要素に戻ったIteratorとしても、Collection.iteratorメソッドのドキュメントに記載されているように、反復の順序について保証がないことです。

...要素が返される順序に関する保証はありません(このコレクションが保証を提供するクラスのインスタンスでない限り)。

たとえば、リストがあるとします[1, 2, 3, 4]

レッツ・発言は5時に追加されたIterator時だった3、と何とか、我々が得ますIteratorから繰り返しを再開できることを4。ただし、5後に来る保証はありません4。反復順序は次のようになり[5, 1, 2, 3, 4]ます5。その場合、イテレータは要素を見逃します。

振る舞いは保証されていないため、特定の方法で物事が起こるとは限りません。

代替案の1つは、別の Collection、新しく作成された要素を追加できる要素を用意し、それらの要素を反復処理することです。

Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"});
Collection<String> additionalList = new ArrayList<String>();

for (String s : list) {
    // Found a need to add a new element to iterate over,
    // so add it to another list that will be iterated later:
    additionalList.add(s);
}

for (String s : additionalList) {
    // Iterate over the elements that needs to be iterated over:
    System.out.println(s);
}

編集

で詳しく説明する アビの答え、我々がキューに反復処理したいという要素をキュー、キューは、要素を有している要素を削除することが可能です。これにより、元の要素に加えて、新しい要素に対する「反復」が可能になります。

それがどのように機能するかを見てみましょう。

概念的には、キューに次の要素がある場合:

[1, 2, 3, 4]

そして、を削除すると1、を追加すること42にします。キューは次のようになります。

[2, 3, 4, 42]

キューはFIFO先入れ先出し)データ構造であるため、この順序は一般的です。(Queueインターフェースのドキュメントに記載されているように、これは必要ではありませんQueuePriorityQueueありません。FIFOではないため、要素を自然な順序で順序付け考えてみてください。)

以下は、LinkedList(であるQueue)を使用して、デキュー中に追加された追加の要素とともにすべての要素を処理する例です。上記の例と同様42に、要素2が削除されると要素が追加されます。

Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);

while (!queue.isEmpty()) {
    Integer i = queue.remove();
    if (i == 2)
        queue.add(42);

    System.out.println(i);
}

結果は次のとおりです。

1
2
3
4
42

さすがに42ヒット時に追加された要素が2登場。


Aviのポイントは、キューがある場合はそれを繰り返す必要がないということだと思います。空でないときに要素を前面からデキューし、新しい要素を背面にエンキューするだけです。
ナット

@Nat:その通りです。指摘していただきありがとうございます。私はそれを反映するように私の答えを編集しました。
coobird 2009年

1
@coobird何らかの理由で、回答が切り捨てられます。[...]追加のelとともにすべての要素を調べるために—そしてそれが私が見ることができるすべてです、しかし私が答えを編集しようとするとすべてがそこにあります。何が起こっているのかについて何か考えはありますか?
–KohányiRóbert 2012


4

実際、それはかなり簡単です。最適な方法を考えてください。私は最適な方法は次のとおりだと信じています。

for (int i=0; i<list.size(); i++) {
   Level obj = list.get(i);

   //Here execute yr code that may add / or may not add new element(s)
   //...

   i=list.indexOf(obj);
}

次の例は、最も論理的な場合、つまり、反復要素の前に追加された新しい要素を反復する必要がない場合に完全に機能します。反復要素の後に追加された要素について-そこでもそれらを反復したくない場合があります。この場合、それらを反復しないようにマークするフラグを使用して、yrオブジェクトを追加または拡張する必要があります。


indexOfは追加には必要ありません。重複していると、混乱する可能性があります。
Peter Lawrey 2010年

はい、確かに、重複は問題です。それを追加してくれてありがとう。
PatlaDJ 2010年

実際のリストの実装によっては、list.get(i)はイテレータを使用するよりもはるかにコストがかかる可能性があることを追加する必要があります。少なくともリンクリストが大きい場合は、パフォーマンスが大幅に低下する可能性があります。例)
Stefan Winkler

4

ListIterator次のように使用します。

List<String> l = new ArrayList<>();
l.add("Foo");
ListIterator<String> iter = l.listIterator(l.size());
while(iter.hasPrevious()){
    String prev=iter.previous();
    if(true /*You condition here*/){
        iter.add("Bah");
        iter.add("Etc");
    }
}

重要なのは、逆の順序で反復することです。追加された要素は、次の反復で表示されます。


2

私はそれがかなり古いことを知っています。しかし、他の人にとってはどんな用途でも考えました。最近、反復中に変更可能なキューが必要な、この同様の問題に遭遇しました。私はlistIteratorを使用して、Aviが提案したものと同じ行に同じものを実装しました-> Aviの回答。これがあなたのニーズに合うかどうか見てください。

ModifiedWhileIterateQueue.java

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ModifyWhileIterateQueue<T> {
        ListIterator<T> listIterator;
        int frontIndex;
        List<T> list;

        public ModifyWhileIterateQueue() {
                frontIndex = 0;
                list =  new ArrayList<T>();
                listIterator = list.listIterator();
        }

        public boolean hasUnservicedItems () {
                return frontIndex < list.size();  
        }

        public T deQueue() {
                if (frontIndex >= list.size()) {
                        return null;
                }
                return list.get(frontIndex++);
        }

        public void enQueue(T t) {
                listIterator.add(t); 
        }

        public List<T> getUnservicedItems() {
                return list.subList(frontIndex, list.size());
        }

        public List<T> getAllItems() {
                return list;
        }
}

ModifiedWhileIterateQueueTest.java

    @Test
    public final void testModifyWhileIterate() {
            ModifyWhileIterateQueue<String> queue = new ModifyWhileIterateQueue<String>();
            queue.enQueue("one");
            queue.enQueue("two");
            queue.enQueue("three");

            for (int i=0; i< queue.getAllItems().size(); i++) {
                    if (i==1) {
                            queue.enQueue("four");
                    }
            }

            assertEquals(true, queue.hasUnservicedItems());
            assertEquals ("[one, two, three, four]", ""+ queue.getUnservicedItems());
            assertEquals ("[one, two, three, four]", ""+queue.getAllItems());
            assertEquals("one", queue.deQueue());

    }

1

イテレータを使用しています...いいえ、そうは思いません。次のようなものを一緒にハックする必要があります。

    Collection< String > collection = new ArrayList< String >( Arrays.asList( "foo", "bar", "baz" ) );
    int i = 0;
    while ( i < collection.size() ) {

        String curItem = collection.toArray( new String[ collection.size() ] )[ i ];
        if ( curItem.equals( "foo" ) ) {
            collection.add( "added-item-1" );
        }
        if ( curItem.equals( "added-item-1" ) ) {
            collection.add( "added-item-2" );
        }

        i++;
    }

    System.out.println( collection );

どのyeilds:
[foo、bar、baz、added-item-1、added-item-2]


1

追加のリストを使用し、addAllを呼び出して反復後に新しいアイテムを挿入するソリューション(たとえば、ユーザーNatによるソリューションなど)に加えて、CopyOnWriteArrayListのような並行コレクションを使用することもできます。

「スナップショット」スタイルのイテレータメソッドは、イテレータが作成された時点での配列の状態への参照を使用します。この配列はイテレータの存続期間中に変更されることはないため、干渉は不可能であり、イテレータはConcurrentModificationExceptionをスローしないことが保証されています。

この特別なコレクション(通常は同時アクセスに使用されます)を使用すると、基になるリストを繰り返し処理しながら操作できます。ただし、イテレータは変更を反映しません。

これは他のソリューションよりも優れていますか?おそらくそうではないでしょうが、コピーオンライトアプローチによってもたらされるオーバーヘッドはわかりません。


1
public static void main(String[] args)
{
    // This array list simulates source of your candidates for processing
    ArrayList<String> source = new ArrayList<String>();
    // This is the list where you actually keep all unprocessed candidates
    LinkedList<String> list = new LinkedList<String>();

    // Here we add few elements into our simulated source of candidates
    // just to have something to work with
    source.add("first element");
    source.add("second element");
    source.add("third element");
    source.add("fourth element");
    source.add("The Fifth Element"); // aka Milla Jovovich

    // Add first candidate for processing into our main list
    list.addLast(source.get(0));

    // This is just here so we don't have to have helper index variable
    // to go through source elements
    source.remove(0);

    // We will do this until there are no more candidates for processing
    while(!list.isEmpty())
    {
        // This is how we get next element for processing from our list
        // of candidates. Here our candidate is String, in your case it
        // will be whatever you work with.
        String element = list.pollFirst();
        // This is where we process the element, just print it out in this case
        System.out.println(element);

        // This is simulation of process of adding new candidates for processing
        // into our list during this iteration.
        if(source.size() > 0) // When simulated source of candidates dries out, we stop
        {
            // Here you will somehow get your new candidate for processing
            // In this case we just get it from our simulation source of candidates.
            String newCandidate = source.get(0);
            // This is the way to add new elements to your list of candidates for processing
            list.addLast(newCandidate);
            // In this example we add one candidate per while loop iteration and 
            // zero candidates when source list dries out. In real life you may happen
            // to add more than one candidate here:
            // list.addLast(newCandidate2);
            // list.addLast(newCandidate3);
            // etc.

            // This is here so we don't have to use helper index variable for iteration
            // through source.
            source.remove(0);
        }
    }
}

1

たとえば、2つのリストがあります。

  public static void main(String[] args) {
        ArrayList a = new ArrayList(Arrays.asList(new String[]{"a1", "a2", "a3","a4", "a5"}));
        ArrayList b = new ArrayList(Arrays.asList(new String[]{"b1", "b2", "b3","b4", "b5"}));
        merge(a, b);
        a.stream().map( x -> x + " ").forEach(System.out::print);
    }
   public static void merge(List a, List b){
        for (Iterator itb = b.iterator(); itb.hasNext(); ){
            for (ListIterator it = a.listIterator() ; it.hasNext() ; ){
                it.next();
                it.add(itb.next());

            }
        }

    }

a1 b1 a2 b2 a3 b3 a4 b4 a5 b5


0

コレクションをその場で変更するよりも、機能的に処理する方が好きです。これにより、エイリアシングの問題やその他のトリッキーなバグの原因だけでなく、この種の問題も完全に回避されます。

だから、私はそれを次のように実装します:

List<Thing> expand(List<Thing> inputs) {
    List<Thing> expanded = new ArrayList<Thing>();

    for (Thing thing : inputs) {
        expanded.add(thing);
        if (needsSomeMoreThings(thing)) {
            addMoreThingsTo(expanded);
        }
    }

    return expanded;
}

0

私見のより安全な方法は、新しいコレクションを作成し、指定されたコレクションを反復処理し、新しいコレクションに各要素を追加し、必要に応じて新しいコレクションにも要素を追加して、最終的に新しいコレクションを返すことです。


0

List<Object>繰り返したいリストがあれば、簡単で簡単な方法は次のとおりです。

while (!list.isEmpty()){
   Object obj = list.get(0);

   // do whatever you need to
   // possibly list.add(new Object obj1);

   list.remove(0);
}

したがって、リストを繰り返し処理し、常に最初の要素を取得してから削除します。このようにして、反復中に新しい要素をリストに追加できます。


0

イテレータは忘れてください。追加するのではなく、削除するだけです。私の答えはリストにのみ当てはまるので、コレクションの問題を解決しなかったことで私を罰しないでください。基本に固執する:

    List<ZeObj> myList = new ArrayList<ZeObj>();
    // populate the list with whatever
            ........
    int noItems = myList.size();
    for (int i = 0; i < noItems; i++) {
        ZeObj currItem = myList.get(i);
        // when you want to add, simply add the new item at last and
        // increment the stop condition
        if (currItem.asksForMore()) {
            myList.add(new ZeObj());
            noItems++;
        }
    }

ステファンに感謝します。修正しました。
ビクターイオネスク2013年

0

ListIteratorに飽きましたが、リストに追加するときにリストを使用する必要があるという私の場合は役に立ちませんでした。これが私のために働くものです:

LinkedListを使用します

LinkedList<String> l = new LinkedList<String>();
l.addLast("A");

while(!l.isEmpty()){
    String str = l.removeFirst();
    if(/* Condition for adding new element*/)
        l.addLast("<New Element>");
    else
        System.out.println(str);
}

これにより、例外が発生したり、無限ループが発生したりする可能性があります。しかし、あなたが言ったように

私の場合はそうではないと確信しています

そのようなコードのコーナーケースをチェックするのはあなたの責任です。


0

これは私が通常行うことであり、セットのようなコレクションを使用します。

Set<T> adds = new HashSet<T>, dels = new HashSet<T>;
for ( T e: target )
  if ( <has to be removed> ) dels.add ( e );
  else if ( <has to be added> ) adds.add ( <new element> )

target.removeAll ( dels );
target.addAll ( adds );

これにより、いくつかの余分なメモリ(中間セットへのポインタが作成されますが、重複する要素は発生しません)と余分なステップ(変更を繰り返します)が作成されますが、通常は大したことではなく、最初のコレクションコピーで作業するよりも優れている場合があります。


0

反復中に同じリストにアイテムを追加することはできませんが、Java 8のflatMapを使用して、ストリームに新しい要素を追加することができます。これは、条件付きで実行できます。この後、追加されたアイテムを処理できます。

これは、条件に応じてオブジェクトを進行中のストリームに追加する方法を示すJavaの例で、条件で処理されます。

List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);

intList = intList.stream().flatMap(i -> {
    if (i == 2) return Stream.of(i, i * 10); // condition for adding the extra items
    return Stream.of(i);
}).map(i -> i + 1)
        .collect(Collectors.toList());

System.out.println(intList);

おもちゃの例の出力は次のとおりです。

[2、3、21、4]


-1

では、一般的ないくつかのコレクションのために、それはかもしれないが、それは、安全ではありません。明らかな代替手段は、ある種のforループを使用することです。ただし、使用しているコレクションについては言及していなかったため、可能かどうかはわかりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.