コレクションを反復しながら要素をコレクションに追加することは可能ですか?
具体的には、コレクションを反復処理し、要素が特定の条件を満たす場合、コレクションに他の要素を追加し、これらの追加された要素も反復処理されるようにします。 (このcouldがループを終了させないことを理解していますが、私の場合はそうではないと確信しています。)
Sunの Java Tutorial は、これは不可能であることを示唆しています。「Iterator.remove
はonly反復中にコレクションを変更する安全な方法です。反復の進行中に、基になるコレクションが他の方法で変更された場合の動作は指定されていません。」
イテレータを使用してやりたいことを実行できない場合、どうすればよいですか?
繰り返したい要素でキューを構築するのはどうですか。要素を追加する場合は、キューの最後にそれらをキューに入れ、キューが空になるまで要素を削除し続けます。これは通常、幅優先検索の仕組みです。
ここには2つの問題があります。
最初の問題は、Collection
が返された後にIterator
に追加することです。前述のように、Iterator.remove
のドキュメントに記載されているように、基になるCollection
が変更されたときの動作は定義されていません。
...反復がこのメソッドを呼び出す以外の方法で進行中に、基になるコレクションが変更された場合、イテレータの動作は指定されません。
2番目の問題は、Iterator
を取得し、Iterator
と同じ要素に戻った場合でも、反復の順序に関する保証はありません。 Collection.iterator
メソッドのドキュメント:
...要素が返される順序に関する保証はありません(このコレクションが保証を提供するクラスのインスタンスでない限り)。
たとえば、[1, 2, 3, 4]
というリストがあるとします。
Iterator
が5
にあるときに3
が追加されたとしましょう。どういうわけか、4
から反復を再開できるIterator
を取得します。ただし、5
が4
の後に来るという保証はありません。反復順序は[5, 1, 2, 3, 4]
である場合があります。その場合、イテレータはまだ要素5
を見逃します。
振る舞いに対する保証がないため、物事が特定の方法で発生するとは想定できません。
別の方法として、新たに作成された要素を追加できる別の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);
}
編集
Avi's answer について詳しく説明すると、繰り返したい要素をキューに入れて、キューに要素がある間は要素を削除することができます。これにより、元の要素に加えて、新しい要素に対する「反復」が可能になります。
それがどのように機能するか見てみましょう。
概念的に、キューに次の要素がある場合:
[1, 2, 3, 4]
そして、1
を削除すると、42
を追加することになり、キューは次のようになります。
[2, 3, 4, 42]
キューは [〜#〜] fifo [〜#〜] (先入れ先出し)データ構造であるため、この順序が一般的です。 ( Queue
インターフェイスのドキュメントに記載されているように、これはQueue
の必要性ではありません。 PriorityQueue
の場合を考えてください=自然な順序で要素を順序付けするため、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
が表示されました。
ListIterator 、 NavigableSet 、および(マップに興味がある場合) NavigableMap 。
実際にはかなり簡単です。最適な方法を考えてください。最適な方法は次のとおりです。
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オブジェクトを単に追加または拡張する必要があります。
次のように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");
}
}
重要なのは、reverseの順序で反復することです。追加された要素は、次の反復で表示されます。
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);
}
}
}
私はそれがかなり古いことを知っています。しかし、それは他の誰にも役立つと考えました。最近、反復中に変更可能なキューが必要なこのような問題に遭遇しました。 listIteratorを使用して、Aviが提案したものと同じ行に同じものを実装しました-> Avi's Answer 。これがニーズに合うかどうかを確認してください。
ModifyWhileIterateQueue.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;
}
}
ModifyWhileIterateQueueTest.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());
}
たとえば、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
イテレータを使用しています...いいえ、そうは思いません。このようなものを一緒にハックする必要があります:
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]
繰り返し中に同じリストにアイテムを追加することはできませんが、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]
これは、セットのようなコレクションで私が通常行うことです:
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 );
これにより、余分なメモリ(中間セットへのポインタ、ただし重複する要素は発生しません)と余分なステップ(変更を繰り返して繰り返す)が作成されますが、通常は大した問題ではなく、初期コレクションコピーを使用するよりも優れている場合があります。
イテレータについては忘れてください。イテレータは追加ではなく、削除のみで機能します。私の答えはリストにのみ当てはまるので、コレクションの問題を解決しなかったからと言って罰せないでください。基本にこだわる:
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++;
}
}
コレクションを適切に変更するのではなく、機能的に処理することを好みます。これにより、この種の問題が完全に回避され、エイリアスの問題やその他の厄介なバグの原因が回避されます。
だから、私はそれを次のように実装します:
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;
}
リストList<Object>
繰り返したいのですが、簡単な方法は次のとおりです。
while (!list.isEmpty()){
Object obj = list.get(0);
// do whatever you need to
// possibly list.add(new Object obj1);
list.remove(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);
}
これにより、例外が発生したり、無限ループが発生したりする可能性があります。しかし、あなたが言及したように
私の場合はそうではないと確信しています
そのようなコードのコーナーケースをチェックするのはあなたの責任です。
より安全な方法は、新しいコレクションを作成し、指定されたコレクションを反復処理し、新しいコレクションに各要素を追加し、必要に応じて新しいコレクションに要素を追加して、最終的に新しいコレクションを返すことです。
追加リストを使用し、繰り返し後にaddAllを呼び出して新しいアイテムを挿入するソリューション(たとえば、ユーザーNatによるソリューション)に加えて、 CopyOnWriteArrayList のような同時コレクションを使用することもできます。
「スナップショット」スタイルのイテレータメソッドは、イテレータが作成された時点での配列の状態への参照を使用します。この配列はイテレーターの存続期間中に変更されることはないため、干渉は不可能であり、イテレーターはConcurrentModificationExceptionをスローしないことが保証されています。
この特別なコレクション(通常は同時アクセスに使用されます)を使用すると、基になるリストを繰り返し処理しながら操作できます。ただし、イテレータは変更を反映しません。
これは他のソリューションよりも優れていますか?おそらくそうではありませんが、Copy-On-Writeアプローチによって生じるオーバーヘッドはわかりません。