Última iteración de mejorado para loop en java

140

¿Hay alguna manera de determinar si el ciclo está iterando por última vez? Mi código se parece a esto:

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

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

Ahora la cosa es que no quiero agregar la coma en la última iteración. Ahora hay una manera de determinar si es la última iteración o si estoy atascado con el bucle for o usando un contador externo para realizar un seguimiento.

Mercurioso
fuente
1
¡Si! Es divertido, ¡solo quería hacer exactamente la misma pregunta!
PhiLho
El mismo tipo de pregunta regresa (y lo hará). Ahora, ¿por qué querrías crear un bucle si un elemento necesita un tratamiento diferente? stackoverflow.com/questions/156650/…
xtofl
Dado que tiene una matriz fija, ¿por qué usar el mejorado para? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

Respuestas:

223

Otra alternativa es agregar la coma antes de agregar i, pero no en la primera iteración. (Por "" + icierto, no lo use , por cierto, realmente no desea la concatenación aquí, y StringBuilder tiene una sobrecarga append (int) perfectamente buena).

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

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

Lo bueno de esto es que funcionará con cualquiera Iterable: no siempre puede indexar las cosas. (El "agregar la coma y luego eliminarlo al final" es una buena sugerencia cuando realmente está utilizando StringBuilder, pero no funciona para cosas como escribir en secuencias. Sin embargo, es posiblemente el mejor enfoque para este problema exacto. )

Jon Skeet
fuente
2
Buen patrón, pero builder.length ()! = 0 es frágil; imagine que algo se agrega (¡condicionalmente!) Al búfer antes de su bucle. En su lugar, use una isFirstbandera booleana. Bono: más rápido también.
Jason Cohen
2
@ Jason: Ciertamente uso el patrón "isFirst" donde el constructor no estará vacío en la primera iteración. Sin embargo, cuando no es necesario, agrega una buena cantidad de hinchazón a la implementación en mi experiencia.
Jon Skeet
3
@Liverpool (etc.): 1000 comprobaciones innecesarias es muy, muy poco probable que tenga un impacto significativo en el rendimiento. También podría señalar que el carácter adicional de la solución de Dinah agregada podría hacer que el StringBuilder tenga que expandirse, duplicando el tamaño de la cadena final con (cont)
Jon Skeet
2
espacio innecesario en el búfer. Sin embargo, el punto importante es la legibilidad. Encuentro que mi versión es más legible que la de Dinah. Si sientes lo contrario, está bien. Solo consideraría el impacto en el rendimiento de los "si" innecesarios después de encontrar un cuello de botella.
Jon Skeet
3
Además, mi solución es más aplicable en general, ya que solo se basa en poder escribir. Podría tomar mi solución y cambiarla para escribir en una secuencia, etc., donde es posible que no pueda recuperar los datos innecesarios después. Me gustan los patrones que son generalmente aplicables.
Jon Skeet
145

Otra forma de hacer esto:

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

Actualización: para Java 8, ahora tiene recopiladores

kit de herramientas
fuente
1
Tuve que eliminar mi respuesta similar, eso me enseñará a no publicar antes de mirar más detenidamente otras respuestas.
Michael Burr
1
También tenga en cuenta que esto maneja el problema potencial que Robert Paulson mencionó en otro hilo de comentarios: esta técnica no depende de que StringBuilder esté vacío cuando se agregan los valores de la matriz.
Michael Burr el
1
Aunque el código es más elegante / ordenado / divertido, es menos obvio que la versión if. Utilice siempre la versión más obvia / legible.
Bill K
8
@Bill: Eso no es bueno como regla dura y rápida; Hay momentos en que la solución "inteligente" es la solución "más correcta". Más concretamente, ¿realmente crees que esta versión es difícil de leer? De cualquier manera, un mantenedor necesitaría pasar por él, no creo que la diferencia sea significativa.
Greg Case
Esto funciona aunque escriba en otro recurso, como las llamadas de escritura del sistema de archivos. Buena idea.
Tuxman
39

Puede ser más fácil agregar siempre. Y luego, cuando haya terminado con su ciclo, simplemente elimine el carácter final. Toneladas menos condicionales de esa manera también.

Puedes usar StringBuilder's deleteCharAt(int index)con index siendolength() - 1

Dinah
fuente
55
La solución más eficiente para este problema. +1.
Real Red.
9
Tal vez soy yo, pero realmente no me gusta el hecho de que estás eliminando algo que acabas de agregar ...
Fortega
2
Fortega: No me gusta comprobar cada vez algo que haré el 99% de las veces. Parece más lógico (y ES más rápido) aplicar la solución de Dinah o sblundy.
búfalo
1
La solución más elegante en mi opinión. ¡Gracias!
Charles Morin
32

Tal vez está utilizando la herramienta incorrecta para el trabajo.

Esto es más manual que lo que estás haciendo, pero es de una manera más elegante, si no un poco "vieja escuela"

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

Otra solución (quizás la más eficiente)

    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
fuente
Esta es una solución natural, excepto que depende de que la matriz no esté vacía.
orcmid el
55
@orcmid: si la matriz está vacía, esto todavía da el resultado correcto: una cadena vacía. No estoy seguro de cuál es tu punto.
Eddie
7

manténgalo simple y use un estándar para loop:

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

o simplemente use apache commons-lang StringUtils.join ()

Gareth Davis
fuente
6

