Ultima iterazione di Enhanced for Loop in Java


140

C'è un modo per determinare se il ciclo sta iterando per l'ultima volta. Il mio codice è simile al seguente:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for(int i : array)
{
    builder.append("" + i);
    if(!lastiteration)
        builder.append(",");
}

Ora il fatto è che non voglio aggiungere la virgola nell'ultima iterazione. Ora c'è un modo per determinare se è l'ultima iterazione o sono bloccato con il ciclo for o usando un contatore esterno per tenere traccia.


1
Si! È divertente, volevo solo fare la stessa identica domanda!
PhiLho,

Lo stesso tipo di domanda ritorna (e lo sarà). Ora, perché dovresti creare un ciclo se un elemento necessita di un trattamento diverso? stackoverflow.com/questions/156650/...
xtofl

Dato che hai un array fisso, perché usare il potenziato per? per (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

Risposte:


223

Un'altra alternativa è aggiungere la virgola prima di aggiungere i, non solo sulla prima iterazione. (Per favore, non usare "" + i, a proposito - non vuoi davvero concatenare qui, e StringBuilder ha un sovraccarico append (int) perfettamente buono.)

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for (int i : array) {
    if (builder.length() != 0) {
        builder.append(",");
    }
    builder.append(i);
}

La cosa bella di questo è che funzionerà con qualsiasi Iterable- non puoi sempre indicizzare le cose. ("Aggiungi la virgola e poi rimuovila alla fine" è un bel suggerimento quando stai davvero usando StringBuilder - ma non funziona per cose come scrivere su stream. Probabilmente è l'approccio migliore per questo esatto problema. )


2
Buon modello, ma builder.length ()! = 0 è fragile - immagina che qualcosa venga aggiunto (condizionatamente!) Al buffer prima del tuo loop. Invece, usa una isFirstbandiera booleana. Bonus: anche più veloce.
Jason Cohen,

2
@Jason: certamente uso il modello "isFirst" in cui il builder non sarà vuoto alla prima iterazione. Tuttavia, quando non è necessario, aggiunge una buona dose di gonfiore all'implementazione nella mia esperienza.
Jon Skeet,

3
@Liverpool (ecc.): 1000 controlli superflui sono molto, molto improbabili che abbiano un impatto significativo sulle prestazioni. Potrei anche sottolineare che il carattere aggiuntivo della soluzione aggiunta di Dinah potrebbe causare l'espansione di StringBuilder, raddoppiando la dimensione della stringa finale con (cont)
Jon Skeet,

2
spazio buffer non necessario. Tuttavia, il punto importante è la leggibilità. Mi capita di trovare la mia versione più leggibile di quella di Dinah. Se senti il ​​contrario, va bene. Considererei l'impatto sulle prestazioni degli "if" non necessari dopo aver trovato un collo di bottiglia.
Jon Skeet,

3
Inoltre, la mia soluzione è generalmente più applicabile, poiché si basa solo sulla capacità di scrivere. Potresti prendere la mia soluzione e cambiarla per scrivere su uno stream, ecc. Dove potresti non essere in grado di recuperare i dati non necessari in seguito. Mi piacciono i modelli che sono generalmente applicabili.
Jon Skeet,

145

Un altro modo per farlo:

String delim = "";
for (int i : ints) {
    sb.append(delim).append(i);
    delim = ",";
}

Aggiornamento: per Java 8, ora hai Collezionisti


1
Ho dovuto eliminare la mia risposta simile - questo mi insegnerà a non postare prima di guardare più attentamente le altre risposte.
Michael Burr,

1
Si noti inoltre che ciò gestisce il potenziale problema citato da Robert Paulson in un altro thread di commenti: questa tecnica non dipende dal fatto che StringBuilder sia vuoto quando vengono aggiunti i valori dell'array.
Michael Burr,

1
Sebbene il codice sia più fluido / ordinato / divertente, è meno ovvio della versione if. Usa sempre la versione più ovvia / leggibile.
Bill K,

8
@Bill: non è eccezionale come regola dura e veloce; ci sono momenti in cui la soluzione "intelligente" è la soluzione "più corretta". Più precisamente, credi davvero che questa versione sia difficile da leggere? In ogni caso, un manutentore avrebbe bisogno di superarlo - non credo che la differenza sia significativa.
Greg Case,

Funziona anche se si scrive su altre risorse come le chiamate di scrittura del filesystem. Buona idea.
Tuxman,

39

Potrebbe essere più facile aggiungere sempre. E poi, quando hai finito con il tuo loop, rimuovi il personaggio finale. Tonnellate di meno anche in questo modo.

È possibile utilizzare StringBuilder's deleteCharAt(int index)con indice di benesserelength() - 1


5
la soluzione più efficiente per questo problema. +1.
Rosso reale.

9
Forse sono io, ma non mi piace davvero il fatto che stai rimuovendo qualcosa che hai appena aggiunto ...
Fortega

2
Fortega: Non mi piace controllare ogni volta qualcosa che farò il 99% delle volte. Sembra più logico (ed È più veloce) applicare la soluzione di Dinah o sblundy.
Buffalo,

1
La soluzione più elegante secondo me. Grazie!
Charles Morin,

32

Forse stai usando lo strumento sbagliato per il lavoro.

Questo è più manuale di quello che stai facendo, ma è in un modo più elegante se non un po '"vecchia scuola"

 StringBuffer buffer = new StringBuffer();
 Iterator iter = s.iterator();
 while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext()) {
            buffer.append(delimiter);
      }
 }


