Java: добавление элементов в коллекцию во время итерации



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



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



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



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

1227   17  

17 ответов:

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

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

первый вопрос, добавление к 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);
}

Edit

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

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

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

[1, 2, 3, 4]

и, когда мы удалить 1, мы решили добавить 42, очередь будет выглядеть следующим образом:

[2, 3, 4, 42]

поскольку очередь является ФИФО (first-in, first-out) структура данных, этот заказ типичный. (Как отмечено в документации для 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 который был добавлен, когда мы попали .

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

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

    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 следующим образом:

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

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

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

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

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 более безопасным способом было бы создать новую коллекцию, перебирать заданную коллекцию, добавлять каждый элемент в новую коллекцию и добавлять дополнительные элементы по мере необходимости в новую коллекцию, а затем возвращать новую коллекцию.

помимо решения использования дополнительного списка и вызова addAll для вставки новых элементов после итерации (например, решение пользователя Nat), вы также можете использовать параллельные коллекции, такие как CopyOnWriteArrayList.

метод итератора стиля "снимок" использует ссылку на состояние массива в точке, в которой был создан итератор. Этот массив никогда не изменяется в течение времени жизни итератора, поэтому интерференция невозможна и итератор гарантированно не бросает ConcurrentModificationException.

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

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

приведенный список List<Object> который вы хотите повторить, простой способ:

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

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

   list.remove(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++;
        }
    }

Я устал 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);
}

Это может дать исключение или запустить в бесконечные циклы. Однако, как вы уже упомянули

я почти уверен, что это не будет в моем случае

проверка угловых случаев в таком коде является вашей ответственностью.

Это то, что я обычно делаю, с коллекциями, как устанавливает:

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

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

хотя мы не можем добавлять элементы в один и тот же список во время итерации, мы можем использовать flatMap Java 8 для добавления новых элементов в поток. Это можно сделать при одном условии. После этого добавленный элемент может быть обработан.

вот пример 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]

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

Comments

    Ничего не найдено.