Los bucles explícitos siempre funcionan mejor que los implícitos.

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

Debería envolver todo en una declaración if solo en caso de que esté tratando con una matriz de longitud cero.

S.Lott
fuente
Me gusta este enfoque porque no realiza innecesariamente una prueba en cada iteración. Lo único que agregaría es que el constructor puede agregar enteros directamente, por lo que no es necesario usar "" + int, solo agregar (array [0]).
Josh
Necesita uno más si está en todo el lote para asegurarse de que la matriz tenga al menos un elemento.
Tom Leys el
2
¿Por qué todos siguen usando ejemplos con concatenación de cadenas DENTRO de append? "," + x se compila en un nuevo StringBuilder (",") .append (x) .toString () ...
John Gardner
@Josh y @John: solo siguiendo el ejemplo n00b. No quiero presentar demasiadas cosas a la vez. Sin embargo, su punto es muy bueno.
S.Lott
@Tom: Correcto. Creo que ya dije eso. No se requiere edición.
S.Lott
4

Si lo convierte en un bucle de índice clásico, sí.

O simplemente puede eliminar la última coma después de que esté hecha. Al igual que:

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

Yo, solo lo uso StringUtils.join()de commons-lang .

sblundy
fuente
3

Necesita un separador de clase .

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

La implementación de la clase Separatores sencilla. Envuelve una cadena que se devuelve en cada llamada, toString()excepto en la primera llamada, que devuelve una cadena vacía.

akuhn
fuente
3

Basado en java.util.AbstractCollection.toString (), sale temprano para evitar el delimitador.

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

Es eficiente y elegante, pero no tan evidente como algunas de las otras respuestas.

13ren
fuente
Olvidó incluir el retorno temprano cuando (! i.hasNext()), que es una parte importante de la solidez del enfoque general. (Las otras soluciones aquí manejar colecciones vacías con gracia, por lo que la suya deben también :)!
Mike Clark
3

Aquí hay una solución:

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

Busque la primera iteración :)  

FallenAvatar
fuente
3

Como se mencionó en el kit de herramientas, en Java 8 ahora tenemos recopiladores . Así es como se vería el código:

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

Creo que eso hace exactamente lo que estás buscando, y es un patrón que puedes usar para muchas otras cosas.

b1tw153
fuente
1

Otra opción más.

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
fuente
Hice una variación en la otra pregunta
13ren
1

Muchas de las soluciones descritas aquí son un poco exageradas, en mi humilde opinión, especialmente aquellas que dependen de bibliotecas externas. Hay un buen lenguaje limpio y claro para lograr una lista separada por comas que siempre he usado. Se basa en el operador condicional (?):

Editar : Solución original correcta, pero no óptima según los comentarios. Intentando por segunda vez:

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

Ahí tienes, en 4 líneas de código, incluida la declaración de la matriz y el StringBuilder.

Julien Chastang
fuente
Pero está creando un nuevo StringBuilder en cada iteración (gracias al operador +).
Michael Myers
Sí, si buscas en el código de bytes, tienes razón. No puedo creer que una pregunta tan simple pueda ser tan complicada. Me pregunto si el compilador puede optimizar aquí.
Julien Chastang
Proporcionó la segunda solución, con suerte mejor.
Julien Chastang
1

Aquí hay un punto de referencia SSCCE que ejecuté (relacionado con lo que tuve que implementar) con estos resultados:

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

Al menos en mi ejemplo, omitir el control en cada iteración no es notablemente más rápido, especialmente para volúmenes de datos sanos, pero es más rápido.

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úfalo
fuente
1
No realice sus propios puntos de referencia y use el propio arnés de microevaluación (jmh) de OpenJDK . Calentará su código y evitará las trampas más problemáticas.
René
0

Otro enfoque es tener la longitud de la matriz (si está disponible) almacenada en una variable separada (más eficiente que volver a verificar la longitud cada vez). Luego puede comparar su índice con esa longitud para determinar si agregar o no la coma final.

EDITAR: Otra consideración es sopesar el costo de rendimiento de eliminar un carácter final (que puede causar una copia de cadena) en contra de que se verifique un condicional en cada iteración.

Brian
fuente
Eliminar un carácter final no debería hacer que se realice una copia de cadena. Los métodos delete / deleteCharAt de StringBuffer eventualmente llaman al siguiente método dentro de las matrices de origen y destino idénticas de la clase del sistema: Sistema -> matriz de vacío vacía nativa pública (Object src, int srcPos, Object dest, int destPos, int length);
Buffalo
0

Si solo está convirtiendo una matriz en una matriz delimitada por comas, muchos idiomas tienen una función de unión para exactamente esto. Convierte una matriz en una cadena con un delimitador entre cada elemento.

Dinah
fuente
0

En este caso, realmente no hay necesidad de saber si es la última repetición. Hay muchas formas en que podemos resolver esto. Una forma sería:

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

Dos caminos alternativos aquí:

1: Utilidades de cadena de Apache Commons

2: Mantenga un booleano llamado first, establecido en verdadero. En cada iteración, si firstes falsa, agregue su coma; después de eso, establecer firsten falso.

Paul Brinkley
fuente
1) El enlace está inactivo 2) Me tomó más tiempo leer la descripción en lugar del código :)
Buffalo
0

Dado que es una matriz fija, sería más fácil simplemente evitar la mejora para ... Si el Objeto es una colección, un iterador sería más fácil.

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
fuente