Dernière itération de la boucle for améliorée en java

140

Existe-t-il un moyen de déterminer si la boucle est itérée pour la dernière fois? Mon code ressemble à ceci:

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

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

Maintenant, le problème est que je ne veux pas ajouter de virgule dans la dernière itération. Existe-t-il maintenant un moyen de déterminer si c'est la dernière itération ou si je suis coincé avec la boucle for ou si j'utilise un compteur externe pour garder une trace.

Mercurieux
la source
1
Ouais! C'est drôle, je voulais juste poser exactement la même question!
PhiLho
Le même genre de question revient (et le sera). Maintenant, pourquoi voudriez-vous créer une boucle si un élément nécessite un traitement différent? stackoverflow.com/questions/156650/…
xtofl
Puisque vous avez un tableau fixe, pourquoi utiliser la version améliorée pour? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

Réponses:

223

Une autre alternative consiste à ajouter la virgule avant d'ajouter i, mais pas lors de la première itération. (Veuillez ne pas utiliser "" + i, au fait - vous ne voulez pas vraiment de concaténation ici, et StringBuilder a une surcharge d'ajout (int) parfaitement bonne.)

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

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

La bonne chose à ce sujet est que cela fonctionnera avec n'importe lequel Iterable- vous ne pouvez pas toujours indexer les choses. (Le "ajouter la virgule puis la supprimer à la fin" est une bonne suggestion lorsque vous utilisez vraiment StringBuilder - mais cela ne fonctionne pas pour des choses comme l'écriture dans des flux. C'est probablement la meilleure approche pour ce problème précis. )

Jon Skeet
la source
2
Bon modèle, mais builder.length ()! = 0 est fragile - imaginez que quelque chose soit ajouté (conditionnellement!) Au tampon avant votre boucle. À la place, utilisez un isFirstindicateur booléen. Bonus: plus rapide aussi.
Jason Cohen
2
@Jason: J'utilise certainement le modèle "isFirst" où le constructeur ne sera pas vide lors de la première itération. Cependant, quand ce n'est pas nécessaire, cela ajoute une bonne dose de gonflement à la mise en œuvre selon mon expérience.
Jon Skeet
3
@Liverpool (etc): 1000 contrôles inutiles sont très, très peu susceptibles d'avoir un impact significatif sur les performances. Je pourrais également souligner que le caractère supplémentaire de la solution de Dinah ajouté pourrait obliger le StringBuilder à se développer, doublant la taille de la chaîne finale avec (suite)
Jon Skeet
2
espace tampon inutile. Cependant, le point important est la lisibilité. Il se trouve que je trouve ma version plus lisible que celle de Dinah. Si vous ressentez le contraire, c'est bien. Je ne considérerais l'impact sur les performances des «si» inutiles qu'après avoir trouvé un goulot d'étranglement.
Jon Skeet
3
De plus, ma solution est plus généralement applicable, car elle ne repose que sur la capacité d'écrire. Vous pouvez prendre ma solution et la modifier pour écrire dans un flux, etc., où vous ne pourrez peut-être pas récupérer les données inutiles par la suite. J'aime les modèles qui sont généralement applicables.
Jon Skeet
145

Une autre façon de procéder:

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

Mise à jour: pour Java 8, vous disposez désormais de collecteurs

boîte à outils
la source
1
J'ai dû supprimer ma réponse similaire - cela m'apprendra à ne pas publier avant de regarder plus attentivement les autres réponses.
Michael Burr
1
Notez également que cela gère le problème potentiel mentionné par Robert Paulson dans un autre fil de commentaires - cette technique ne dépend pas du fait que StringBuilder est vide lorsque les valeurs du tableau sont ajoutées.
Michael Burr
1
Bien que le code soit plus lisse / soigné / amusant, il est moins évident que la version if. Utilisez toujours la version la plus évidente / lisible.
Bill K
8
@Bill: Ce n'est pas génial en tant que règle absolue; il y a des moments où la solution «intelligente» est la solution «la plus correcte». Plus précisément, pensez-vous vraiment que cette version est difficile à lire? Dans tous les cas, un responsable devrait le parcourir - je ne pense pas que la différence soit significative.
Greg Case
Cela fonctionne bien que vous écriviez sur une autre ressource comme les appels d'écriture du système de fichiers. Bonne idée.
Tuxman
39

