Programing

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

lottogame 2020. 10. 17. 09:01
반응형

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


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

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

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

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


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


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

첫 번째 문제는에 추가 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나타났습니다.


또한처럼, 더 전문 유형의 일부를보고 할 수 있습니다 반복자 , 해 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");
    }
}

핵심은 역순 으로 반복하는 것입니다. 그러면 추가 된 요소가 다음 반복에 나타납니다.


반복자를 사용하여 ... 아니, 그렇게 생각하지 않습니다. 다음과 같이 함께 해킹해야합니다.

    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]


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

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

  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


꽤 오래되었다는 것을 알고 있습니다. 그러나 다른 누구에게도 쓸모가 있다고 생각했습니다. 최근에 반복 중에 수정할 수있는 대기열이 필요한 유사한 문제를 발견했습니다. 나는 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());

    }

컬렉션을 제자리에서 변경하는 것보다 기능적으로 처리하는 것을 선호합니다. 이렇게하면 앨리어싱 문제 및 기타 까다로운 버그 소스뿐만 아니라 이러한 종류의 문제를 모두 방지 할 수 있습니다.

So, I would implement it like:

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

IMHO the safer way would be to create a new collection, to iterate over your given collection, adding each element in the new collection, and adding extra elements as needed in the new collection as well, finally returning the new collection.


Besides the solution of using an additional list and calling addAll to insert the new items after the iteration (as e.g. the solution by user Nat), you can also use concurrent collections like the CopyOnWriteArrayList.

The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException.

With this special collection (usually used for concurrent access) it is possible to manipulate the underlying list while iterating over it. However, the iterator will not reflect the changes.

Is this better than the other solution? Probably not, I don't know the overhead introduced by the Copy-On-Write approach.


Given a list List<Object> which you want to iterate over, the easy-peasy way is:

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

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

   list.remove(0);
}

So, you iterate through a list, always taking the first element and then removing it. This way you can append new elements to the list while iterating.


Forget about iterators, they don't work for adding, only for removing. My answer applies to lists only, so don't punish me for not solving the problem for collections. Stick to the basics:

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

I tired ListIterator but it didn't help my case, where you have to use the list while adding to it. Here's what works for me:

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

This could give an exception or run into infinite loops. However, as you have mentioned

I'm pretty sure it won't in my case

checking corner cases in such code is your responsibility.


This is what I usually do, with collections like sets:

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

This creates some extra-memory (the pointers for intermediate sets, but no duplicated elements happen) and extra-steps (iterating again over changes), however usually that's not a big deal and it might be better than working with an initial collection copy.


Even though we cannot add items to the same list during iteration, we can use Java 8's flatMap, to add new elements to a stream. This can be done on a condition. After this the added item can be processed.

Here is a Java example which shows how to add to the ongoing stream an object depending on a condition which is then processed with a condition:

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

The output of the toy example is:

[2, 3, 21, 4]


In general, it's not safe, though for some collections it may be. The obvious alternative is to use some kind of for loop. But you didn't say what collection you're using, so that may or may not be possible.

참고URL : https://stackoverflow.com/questions/993025/adding-elements-to-a-collection-during-iteration

반응형