14

Un'altra soluzione (forse la più efficiente)

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();

    if (array.length != 0) {
        builder.append(array[0]);
        for (int i = 1; i < array.length; i++ )
        {
            builder.append(",");
            builder.append(array[i]);
        }
    }

Questa è una soluzione naturale, tranne per il fatto che l'array non è vuoto.
orcmid

5
@orcmid: se l'array è vuoto, ciò fornisce comunque l'output corretto: una stringa vuota. Non sono sicuro di quale sia il tuo punto.
Eddie,

7

mantienilo semplice e usa uno standard per loop:

for(int i = 0 ; i < array.length ; i ++ ){
    builder.append(array[i]);
    if( i != array.length - 1 ){
        builder.append(',');
    }
}

o usa semplicemente apache commons-lang StringUtils.join ()


6

I loop espliciti funzionano sempre meglio di quelli impliciti.

builder.append( "" + array[0] );
for( int i = 1; i != array.length; i += 1 ) {
   builder.append( ", " + array[i] );
}

Dovresti racchiudere il tutto in un'istruzione if nel caso in cui tu abbia a che fare con un array di lunghezza zero.


Mi piace questo approccio perché non esegue inutilmente un test ogni iterazione. L'unica cosa che aggiungerei è che il builder può aggiungere direttamente numeri interi, quindi non è necessario usare "" + int, basta aggiungere (array [0]).
Josh,

Ne hai bisogno di un altro se in tutto il lotto per assicurarti che l'array abbia almeno un elemento.
Tom Leys,

2
perché tutti continuano a usare esempi con concatenazione di stringhe INSIDE di append? il "," + x viene compilato nel nuovo StringBuilder (",") .append (x) .toString () ...
John Gardner,

@Josh e @John: sto solo seguendo l'esempio n00b. Non voglio introdurre troppe cose contemporaneamente. Il tuo punto è molto buono, comunque.
S. Lott,

@ Tom: giusto. Penso di averlo già detto. Nessuna modifica richiesta.
S. Lott,

4

Se lo converti in un classico ciclo indice, sì.

Oppure potresti semplicemente eliminare l'ultima virgola dopo averlo fatto. Così:

int[] array = {1, 2, 3...};
StringBuilder

builder = new StringBuilder();

for(int i : array)
{
    builder.append(i + ",");
}

if(builder.charAt((builder.length() - 1) == ','))
    builder.deleteCharAt(builder.length() - 1);

Io uso solo StringUtils.join()da commons-lang .


3

È necessario il separatore di classe .

Separator s = new Separator(", ");
for(int i : array)
{
     builder.append(s).append(i);
}

L'implementazione della classe Separatorè semplice. Avvolge una stringa che viene restituita ad ogni chiamata toString()fatta eccezione per la prima chiamata, che restituisce una stringa vuota.


3

Basato su java.util.AbstractCollection.toString (), esce presto per evitare il delimitatore.

StringBuffer buffer = new StringBuffer();
Iterator iter = s.iterator();
for (;;) {
  buffer.append(iter.next());
  if (! iter.hasNext())
    break;
  buffer.append(delimiter);
}

È efficiente ed elegante, ma non così evidente come alcune delle altre risposte.


Hai dimenticato di includere il ritorno anticipato quando (! i.hasNext()), che è una parte importante della solidità dell'approccio globale. (Le altre soluzioni qui gestiscono raccolte vuote con garbo, quindi anche le tue! :)
Mike Clark,

3

Ecco una soluzione:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();
bool firstiteration=true;

for(int i : array)
{
    if(!firstiteration)
        builder.append(",");

    builder.append("" + i);
    firstiteration=false;
}

Cerca la prima iterazione :)  


3

Come accennato al toolkit, in Java 8 ora abbiamo Collezionisti . Ecco come sarebbe il codice:

String joined = array.stream().map(Object::toString).collect(Collectors.joining(", "));

Penso che faccia esattamente quello che stai cercando, ed è uno schema che potresti usare per molte altre cose.


1

Ancora un'altra opzione.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);

Ho fatto una variazione sull'altra domanda
13ren

1

Molte delle soluzioni descritte qui sono un po 'esagerate, IMHO, in particolare quelle che si basano su librerie esterne. C'è un bel linguaggio pulito e chiaro per ottenere un elenco separato da virgole che ho sempre usato. Si basa sull'operatore condizionale (?):

Modifica : soluzione originale corretta, ma non ottimale secondo i commenti. Provare una seconda volta:

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();
    for (int i = 0 ;  i < array.length; i++)
           builder.append(i == 0 ? "" : ",").append(array[i]); 