Il peut être plus facile de toujours ajouter. Et puis, lorsque vous avez terminé votre boucle, supprimez simplement le caractère final. Des tonnes moins conditionnelles de cette façon aussi.

Vous pouvez utiliser StringBuilderdes deleteCharAt(int index)avec l'index étantlength() - 1

Dinah
la source
5
la solution la plus efficace à ce problème. +1.
Vraiment rouge.
9
C'est peut-être moi, mais je n'aime vraiment pas le fait que vous supprimiez quelque chose que vous venez d'ajouter ...
Fortega
2
Fortega: Je n'aime pas vérifier à chaque fois quelque chose que je ferai 99% du temps. Il semble plus logique (et c'est plus rapide) d'appliquer la solution de Dinah ou de sblundy.
Buffalo
1
La solution la plus élégante à mon avis. Je vous remercie!
Charles Morin
32

Peut-être que vous utilisez le mauvais outil pour le travail.

C'est plus manuel que ce que vous faites mais c'est en quelque sorte plus élégant sinon un peu "old school"

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

C'est presque une répétition de cette question StackOverflow . Ce que vous voulez, c'est StringUtils et appeler la méthode de jointure .

StringUtils.join(strArr, ',');
Phil H
la source
14

Une autre solution (peut-être la plus efficace)

    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]);
        }
    }
bruno conde
la source
C'est une solution naturelle sauf que cela dépend du fait que le tableau n'est pas vide.
orcmid
5
@orcmid: Si le tableau est vide, cela donne toujours la sortie correcte - une chaîne vide. Je ne suis pas sûr de votre argument.
Eddie
7

restez simple et utilisez une boucle for standard:

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

ou utilisez simplement apache commons-lang StringUtils.join ()

Gareth Davis
la source
6

Les boucles explicites fonctionnent toujours mieux que les boucles implicites.

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

Vous devriez envelopper le tout dans une instruction if juste au cas où vous auriez affaire à un tableau de longueur nulle.

S.Lott
la source
J'aime cette approche car elle n'effectue pas inutilement un test à chaque itération. La seule chose que j'ajouterais est que le constructeur peut ajouter des entiers directement, il n'est donc pas nécessaire d'utiliser "" + int, il suffit d'ajouter (array [0]).
Josh
Vous en avez besoin de plus sur tout le terrain pour vous assurer que le tableau contient au moins un élément.
Tom Leys
2
Pourquoi tout le monde continue-t-il à utiliser des exemples avec une concaténation de chaînes à L'INTÉRIEUR de l'append? le "," + x se compile dans le nouveau StringBuilder (",") .append (x) .toString () ...
John Gardner
@Josh et @John: juste en suivant l'exemple n00b. Je ne veux pas introduire trop de choses à la fois. Votre point est cependant très bon.
S.Lott
@Tom: C'est vrai. Je pense que je l'ai déjà dit. Aucune modification requise.
S.Lott
4

Si vous le convertissez en une boucle d'index classique, oui.

Ou vous pouvez simplement supprimer la dernière virgule après avoir terminé. Ainsi:

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

Moi, j'utilise juste StringUtils.join()de commons-lang .

sblundy
la source
3

Vous avez besoin d'un séparateur de classe .

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

La mise en œuvre de la classe Separatorest simple. Il encapsule une chaîne qui est retournée à chaque appel de, à l' toString()exception du premier appel, qui renvoie une chaîne vide.

Akuhn
la source
3

Basé sur java.util.AbstractCollection.toString (), il se termine tôt pour éviter le délimiteur.

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

C'est efficace et élégant, mais pas aussi évident que certaines des autres réponses.

