반복 중에 컬렉션에 요소 추가


81

컬렉션을 반복하는 동안 컬렉션에 요소를 추가 할 수 있습니까?

보다 구체적으로, 컬렉션을 반복하고 요소가 특정 조건을 충족하면 컬렉션에 다른 요소를 추가하고 이러한 추가 된 요소도 반복되는지 확인합니다. (나는 이것이 끝없는 루프로 이어질 있다는 것을 알고 있지만 내 경우에는 그렇지 않을 것이라고 확신합니다.)

자바 튜토리얼 썬이 불가능합니다 제안 : "주 Iterator.remove입니다 . 반복하는 동안 컬렉션을 수정할 수있는 안전한 방법, 반복이 진행되는 동안 기본 컬렉션 다른 방법으로 변경되었을 경우의 동작은 정의되어 있지 않기 때문에이"

반복기를 사용하여 원하는 작업을 수행 할 수없는 경우 어떻게 하시겠습니까?

답변:


62

반복하려는 요소로 큐를 구축하는 것은 어떻습니까? 요소를 추가하려면 큐 끝에 큐에 넣고 큐가 비워 질 때까지 요소를 계속 제거하십시오. 이것이 일반적으로 폭 우선 검색이 작동하는 방식입니다.


2
이것은 OP가 코딩하는 모델에 맞는 경우 작업을 수행하는 좋은 방법입니다. 이렇게하면 반복자를 사용하지 않고 while 루프 만 사용할 수 있습니다. 큐에 요소가있는 동안 첫 번째 요소를 처리하십시오. 그러나 목록으로도이 작업을 수행 할 수 있습니다.
Eddie

31
ListIterator iter = list.listIterator() 모두가 add()하고 remove()추가하고 반복하는 동안 요소를 제거 할 수 있도록 방법을
soulmachine

4
@soulmachine 이것에 대해 확실합니까? 그렇게하려고하면 ConcurrentModificationException이 발생합니다.
Nieke Aerts

난 당신이 올바른라고 생각하지만, 또 다른 옵션을 사용하여 스레드 안전 컬렉션 같은이LinkedBlockingQueue
soulmachine

여기에 여전히 공백이 있다고 생각합니다. 예를 들어 역 추적 알고리즘이있는 경우 @soulmachine이 제안한대로 List 인터페이스의 형제를 사용할 필요가 없으면 Set을 사용하여 처리 할 수 ​​없습니다 (또는 어떻게?).
stdout

46

여기에는 두 가지 문제가 있습니다.

첫 번째 문제는에 추가 Collection하는 것 Iterator입니다. 언급 Collection했듯이에 대한 설명서에 명시된대로 기본 이 수정 될 때 정의 된 동작이 없습니다 Iterator.remove.

...이 메서드를 호출하는 것 이외의 방법으로 반복이 진행되는 동안 기본 컬렉션이 수정되면 반복기의 동작이 지정되지 않습니다.

두 번째 문제 는를 Iterator획득 할 수 있고 동일한 요소로 돌아가 Iterator더라도 Collection.iterator메서드 문서에 언급 된대로 반복 순서에 대한 보장이 없다는 것입니다 .

... 요소가 반환되는 순서에 대한 보장이 없습니다 (이 컬렉션이 보장을 제공하는 일부 클래스의 인스턴스가 아닌 경우).

예를 들어 목록이 있다고 가정 해 보겠습니다 [1, 2, 3, 4].

하자 말은 5(가) 때 추가 된 Iterator있었다 3, 어떻게 든, 우리는 얻을 Iterator에서 반복을 재개 할 것을 4. 그러나 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의 대답 에 대해 자세히 설명 하면 반복하려는 요소를 대기열에 넣고 대기열에 요소가있는 동안 요소를 제거 할 수 있습니다. 이렇게하면 원래 요소 외에 새 요소에 대한 "반복"이 허용됩니다.

어떻게 작동하는지 살펴 보겠습니다.

개념적으로 큐에 다음 요소가있는 경우 :

[1, 2, 3, 4]

그리고을 제거 할 때 1를 추가하기로 결정 42하면 대기열은 다음과 같습니다.

[2, 3, 4, 42]

큐가 FIFO ( 선입 선출 ) 데이터 구조이므로이 순서는 일반적입니다. ( Queue인터페이스 에 대한 문서에서 언급했듯이 , 이것은 반드시 필요한 것은 아닙니다.Queue . . PriorityQueue요소를 자연스러운 순서로 정렬하는 경우 FIFO가 아닙니다.)

다음은 사용 예이다 LinkedList(a 인 Queuedequeing 동안 첨가 추가 요소와 함께 모든 요소를 통과하기 위해). 위의 예와 유사하게 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

@Nat : 당신이 맞아요, 지적 해주셔서 감사합니다. 나는 그것을 반영하기 위해 내 대답을 편집했습니다.
coobird 2009-06-14

1
@coobird 어떤 이유로 귀하의 답변이 잘립니다. [...] 추가 el함께 모든 요소를 살펴보기 위해 – 그리고 그것이 내가 볼 수있는 전부입니다. 그러나 내가 답을 수정하려고하면 모든 것이 거기에 있습니다. 무슨 일이 일어나고 있는지 아십니까?
Kohányi Róbert


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 2013 년

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 '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());

    }

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 );

어떤 소리 :
[foo, bar, baz, added-item-1, added-item-2]


1

추가 목록을 사용하고 addAll을 호출하여 반복 후에 새 항목을 삽입하는 솔루션 (예 : 사용자 Nat의 솔루션) 외에도 CopyOnWriteArrayList 와 같은 동시 컬렉션을 사용할 수도 있습니다 .

"스냅 샷"스타일 반복기 메서드는 반복기가 생성 된 지점의 배열 상태에 대한 참조를 사용합니다. 이 배열은 반복기의 수명 동안 절대 변경되지 않으므로 간섭이 불가능하며 반복기가 ConcurrentModificationException을 throw하지 않도록 보장합니다.

이 특수 컬렉션 (일반적으로 동시 액세스에 사용됨)을 사용하면 반복하는 동안 기본 목록을 조작 할 수 있습니다. 그러나 반복자는 변경 사항을 반영하지 않습니다.

이것이 다른 솔루션보다 낫습니까? 아마 아닐 것입니다. Copy-On-Write 접근 방식으로 인한 오버 헤드를 알지 못합니다.


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

예를 들어 두 가지 목록이 있습니다.

  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

IMHO가 더 안전한 방법은 새 컬렉션을 만들고, 주어진 컬렉션을 반복하고, 새 컬렉션에 각 요소를 추가하고, 새 컬렉션에서도 필요에 따라 추가 요소를 추가하고, 마지막으로 새 컬렉션을 반환하는 것입니다.


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++;
        }
    }

감사합니다 스테판. 고쳤다.
Victor Ionescu 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.