Letzte Iteration der erweiterten for-Schleife in Java

139

Gibt es eine Möglichkeit festzustellen, ob die Schleife zum letzten Mal iteriert? Mein Code sieht ungefähr so ​​aus:

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

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

Jetzt möchte ich das Komma in der letzten Iteration nicht anhängen. Jetzt gibt es eine Möglichkeit festzustellen, ob es sich um die letzte Iteration handelt oder ob ich bei der for-Schleife stecke oder einen externen Zähler verwende, um den Überblick zu behalten.

Mercurious
quelle
1
Ja! Es ist lustig, ich wollte nur genau die gleiche Frage stellen!
PhiLho
Die gleiche Art von Frage kehrt zurück (und wird es auch tun). Warum sollten Sie nun eine Schleife erstellen, wenn ein Element eine andere Behandlung benötigt? stackoverflow.com/questions/156650/…
xtofl
Da Sie ein festes Array haben, warum sollten Sie das erweiterte für verwenden? für (int i = 0; i <array.length; i ++ if (i <array.lenth)
,,,

Antworten:

223

Eine andere Alternative besteht darin, das Komma anzuhängen, bevor Sie i anhängen, nur nicht bei der ersten Iteration. (Bitte "" + iübrigens nicht verwenden - Sie möchten hier nicht wirklich Verkettung, und StringBuilder hat eine vollkommen gute Überladung von Anhängen (int).)

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

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

Das Schöne daran ist, dass es mit jedem funktioniert Iterable- man kann Dinge nicht immer indizieren. (Das "Komma hinzufügen und am Ende entfernen" ist ein netter Vorschlag, wenn Sie StringBuilder wirklich verwenden - aber es funktioniert nicht für Dinge wie das Schreiben in Streams. Es ist möglicherweise der beste Ansatz für genau dieses Problem. )

Jon Skeet
quelle
2
Gutes Muster, aber builder.length ()! = 0 ist spröde - stellen Sie sich vor, etwas wird (bedingt!) Vor Ihrer Schleife zum Puffer hinzugefügt. Verwenden Sie stattdessen ein isFirstboolesches Flag. Bonus: auch schneller.
Jason Cohen
2
@ Jason: Ich verwende auf jeden Fall das "isFirst" -Muster, bei dem der Builder bei der ersten Iteration nicht leer ist. Wenn es jedoch nicht benötigt wird, fügt es meiner Erfahrung nach der Implementierung einiges an Aufblähung hinzu.
Jon Skeet
3
@Liverpool (etc): 1000 unnötige Überprüfungen haben sehr, sehr unwahrscheinlich einen signifikanten Einfluss auf die Leistung. Ich möchte auch darauf hinweisen, dass das zusätzliche Zeichen durch Dinahs hinzugefügte Lösung dazu führen kann, dass der StringBuilder erweitert werden muss, wodurch sich die Größe der endgültigen Zeichenfolge mit (cont) verdoppelt
Jon Skeet
2
unnötiger Pufferplatz. Der wichtige Punkt ist jedoch die Lesbarkeit. Ich finde meine Version besser lesbar als die von Dinah. Wenn Sie das Gegenteil fühlen, ist das in Ordnung. Ich würde die Auswirkungen der unnötigen "Wenns" auf die Leistung erst berücksichtigen, nachdem ich einen Engpass gefunden habe.
Jon Skeet
3
Außerdem ist meine Lösung allgemeiner anwendbar, da sie nur darauf beruht, schreiben zu können. Sie könnten meine Lösung nehmen und ändern, um sie in einen Stream usw. zu schreiben - wo Sie möglicherweise nicht in der Lage sind, die unnötigen Daten anschließend zurückzunehmen. Ich mag Muster, die allgemein anwendbar sind.
Jon Skeet
145

Ein anderer Weg, dies zu tun:

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

Update: Für Java 8 haben Sie jetzt Collectors

Toolkit
quelle
1
Musste meine ähnliche Antwort löschen - das wird mich lehren, nicht zu posten, bevor ich mir andere Antworten genauer anschaue.
Michael Burr
1
Beachten Sie auch, dass dies das potenzielle Problem behandelt, das Robert Paulson in einem anderen Kommentarthread erwähnt hat. Diese Technik hängt nicht davon ab, dass der StringBuilder leer ist, wenn die Array-Werte angehängt werden.
Michael Burr
1
Obwohl der Code schlauer / ordentlicher / lustiger ist, ist er weniger offensichtlich als die if-Version. Verwenden Sie immer die offensichtlichere / lesbarere Version.
Bill K
8
@ Bill: Das ist keine gute Regel. Es gibt Zeiten, in denen die "clevere" Lösung die "korrektere" Lösung ist. Genauer gesagt, ist diese Version wirklich schwer zu lesen? So oder so müsste ein Betreuer durchgehen - ich denke nicht, dass der Unterschied signifikant ist.
Greg Case
Dies funktioniert, obwohl Sie in andere Ressourcen wie die Schreibaufrufe des Dateisystems schreiben. Gute Idee.
Tuxman
39

Es könnte einfacher sein, immer anzuhängen. Und wenn Sie mit Ihrer Schleife fertig sind, entfernen Sie einfach das letzte Zeichen. Tonnenweise weniger Bedingungen auch so.

Sie können StringBuilder's deleteCharAt(int index)mit Index verwendenlength() - 1

Dinah
quelle
5
die effizienteste Lösung für dieses Problem. +1.
Echtes Rot.
9
Vielleicht bin ich es, aber ich mag es wirklich nicht, dass Sie etwas entfernen, das Sie gerade hinzugefügt haben ...
Fortega
2
Fortega: Ich mag es nicht, jedes Mal nach etwas zu suchen, das ich 99% der Zeit tun werde. Es erscheint logischer (und es ist schneller), die Lösung von Dinah oder sblundy anzuwenden.
Buffalo
1
Die eleganteste Lösung meiner Meinung nach. Danke dir!
Charles Morin
32

Möglicherweise verwenden Sie das falsche Tool für den Job.

Dies ist manueller als das, was Sie tun, aber es ist in gewisser Weise eleganter, wenn nicht ein bisschen "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
quelle
15

Dies ist fast eine Wiederholung dieser StackOverflow-Frage . Was Sie wollen, ist StringUtils und das Aufrufen der Join- Methode.

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

Eine andere Lösung (vielleicht die effizienteste)

    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
quelle
Dies ist eine natürliche Lösung, mit der Ausnahme, dass das Array nicht leer ist.
Orcmid
5
@orcmid: Wenn das Array leer ist, gibt dies immer noch die richtige Ausgabe - eine leere Zeichenfolge. Ich bin mir nicht sicher, worum es dir geht.
Eddie
6

Explizite Schleifen funktionieren immer besser als implizite.

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

Sie sollten das Ganze in eine if-Anweisung einschließen, nur für den Fall, dass Sie es mit einem Array mit der Länge Null zu tun haben.

S.Lott
quelle
Ich mag diesen Ansatz, weil er nicht bei jeder Iteration unnötig einen Test durchführt. Das einzige, was ich hinzufügen möchte, ist, dass der Builder Ganzzahlen direkt anhängen kann, sodass nicht "" + int verwendet werden muss, sondern nur anhängen (Array [0]).
Josh
Sie benötigen eine weitere, wenn das gesamte Los vorhanden ist, um sicherzustellen, dass das Array mindestens ein Element enthält.
Tom Leys
2
Warum verwendet jeder weiterhin Beispiele mit Zeichenfolgenverkettung INNERHALB von Anhängen? Das "," + x wird in den neuen StringBuilder (",") kompiliert .append (x) .toString () ...
John Gardner
@Josh und @John: Folgen Sie einfach dem Beispiel n00b. Ich möchte nicht zu viele Dinge gleichzeitig vorstellen. Ihr Punkt ist jedoch sehr gut.
S.Lott
@ Tom: Richtig. Ich glaube, das habe ich schon gesagt. Keine Bearbeitung erforderlich.
S.Lott
6

Halten Sie es einfach und verwenden Sie einen Standard für die Schleife:

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

oder benutze einfach apache commons-lang StringUtils.join ()

Gareth Davis
quelle
4

Wenn Sie es in eine klassische Indexschleife konvertieren, ja.

Oder Sie können einfach das letzte Komma löschen, nachdem es fertig ist. Wie so:

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

Ich benutze nur StringUtils.join()von commons-lang .

sblundy
quelle
3

Sie benötigen einen Klassentrenner .

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

Die Implementierung der Klasse Separatorist unkompliziert. Es umschließt eine Zeichenfolge, die bei jedem Aufruf von zurückgegeben wird, mit toString()Ausnahme des ersten Aufrufs, der eine leere Zeichenfolge zurückgibt.

akuhn
quelle
3

Basierend auf java.util.AbstractCollection.toString () wird es vorzeitig beendet, um das Trennzeichen zu vermeiden.

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

Es ist effizient und elegant, aber nicht so selbstverständlich wie einige der anderen Antworten.

13ren
quelle
Sie haben vergessen, die vorzeitige Rückkehr einzubeziehen (! i.hasNext()), wenn dies ein wichtiger Teil der Robustheit des Gesamtansatzes ist. (Die anderen Lösungen hier behandeln leere Sammlungen elegant, also sollten auch Ihre! :)
Mike Clark
3

Hier ist eine Lösung:

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

Suche nach der ersten Iteration :)  