Ecco fatto, in 4 righe di codice tra cui la dichiarazione dell'array e StringBuilder.


Ma stai creando un nuovo StringBuilder su ogni iterazione (grazie all'operatore +).
Michael Myers

Sì, se guardi nel bytecode, hai ragione. Non posso credere che una domanda così semplice possa diventare così complicata. Mi chiedo se il compilatore può ottimizzare qui.
Julien Chastang,

Fornita la seconda soluzione, si spera migliore.
Julien Chastang,

1

Ecco un benchmark SSCCE che ho eseguito (in relazione a ciò che dovevo implementare) con questi risultati:

elapsed time with checks at every iteration: 12055(ms)
elapsed time with deletion at the end: 11977(ms)

Almeno sul mio esempio, saltare il controllo ad ogni iterazione non è notevolmente più veloce, specialmente per volumi di dati sani, ma è più veloce.

import java.util.ArrayList;
import java.util.List;


public class TestCommas {

  public static String GetUrlsIn(int aProjectID, List<String> aUrls, boolean aPreferChecks)
  {

    if (aPreferChecks) {

      StringBuffer sql = new StringBuffer("select * from mytable_" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {

        if (inHashes.length() > 0) {
        inHashes.append(",");
        inURLs.append(",");
        }

        inHashes.append(url.hashCode());

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"");//.append(",");

      }

      }

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

    else {

      StringBuffer sql = new StringBuffer("select * from mytable" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {
        inHashes.append(url.hashCode()).append(","); 

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"").append(",");
      }

      }

      inHashes.deleteCharAt(inHashes.length()-1);
      inURLs.deleteCharAt(inURLs.length()-1);

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

  }

  public static void main(String[] args) { 
        List<String> urls = new ArrayList<String>();

    for (int i = 0; i < 10000; i++) {
      urls.add("http://www.google.com/" + System.currentTimeMillis());
      urls.add("http://www.yahoo.com/" + System.currentTimeMillis());
      urls.add("http://www.bing.com/" + System.currentTimeMillis());
    }


    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, true);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("elapsed time with checks at every iteration: " + (endTime-startTime) + "(ms)");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, false);
    }
    endTime = System.currentTimeMillis();
    System.out.println("elapsed time with deletion at the end: " + (endTime-startTime) + "(ms)");
  }
}

1
Si prega di non lanciare i propri parametri di riferimento e utilizzare il proprio cablaggio di micro benchmarking OpenJDK (jmh) . Riscalderà il tuo codice ed eviterà le insidie ​​più problematiche.
René,

0

Un altro approccio è quello di avere la lunghezza dell'array (se disponibile) memorizzata in una variabile separata (più efficiente che ricontrollare la lunghezza ogni volta). È quindi possibile confrontare il proprio indice con quella lunghezza per determinare se aggiungere o meno la virgola finale.

MODIFICA: Un'altra considerazione è la valutazione del costo delle prestazioni della rimozione di un carattere finale (che può causare una copia di stringa) rispetto al controllo di un condizionale in ogni iterazione.


La rimozione di un carattere finale non dovrebbe comportare la creazione di una copia di stringa. I metodi delete / deleteCharAt di StringBuffer alla fine chiamano il seguente metodo all'interno della classe System identica array di origine e destinazione: Sistema -> arraycopy vuoto nativo statico pubblico (Object src, int srcPos, Object dest, int destPos, int length);
Buffalo,

0

Se stai solo trasformando un array in un array delimitato da virgole, molte lingue hanno una funzione join esattamente per questo. Trasforma un array in una stringa con un delimitatore tra ciascun elemento.


0

In questo caso non c'è davvero bisogno di sapere se è l'ultima ripetizione. Ci sono molti modi in cui possiamo risolverlo. Un modo sarebbe:

String del = null;
for(int i : array)
{
    if (del != null)
       builder.append(del);
    else
       del = ",";
    builder.append(i);
}

0

Due percorsi alternativi qui:

1: Utilità di stringa di Apache Commons

2: Mantieni un booleano chiamato first, impostato su vero. In ogni iterazione, se firstè falso, aggiungi la tua virgola; successivamente, impostare firstsu falso.


1) Il link è morto 2) Mi ci è voluto più tempo per leggere la descrizione piuttosto che il codice :)
Buffalo

0

Dal momento che è un array fisso, sarebbe più semplice evitare il potenziato per ... Se l'oggetto è una raccolta un iteratore sarebbe più semplice.

int nums[] = getNumbersArray();
StringBuilder builder = new StringBuilder();

// non enhanced version
for(int i = 0; i < nums.length; i++){
   builder.append(nums[i]);
   if(i < nums.length - 1){
       builder.append(",");
   }   
}

//using iterator
Iterator<int> numIter = Arrays.asList(nums).iterator();

while(numIter.hasNext()){
   int num = numIter.next();
   builder.append(num);
   if(numIter.hasNext()){
      builder.append(",");
   }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.