Meilleur Loop Idiom pour boîtier spécial du dernier élément

Je suis souvent confronté à ce cas lors de traitements de texte simples et d’impressions où je boucle sur une collection et je veux mettre le dernier élément en majuscule (par exemple, chaque élément normal sera séparé par une virgule, sauf pour le dernier cas).

Existe-t-il un idiome de meilleure pratique ou une forme élégante qui ne nécessite pas de dupliquer du code ou de déplacer un if, sinon dans la boucle.

Par exemple, j’ai une liste de chaînes à imprimer dans une liste séparée par des virgules. (la solution do while suppose déjà que la liste contient 2 éléments ou plus, sinon elle serait aussi mauvaise que la boucle avec conditionnel).

par exemple List = (“chien”, “chat”, “chauve-souris”)

Je veux imprimer “[chien, chat, chauve-souris]”

Je présente 2 méthodes le

  1. Pour boucle avec conditionnel

    public static Ssortingng forLoopConditional(Ssortingng[] items) { Ssortingng itemOutput = "["; for (int i = 0; i < items.length; i++) { // Check if we're not at the last element if (i < (items.length - 1)) { itemOutput += items[i] + ", "; } else { // last element itemOutput += items[i]; } } itemOutput += "]"; return itemOutput; } 
  2. faire en boucle amorcer la boucle

     public static Ssortingng doWhileLoopPrime(Ssortingng[] items) { Ssortingng itemOutput = "["; int i = 0; itemOutput += items[i++]; if (i < (items.length)) { do { itemOutput += ", " + items[i++]; } while (i < items.length); } itemOutput += "]"; return itemOutput; } 

    Classe testeur:

     public static void main(Ssortingng[] args) { Ssortingng[] items = { "dog", "cat", "bat" }; System.out.println(forLoopConditional(items)); System.out.println(doWhileLoopPrime(items)); } 

Dans la classe Java AbstractCollection, elle a l’implémentation suivante (un peu verbeuse car elle contient toutes les vérifications d’erreur de casse, mais pas mal).

 public Ssortingng toSsortingng() { Iterator i = iterator(); if (! i.hasNext()) return "[]"; SsortingngBuilder sb = new SsortingngBuilder(); sb.append('['); for (;;) { E e = i.next(); sb.append(e == this ? "(this Collection)" : e); if (! i.hasNext()) return sb.append(']').toSsortingng(); sb.append(", "); } } 

Il y a beaucoup de boucles dans ces réponses, mais je trouve qu’un iterator et une boucle while se lit beaucoup plus facilement. Par exemple:

 Iterator itemIterator = Arrays.asList(items).iterator(); if (itemIterator.hasNext()) { // special-case first item. in this case, no comma while (itemIterator.hasNext()) { // process the rest } } 

C’est l’approche adoptée par Joiner dans les collections Google et je la trouve très lisible.

Je l’écris habituellement comme ceci:

 static Ssortingng commaSeparated(Ssortingng[] items) { SsortingngBuilder sb = new SsortingngBuilder(); Ssortingng sep = ""; for (Ssortingng item: items) { sb.append(sep); sb.append(item); sep = ","; } return sb.toSsortingng(); } 
 ssortingng value = "[" + SsortingngUtils.join( items, ',' ) + "]"; 

Mon habitude est de tester si la variable d’index est nulle, par exemple:

 var result = "[ "; for (var i = 0; i < list.length; ++i) { if (i != 0) result += ", "; result += list[i]; } result += " ]"; 

Mais bien sûr, c'est seulement si nous parlons de langages qui n'ont pas de méthode Array.join (","). 😉

Je pense qu’il est plus facile de considérer le premier élément comme un cas particulier, car il est beaucoup plus facile de savoir si une itération est la première plutôt que la dernière. Il ne faut pas de logique complexe ou coûteuse pour savoir si quelque chose est fait pour la première fois.

 public static Ssortingng prettyPrint(Ssortingng[] items) { Ssortingng itemOutput = "["; boolean first = true; for (int i = 0; i < items.length; i++) { if (!first) { itemOutput += ", "; } itemOutput += items[i]; first = false; } itemOutput += "]"; return itemOutput; } 

J’aime utiliser un drapeau pour le premier élément.

  ArrayList list = new ArrayList(){{ add("dog"); add("cat"); add("bat"); }}; Ssortingng output = "["; boolean first = true; for(Ssortingng word: list){ if(!first) output += ", "; output+= word; first = false; } output += "]"; 

Puisque votre cas traite simplement du texte, vous n’avez pas besoin du conditionnel dans la boucle. Exemple AC:

 char* items[] = {"dog", "cat", "bat"}; char* output[STRING_LENGTH] = {0}; char* pStr = &output[1]; int i; output[0] = '['; for (i=0; i < (sizeof(items) / sizeof(char*)); ++i) { sprintf(pStr,"%s,",items[i]); pStr = &output[0] + strlen(output); } output[strlen(output)-1] = ']'; 

Au lieu d'append un conditionnel pour éviter de générer la virgule finale, lancez-le et générez-le (pour garder votre boucle simple et sans condition) et écrasez-le simplement à la fin. Plusieurs fois, je trouve plus clair de générer le cas particulier comme n'importe quelle autre itération de boucle, puis de le remplacer manuellement à la fin (bien que si le code "le remplace" dépasse quelques lignes, cette méthode peut devenir plus difficile à lis).

J’irais avec votre deuxième exemple, c.-à-d. manipulez le cas particulier en dehors de la boucle, écrivez-le un peu plus simplement:

 Ssortingng itemOutput = "["; if (items.length > 0) { itemOutput += items[0]; for (int i = 1; i < items.length; i++) { itemOutput += ", " + items[i]; } } itemOutput += "]"; 

Solution Java 8, au cas où quelqu’un le chercherait:

 Ssortingng res = Arrays.stream(items).reduce((t, u) -> t + "," + u).get(); 

 Ssortingng[] items = { "dog", "cat", "bat" }; Ssortingng res = "["; for (Ssortingng s : items) { res += (res.length == 1 ? "" : ", ") + s; } res += "]"; 

ou alors est assez lisible. Vous pouvez bien sûr mettre le conditionnel dans une clause if distincte. Ce qui rend idiomatique (je pense au moins), c’est qu’il utilise une boucle foreach et n’utilise pas un en-tête de boucle compliqué.

En outre, aucune logique n’est dupliquée (c’est-à-dire qu’un seul élément des items est réellement ajouté à la chaîne de sortie – dans une application réelle, cela peut être une opération de formatage plus compliquée et plus longue, donc je ne voudrais pas répéter le code).

Dans ce cas, vous êtes essentiellement en train de concaténer une liste de chaînes en utilisant une chaîne de séparation. Vous pouvez peut-être écrire vous-même quelque chose qui le fait. Ensuite, vous obtiendrez quelque chose comme:

 Ssortingng[] items = { "dog", "cat", "bat" }; Ssortingng result = "[" + joinListOfSsortingngs(items, ", ") + "]" 

avec

 public static Ssortingng joinListOfSsortingngs(Ssortingng[] items, Ssortingng sep) { SsortingngBuffer result; for (int i=0; i 

Si vous avez une Collection au lieu d'une Ssortingng[] vous pouvez également utiliser les iterators et la méthode hasNext() pour vérifier si c'est la dernière ou non.

Si vous construisez une chaîne de manière dynamic, vous ne devriez pas utiliser l’opérateur + =. La classe SsortingngBuilder fonctionne beaucoup mieux pour la concaténation de chaînes dynamics répétée.

 public Ssortingng commaSeparate(Ssortingng[] items, Ssortingng delim){ SsortingngBuilder bob = new SsortingngBuilder(); for(int i=0;i 

Alors l'appel est comme ça

 Ssortingng[] items = {"one","two","three"}; SsortingngBuilder bob = new SsortingngBuilder(); bob.append("["); bob.append(commaSeperate(items,",")); bob.append("]"); System.out.print(bob.toSsortingng()); 

En général, mon préféré est la sortie à plusieurs niveaux. Changement

 for ( s1; exit-condition; s2 ) { doForAll(); if ( !modified-exit-condition ) doForAllButLast(); } 

à

 for ( s1;; s2 ) { doForAll(); if ( modified-exit-condition ) break; doForAllButLast(); } 

Il élimine les codes en double ou les contrôles redondants.

Votre exemple:

 for (int i = 0;; i++) { itemOutput.append(items[i]); if ( i == items.length - 1) break; itemOutput.append(", "); } 

Cela fonctionne pour certaines choses mieux que d’autres. Je ne suis pas un grand fan de cet exemple spécifique.

Bien sûr, cela devient vraiment difficile pour les scénarios où la condition de sortie dépend de ce qui se passe dans doForAll() et pas seulement de s2 . Utiliser un Iterator est un tel cas.

Voici un article du prof qui l’a promu sans vergogne auprès de ses élèves :-). Lisez la section 5 pour savoir exactement de quoi vous parlez.

Je pense qu’il y a deux réponses à cette question: le meilleur idiome pour ce problème dans n’importe quelle langue, et le meilleur idiome pour ce problème en Java. Je pense aussi que le but de ce problème n’était pas d’associer des chaînes, mais plutôt le pattern en général, donc cela n’aide pas vraiment de montrer les fonctions de bibliothèque qui peuvent le faire.

Tout d’abord, les actions consistant à entourer une chaîne avec [] et à créer une chaîne séparée par des virgules sont deux actions distinctes et, idéalement, deux fonctions distinctes.

Pour n’importe quelle langue, je pense que la combinaison de la récursivité et de la correspondance de modèle fonctionne le mieux. Par exemple, dans Haskell, je ferais ceci:

 join [] = "" join [x] = x join (x:xs) = concat [x, ",", join xs] surround before after str = concat [before, str, after] yourFunc = surround "[" "]" . join -- example usage: yourFunc ["dog", "cat"] will output "[dog,cat]" 

L’avantage de l’écrire comme cela est qu’il énumère clairement les différentes situations auxquelles la fonction sera confrontée et comment elle va le gérer.

Une autre manière très agréable de faire cela est d’utiliser une fonction de type accumulateur. Par exemple:

 join [] = "" join ssortingngs = foldr1 (\ab -> concat [a, ",", b]) ssortingngs 

Cela peut être fait dans d’autres langues, par exemple: c #:

 public static ssortingng Join(List ssortingngs) { if (!ssortingngs.Any()) return ssortingng.Empty; return ssortingngs.Aggregate((acc, val) => acc + "," + val); } 

Pas très efficace dans cette situation, mais peut être utile dans d’autres cas (ou l’efficacité peut ne pas avoir d’importance).

Malheureusement, Java ne peut utiliser aucune de ces méthodes. Donc, dans ce cas, je pense que le meilleur moyen est d’avoir des vérifications en haut de la fonction pour les cas d’exception (0 ou 1 éléments), puis d’utiliser une boucle for pour gérer le cas avec plus d’un élément:

 public static Ssortingng join(Ssortingng[] items) { if (items.length == 0) return ""; if (items.length == 1) return items[0]; SsortingngBuilder result = new SsortingngBuilder(); for(int i = 0; i < items.length - 1; i++) { result.append(items[i]); result.append(","); } result.append(items[items.length - 1]); return result.toString(); } 

Cette fonction montre clairement ce qui se passe dans les deux cas extrêmes (0 ou 1 élément). Il utilise ensuite une boucle pour tous les éléments sauf les derniers et ajoute finalement le dernier élément sans virgule. La manière inverse de gérer l'élément sans virgule au début est également facile à faire.

Notez que les if (items.length == 1) return items[0]; ligne n'est pas vraiment nécessaire, mais je pense que cela rend plus facile la détermination de la fonction en un coup d'œil.

(Notez que si quelqu'un veut plus d'explications sur les fonctions haskell / c # demander et je vais l'append)

Cela peut être réalisé en utilisant Java 8 lambda et Collectors.joining () comme –

 List items = Arrays.asList("dog", "cat", "bat"); Ssortingng result = items.stream().collect(Collectors.joining(", ", "[", "]")); System.out.println(result); 

J’écris habituellement une boucle comme celle-ci:

 public static Ssortingng forLoopConditional(Ssortingng[] items) { SsortingngBuilder builder = new SsortingngBuilder(); builder.append("["); for (int i = 0; i < items.length - 1; i++) { builder.append(items[i] + ", "); } if (items.length > 0) { builder.append(items[items.length - 1]); } builder.append("]"); return builder.toSsortingng(); } 

Si vous cherchez simplement une liste séparée par des virgules comme ceci: “[The, Cat, in, the, Hat]”, ne perdez même pas de temps à écrire votre propre méthode. Utilisez simplement List.toSsortingng:

 List ssortingngs = Arrays.asList("The", "Cat", "in", "the", "Hat); System.out.println(ssortingngs.toSsortingng()); 

Pourvu que le type générique de la liste ait un toSsortingng avec la valeur que vous voulez afficher, appelez simplement List.toSsortingng:

 public class Dog { private Ssortingng name; public Dog(Ssortingng name){ this.name = name; } public Ssortingng toSsortingng(){ return name; } } 

Ensuite, vous pouvez faire:

 List dogs = Arrays.asList(new Dog("Frank"), new Dog("Hal")); System.out.println(dogs); 

Et vous aurez: [Frank, Hal]

Une troisième alternative est la suivante

 SsortingngBuilder output = new SsortingngBuilder(); for (int i = 0; i < items.length - 1; i++) { output.append(items[i]); output.append(","); } if (items.length > 0) output.append(items[items.length - 1]); 

Mais le mieux est d’utiliser une méthode de type join (). Pour Java, il existe un Ssortingng.join dans les bibliothèques tierces, de cette façon, votre code devient:

 SsortingngUtils.join(items,','); 

FWIW, la méthode join () (ligne 3232 et suivantes) dans Apache Commons utilise un if si dans une boucle si:

 public static Ssortingng join(Object[] array, char separator, int startIndex, int endIndex) { if (array == null) { return null; } int bufSize = (endIndex - startIndex); if (bufSize <= 0) { return EMPTY; } bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + 1); StringBuilder buf = new StringBuilder(bufSize); for (int i = startIndex; i < endIndex; i++) { if (i > startIndex) { buf.append(separator); } if (array[i] != null) { buf.append(array[i]); } } return buf.toSsortingng(); }