FallenAvatar
quelle
3

Wie bereits erwähnt, haben wir in Java 8 jetzt Collectors . So würde der Code aussehen:

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

Ich denke, das macht genau das, wonach Sie suchen, und es ist ein Muster, das Sie für viele andere Dinge verwenden könnten.

b1tw153
quelle
1

Noch eine 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
quelle
Ich habe eine Variation bei der anderen Frage gemacht
13ren
1

Viele der hier beschriebenen Lösungen sind meiner Meinung nach etwas übertrieben, insbesondere diejenigen, die auf externen Bibliotheken basieren. Es gibt eine schöne, saubere und klare Sprache, um eine durch Kommas getrennte Liste zu erhalten, die ich immer verwendet habe. Es basiert auf dem bedingten (?) Operator:

Bearbeiten : Ursprüngliche Lösung korrekt, aber laut Kommentaren nicht optimal. Ein zweites Mal versuchen:

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

Los geht's in 4 Codezeilen, einschließlich der Deklaration des Arrays und des StringBuilder.

Julien Chastang
quelle
Sie erstellen jedoch bei jeder Iteration einen neuen StringBuilder (dank des Operators +).
Michael Myers
Ja, wenn Sie in den Bytecode schauen, haben Sie Recht. Ich kann nicht glauben, dass eine so einfache Frage so kompliziert werden kann. Ich frage mich, ob der Compiler hier optimieren kann.
Julien Chastang
Vorausgesetzt, 2., hoffentlich bessere Lösung.
Julien Chastang
1

