Java: aggiunta di elementi a una raccolta durante l’iterazione

È ansible aggiungere elementi a una raccolta mentre si sta iterando su di essa?

Più specificamente, vorrei ripetere su una collezione, e se un elemento soddisfa una determinata condizione, voglio aggiungere altri elementi alla collezione, e assicurarmi che anche questi elementi aggiunti vengano ripetuti. (Mi rendo conto che questo potrebbe portare a un ciclo non terminato, ma sono abbastanza sicuro che non lo farà nel mio caso.)

Java Tutorial di Sun suggerisce che ciò non è ansible: “Notare che Iterator.remove è l’ unico modo sicuro per modificare una raccolta durante l’iterazione, il comportamento non è specificato se la raccolta sottostante viene modificata in qualsiasi altro modo mentre l’iterazione è in corso. ”

Quindi se non posso fare quello che voglio fare usando gli iteratori, cosa suggerisci che io faccia?

    Che ne dici di build una coda con gli elementi che vuoi ripetere? quando si desidera aggiungere elementi, accodarli alla fine della coda e continuare a rimuovere elementi finché la coda non è vuota. Questo è il modo in cui solitamente funziona una ricerca in ampiezza.

    Ci sono due problemi qui:

    Il primo problema è l’aggiunta a una Collection dopo che viene restituito un Iterator . Come accennato, non esiste un comportamento definito quando la Collection sottostante viene modificata, come indicato nella documentazione di Iterator.remove :

    … Il comportamento di un iteratore non è specificato se la raccolta sottostante viene modificata mentre l’iterazione è in corso in un modo diverso dal chiamare questo metodo.

    Il secondo problema è che, anche se è ansible ottenere un Iterator e quindi tornare allo stesso elemento a cui si trovava Iterator , non c’è alcuna garanzia sull’ordine di iterazione, come indicato nella documentazione del metodo Collection.iterator :

    … Non ci sono garanzie riguardo all’ordine in cui gli elementi vengono restituiti (a meno che questa raccolta non sia un’istanza di una class che fornisce una garanzia).

    Ad esempio, diciamo che abbiamo la lista [1, 2, 3, 4] .

    Diciamo che 5 stato aggiunto quando l’ Iterator era a 3 , e in qualche modo, otteniamo un Iterator che può riprendere l’iterazione da 4 . Tuttavia, non vi è alcuna garanzia che 5 arriverà dopo il 4 . L’ordine di ripetizione può essere [5, 1, 2, 3, 4] – quindi l’iteratore mancherà ancora l’elemento 5 .

    Dato che non c’è garanzia per il comportamento, non si può presumere che le cose accadranno in un certo modo.

    Un’alternativa potrebbe essere quella di avere una Collection separata a cui gli elementi appena creati possono essere aggiunti e quindi scorrere su quegli elementi:

     Collection list = Arrays.asList(new String[]{"Hello", "World!"}); Collection additionalList = new ArrayList(); 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); } 

    modificare

    Elaborando la risposta di Avi , è ansible accodare gli elementi che vogliamo ripetere in una coda e rimuovere gli elementi mentre la coda ha degli elementi. Ciò consentirà l ‘”iterazione” sui nuovi elementi oltre agli elementi originali.

    Diamo un’occhiata a come funzionerebbe.

    Concettualmente, se abbiamo i seguenti elementi in coda:

    [1, 2, 3, 4]

    E, quando rimuoviamo 1 , decidiamo di aggiungere 42 , la coda sarà la seguente:

    [2, 3, 4, 42]

    Poiché la coda è una struttura di dati FIFO (first-in, first-out), questo ordine è tipico. (Come indicato nella documentazione per l’interfaccia Queue , questa non è una necessità di una Queue . Prendiamo il caso di PriorityQueue che ordina gli elementi tramite il loro ordinamento naturale, quindi non è FIFO.)

    Di seguito è riportato un esempio che utilizza una LinkedList (che è una Queue ) per passare attraverso tutti gli elementi insieme agli elementi aggiuntivi aggiunti durante la dequalificazione. Simile all’esempio precedente, l’elemento 42 viene aggiunto quando l’elemento 2 viene rimosso:

     Queue queue = new LinkedList(); 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); } 

    Il risultato è il seguente:

     1 2 3 4 42 

    Come speravo, è apparso l’elemento 42 che è stato aggiunto quando abbiamo colpito 2 .

    Potresti anche voler guardare alcuni dei tipi più specializzati, come ListIterator , NavigableSet e (se sei interessato alle mappe) NavigableMap .

    In realtà è piuttosto facile. Pensa solo per il modo ottimale. Credo che il modo ottimale sia:

     for (int i=0; i 

    L'esempio seguente funziona perfettamente nel caso più logico - quando non è necessario ripetere i nuovi elementi aggiunti prima dell'elemento di iterazione. Informazioni sugli elementi aggiunti dopo l'elemento di iterazione: potrebbe non essere necessario eseguirne l'iterazione. In questo caso dovresti semplicemente aggiungere / o estendere l'object yr con una bandiera che li contrassegnerà per non iterarli.

    Usando gli iteratori … no, non la penso così. Dovrai hackerare insieme qualcosa del genere:

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

    Quali anni:
    [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 source = new ArrayList(); // This is the list where you actually keep all unprocessed candidates LinkedList list = new LinkedList(); // 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); } } } 

    Per esempio abbiamo due liste:

      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

    Preferisco elaborare le raccolte dal punto di vista funzionale anziché trasformarle in posizione. Questo evita completamente questo tipo di problema, così come problemi di aliasing e altre fonti di bug insidiose.

    Quindi, lo implementerei come:

     List expand(List inputs) { List expanded = new ArrayList(); for (Thing thing : inputs) { expanded.add(thing); if (needsSomeMoreThings(thing)) { addMoreThingsTo(expanded); } } return expanded; } 

    IMHO il modo più sicuro sarebbe creare una nuova collezione, per iterare sulla tua collezione, aggiungere ogni elemento nella nuova collezione e aggiungere elementi aggiuntivi, se necessario, anche alla nuova collezione, restituendo finalmente la nuova collezione.

    Oltre alla soluzione dell’utilizzo di un elenco aggiuntivo e alla chiamata di addAll per inserire i nuovi elementi dopo l’iterazione (ad esempio, la soluzione dell’utente Nat), è ansible utilizzare anche raccolte concorrenti come CopyOnWriteArrayList .

    Il metodo di iterazione di stile “snapshot” utilizza un riferimento allo stato dell’array nel punto in cui è stato creato l’iteratore. Questo array non cambia mai durante il ciclo di vita dell’iteratore, quindi l’interferenza è imansible e l’iteratore è garantito non lanciare ConcurrentModificationException.

    Con questa raccolta speciale (di solito utilizzata per l’accesso simultaneo) è ansible manipolare l’elenco sottostante mentre si itera su di esso. Tuttavia, l’iteratore non rifletterà le modifiche.

    È meglio dell’altra soluzione? Probabilmente no, non conosco l’overhead introdotto dall’approccio Copy-On-Write.

    Dato un elenco di List cui vuoi ripetere l’iterazione, il modo facile-peasy è:

     while (!list.isEmpty()){ Object obj = list.get(0); // do whatever you need to // possibly list.add(new Object obj1); list.remove(0); } 

    Quindi, si scorre un elenco, prendendo sempre il primo elemento e quindi rimuovendolo. In questo modo è ansible aggiungere nuovi elementi all’elenco durante l’iterazione.

    Dimentica gli iteratori, non funzionano per l’aggiunta, solo per la rimozione. La mia risposta si applica solo agli elenchi, quindi non punirmi per non aver risolto il problema per le raccolte. Attenersi alle regole di base:

      List myList = new ArrayList(); // 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++; } } 

    Ho stancato ListIterator ma non ha aiutato il mio caso, in cui devi utilizzare l’elenco mentre aggiungo ad esso. Ecco cosa funziona per me:

    Usa LinkedList .

     LinkedList l = new LinkedList(); l.addLast("A"); while(!l.isEmpty()){ String str = l.removeFirst(); if(/* Condition for adding new element*/) l.addLast(""); else System.out.println(str); } 

    Questo potrebbe dare un’eccezione o correre in loop infiniti. Tuttavia, come hai menzionato

    Sono abbastanza sicuro che non lo farà nel mio caso

    il controllo dei casi d’angolo in tale codice è responsabilità dell’utente.

    Questo è quello che faccio di solito, con raccolte come set:

     Set adds = new HashSet, dels = new HashSet; for ( T e: target ) if (  ) dels.add ( e ); else if (  ) adds.add (  ) target.removeAll ( dels ); target.addAll ( adds ); 

    Questo crea un po ‘di memoria extra (i puntatori per gli insiemi intermedi, ma non si verificano elementi duplicati) e extra-passaggi (ripetendo di nuovo le modifiche), ma di solito non è un grosso problema e potrebbe essere meglio che lavorare con una copia iniziale della collezione.

    Anche se non possiamo aggiungere elementi alla stessa lista durante l’iterazione, possiamo usare flatMap di Java 8, per aggiungere nuovi elementi a uno stream. Questo può essere fatto a condizione. Dopo questo l’articolo aggiunto può essere elaborato.

    Ecco un esempio di Java che mostra come aggiungere al stream in corso un object in base a una condizione che viene quindi elaborata con una condizione:

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

    L’output dell’esempio del giocattolo è:

    [2, 3, 21, 4]

    Utilizzare ListIterator come segue:

     List l = new ArrayList<>(); l.add("Foo"); ListIterator iter = l.listIterator(l.size()); while(iter.hasPrevious()){ String prev=iter.previous(); if(true /*You condition here*/){ iter.add("Bah"); iter.add("Etc"); } } 

    La chiave è di scorrere in ordine inverso – quindi gli elementi aggiunti appaiono nella successiva iterazione.

    In generale , non è sicuro, anche se per alcune collezioni potrebbe essere. L’alternativa ovvia è usare un qualche tipo di ciclo. Ma non hai detto quale collezione stai usando, in modo che possa o meno essere ansible.