13ren
la source
Vous avez oublié d'inclure le retour anticipé quand (! i.hasNext()), qui est un élément important de la robustesse de l'approche globale. (Les autres solutions ici gèrent les collections vides avec élégance, donc la vôtre devrait aussi! :)
Mike Clark
3

Voici une solution:

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

Recherchez la première itération :)  

DéchuAvatar
la source
3

Comme la boîte à outils l'a mentionné, dans Java 8, nous avons maintenant des collecteurs . Voici à quoi ressemblerait le code:

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

Je pense que cela fait exactement ce que vous recherchez, et c'est un modèle que vous pourriez utiliser pour beaucoup d'autres choses.

b1tw153
la source
1

Encore une autre option.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);
Peter Lawrey
la source
J'ai fait une variation à l'autre question
13ren
1

Bon nombre des solutions décrites ici sont un peu exagérées, à mon humble avis, en particulier celles qui reposent sur des bibliothèques externes. Il existe une belle expression propre et claire pour créer une liste séparée par des virgules que j'ai toujours utilisée. Il repose sur l'opérateur conditionnel (?):

Edit : Solution d'origine correcte, mais non optimale selon les commentaires. Essayer une deuxième fois:

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

Voilà, en 4 lignes de code comprenant la déclaration du tableau et le StringBuilder.

Julien Chastang
la source
Mais vous créez un nouveau StringBuilder à chaque itération (grâce à l'opérateur +).
Michael Myers
Oui, si vous regardez dans le bytecode, vous avez raison. Je ne peux pas croire qu'une question aussi simple puisse devenir si compliquée. Je me demande si le compilateur peut optimiser ici.
Julien Chastang
Fourni 2ème solution, espérons-le mieux.
Julien Chastang
1

Voici un benchmark SSCCE que j'ai exécuté (lié à ce que je devais implémenter) avec ces résultats:

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

Sur mon exemple au moins, sauter le contrôle à chaque itération est pas sensiblement plus rapide en particulier pour les volumes sains de données, mais il est plus rapide.

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)");
  }
}
Buffle
la source
1
Veuillez ne pas rouler vos propres benchmarks et utiliser le propre micro-harnais de benchmarking d'OpenJDK (jmh) . Cela réchauffera votre code et évitera les pièges les plus problématiques.
René
0

Une autre approche consiste à stocker la longueur du tableau (si disponible) dans une variable distincte (plus efficace que de revérifier la longueur à chaque fois). Vous pouvez ensuite comparer votre index à cette longueur pour déterminer s'il faut ou non ajouter la virgule finale.

EDIT: Une autre considération est de peser le coût de performance de la suppression d'un caractère final (qui peut provoquer une copie de chaîne) par rapport à la vérification d'un conditionnel à chaque itération.

Brian
la source
La suppression d'un caractère final ne devrait pas entraîner la copie d'une chaîne. Les méthodes delete / deleteCharAt de StringBuffer appellent finalement la méthode suivante dans les tableaux source et destination identiques de la classe System: System -> public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length);
Buffalo
0

Si vous ne transformez un tableau qu'en un tableau délimité par des virgules, de nombreux langages ont une fonction de jointure pour exactement cela. Il transforme un tableau en une chaîne avec un délimiteur entre chaque élément.

Dinah
la source
0

Dans ce cas, il n'est vraiment pas nécessaire de savoir s'il s'agit de la dernière répétition. Il existe de nombreuses façons de résoudre ce problème. Une solution serait:

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

Deux chemins alternatifs ici:

1: Utils de chaîne Apache Commons

2: Gardez un booléen appelé first, défini sur true. Dans chaque itération, si firstest faux, ajoutez votre virgule; après cela, définissez firstsur false.

Paul Brinkley
la source
1) Le lien est mort 2) Il m'a fallu plus de temps pour lire la description plutôt que le code :)
Buffalo
0

Puisqu'il s'agit d'un tableau fixe, il serait plus facile d'éviter simplement le pour ... Si l'objet est une collection, un itérateur serait plus facile.

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(",");
   }
}
AnthonyJClink
la source