Hier ist ein SSCCE-Benchmark, den ich (im Zusammenhang mit dem, was ich implementieren musste) mit diesen Ergebnissen durchgeführt habe:

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

An meinem Beispiel zumindest ist die Prüfung bei jeder Iteration des Überspringen nicht merklich schneller speziell für vernünftige Datenmengen, aber es ist schneller.

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)");
  }
}
Büffel
quelle
1
Bitte rollen Sie keine eigenen Benchmarks und verwenden Sie OpenJDKs eigenen Micro-Benchmarking-Gurt (jmh) . Es wird Ihren Code aufwärmen und die problematischsten Fallstricke vermeiden.
René
0

Ein anderer Ansatz besteht darin, die Länge des Arrays (falls verfügbar) in einer separaten Variablen zu speichern (effizienter als die erneute Überprüfung der Länge jedes Mal). Sie können dann Ihren Index mit dieser Länge vergleichen, um festzustellen, ob das letzte Komma hinzugefügt werden soll oder nicht.

BEARBEITEN: Eine weitere Überlegung ist das Abwägen der Leistungskosten für das Entfernen eines endgültigen Zeichens (was zu einer Zeichenfolgenkopie führen kann) gegen das Überprüfen einer Bedingung in jeder Iteration.

Brian
quelle
Das Entfernen eines endgültigen Zeichens sollte nicht dazu führen, dass eine Zeichenfolgenkopie erstellt wird. Die delete / deleteCharAt-Methoden von StringBuffer rufen schließlich die folgende Methode innerhalb der identischen Quell- und Zielarrays der Systemklasse auf: System -> public static native void arraycopy (Objekt src, int srcPos, Objekt dest, int destPos, int length);
Buffalo
0

Wenn Sie ein Array nur in ein durch Kommas getrenntes Array verwandeln, haben viele Sprachen genau dafür eine Verknüpfungsfunktion. Es verwandelt ein Array in eine Zeichenfolge mit einem Trennzeichen zwischen den einzelnen Elementen.

Dinah
quelle
0

In diesem Fall müssen Sie wirklich nicht wissen, ob es sich um die letzte Wiederholung handelt. Es gibt viele Möglichkeiten, dies zu lösen. Ein Weg wäre:

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

Zwei alternative Wege hier:

1: Apache Commons String Utils

2: Behalten Sie einen Booleschen Wert bei first, der auf true gesetzt ist. Wenn in jeder Iteration firstfalsch ist, fügen Sie Ihr Komma hinzu. danach firstauf false setzen.

Paul Brinkley
quelle
1) Der Link ist tot 2) Ich habe länger gebraucht, um die Beschreibung anstatt den Code zu lesen :)
Buffalo
0

Da es sich um ein festes Array handelt, ist es einfacher, das erweiterte für ... zu vermeiden. Wenn das Objekt eine Sammlung ist, wäre ein Iterator einfacher.

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
quelle