Добавление элементов в коллекцию во время итерации - PullRequest
73 голосов
/ 14 июня 2009

Можно ли добавить элементы в коллекцию при переборах по ней?

Более конкретно, я хотел бы перебрать коллекцию, и если элемент удовлетворяет определенному условию, я хочу добавить некоторые другие элементы в коллекцию и убедиться, что эти добавленные элементы также повторяются. (Я понимаю, что это может привести к бесконечному циклу, но я уверен, что в моем случае этого не произойдет.)

Учебное пособие Java от Sun предполагает, что это невозможно: «Обратите внимание, что Iterator.remove - это безопасный только безопасный способ изменения коллекции во время итерации; поведение не определено, если базовая коллекция изменяется любым другим способом, пока выполняется итерация. "

Так что, если я не могу сделать то, что я хочу, используя итераторы, что вы предлагаете мне сделать?

Ответы [ 18 ]

59 голосов
/ 14 июня 2009

Как насчет построения очереди с элементами, которые вы хотите перебрать; когда вы хотите добавить элементы, поставьте их в очередь в конце очереди и продолжайте удалять элементы, пока очередь не станет пустой. Так обычно работает поиск в ширину.

46 голосов
/ 14 июня 2009

Здесь есть два вопроса:

Первая проблема заключается в добавлении к 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);
}

Редактировать

Обработка Ответ Ави , можно поставить в очередь элементы, которые мы хотим перебрать в очередь, и удалить элементы, пока в очереди есть элементы. Это позволит «перебирать» новые элементы в дополнение к исходным.

Давайте посмотрим, как это будет работать.

Концептуально, если в очереди есть следующие элементы:

[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.

8 голосов
/ 14 июня 2009

Вы также можете взглянуть на некоторые из более специализированных типов, например ListIterator , NavigableSet и (если вам интересны карты) NavigableMap .

4 голосов
/ 17 января 2010

На самом деле это довольно просто. Просто подумай об оптимальном пути. Я считаю, что оптимальный способ:

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 с помощью флага, который помечает их, чтобы не повторять их.

2 голосов
/ 15 ноября 2017

Используйте 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");
    }
}

Ключ должен повторяться в обратном порядке - тогда добавленные элементы появляются на следующей итерации.

1 голос
/ 14 июня 2009

Использование итераторов ... нет, я так не думаю. Вам придется взломать что-то вроде этого:

    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, добавленный элемент 1, добавленный элемент 2]

1 голос
/ 14 октября 2018

Я знаю, это было довольно старым. Но думал о его какой-либо пользе для кого-либо еще. Недавно я столкнулся с подобной проблемой, когда мне нужна очередь, которую можно изменять во время итерации. Я использовал listIterator, чтобы реализовать то же самое в тех же строках, что и то, что предложил Avi -> Ответ Avi . Посмотрите, подойдет ли это вам.

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 голос
/ 25 ноября 2015

Например, у нас есть два списка:

  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

1 голос
/ 03 ноября 2011
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);
        }
    }
}
0 голосов
/ 14 июня 2009

Я предпочитаю обрабатывать коллекции функционально, а не изменять их на месте. Это полностью исключает проблемы такого рода, а также проблемы наложения имен и другие хитрые источники ошибок.

Итак, я бы реализовал это так:

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