Última iteração do loop for aprimorado em java

140

Existe uma maneira de determinar se o loop está repetindo pela última vez. Meu código é algo como isto:

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

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

Agora, o problema é que não quero acrescentar a vírgula na última iteração. Agora existe uma maneira de determinar se é a última iteração ou estou preso ao loop for ou usando um contador externo para acompanhar.

Mercury
fonte
1
sim! É engraçado, eu só queria fazer exatamente a mesma pergunta!
PhiLho
O mesmo tipo de pergunta retorna (e assim será). Agora, por que você deseja criar um loop se um elemento precisa de um tratamento diferente? stackoverflow.com/questions/156650/…
xtofl 14/11
Como você possui uma matriz fixa, por que usar o avançado? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

Respostas:

223

Outra alternativa é acrescentar a vírgula antes de acrescentar i, mas não na primeira iteração. (Por favor, não use "" + i, a propósito - você realmente não quer concatenação aqui, e o StringBuilder tem uma sobrecarga de acréscimo (int) perfeitamente boa.)

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

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

O bom disso é que ele funcionará com qualquer um Iterable- nem sempre é possível indexar as coisas. ("Adicione a vírgula e remova-a no final" é uma boa sugestão quando você está realmente usando o StringBuilder - mas não funciona para coisas como escrever em fluxos. É possivelmente a melhor abordagem para esse problema exato. )

Jon Skeet
fonte
2
Bom padrão, mas builder.length ()! = 0 é quebradiço - imagine que algo seja adicionado (condicionalmente!) Ao buffer antes do seu loop. Em vez disso, use um isFirstsinalizador booleano. Bônus: mais rápido também.
Jason Cohen
2
@ Jason: Eu certamente uso o padrão "isFirst" onde o construtor não ficará vazio na primeira iteração. No entanto, quando não é necessário, adiciona uma quantidade considerável de inchaço à implementação na minha experiência.
Jon Skeet
3
@Iverpool (etc): é muito, muito improvável que 1000 verificações desnecessárias tenham um impacto significativo no desempenho. Eu poderia igualmente salientar que o caráter extra a solução de Dinah adicionado pode fazer com que o StringBuilder ter que expandir, dobrando o tamanho da cadeia final com (cont)
Jon Skeet
2
espaço de buffer desnecessário. No entanto, o ponto importante é a legibilidade. Por acaso, acho minha versão mais legível que a de Dinah. Se você se sentir do lado oposto, tudo bem. Eu consideraria apenas o impacto no desempenho dos "ifs" desnecessários depois de encontrar um gargalo.
21909 Jon Skeet
3
Além disso, minha solução é mais aplicável em geral, pois depende apenas de poder escrever. Você pode pegar minha solução e alterá-la para gravar em um fluxo etc. - onde talvez não seja possível recuperar os dados desnecessários posteriormente. Eu gosto de padrões geralmente aplicáveis.
21909 Jon Skeet
145

Outra maneira de fazer isso:

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

Atualização: para Java 8, agora você tem Coletores

kit de ferramentas
fonte
1
Tive que excluir minha resposta semelhante - isso me ensinará a não postar antes de olhar com mais cuidado para outras respostas.
Michael Burr
1
Observe também que isso lida com o possível problema mencionado por Robert Paulson em outro segmento de comentários - essa técnica não depende do StringBuilder estar vazio quando os valores da matriz são anexados.
Michael Burr
1
Embora o código seja mais liso / limpo / divertido, é menos óbvio que a versão if. Sempre use a versão mais óbvia / legível.
Bill K
8
@ Bill: Isso não é ótimo como regra rígida e rápida; há momentos em que a solução 'inteligente' é a solução "mais correta". Mais ao ponto, você realmente acha que esta versão é difícil de ler? De qualquer maneira, um mantenedor precisaria passar por isso - não acho que a diferença seja significativa.
Greg Case
Isso funciona, embora você grave em outro recurso, como as chamadas de gravação do sistema de arquivos. Boa ideia.
Tuxman 20/08/2015
39

Pode ser mais fácil anexar sempre. E então, quando você terminar seu loop, remova o caractere final. Toneladas menos condicionais dessa maneira também.

Você pode usar StringBuilder's deleteCharAt(int index)com índice sendolength() - 1

Dinah
fonte
5
a solução mais eficiente para esse problema. +1.
vermelho real.
9
Talvez seja eu, mas eu realmente não gosto do fato de que você está removendo algo que você acabou de adicionar ...
Fortega
2
Fortega: Eu não gosto de procurar sempre algo que farei 99% das vezes. Parece mais lógico (e é mais rápido) aplicar a solução de Dinah ou sblundy.
Buffalo1 de
1
Solução mais elegante na minha opinião. Obrigado!
Charles Morin
32

Talvez você esteja usando a ferramenta errada para o trabalho.

Isso é mais manual do que você está fazendo, mas é, de certa forma, mais elegante, se não um pouco "antiquado"

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

Outra solução (talvez a mais 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
fonte
Essa é uma solução natural, exceto que depende da matriz não estar vazia.
orcmid 13/11/08
5
@orcmid: Se a matriz estiver vazia, isso ainda fornecerá a saída correta - uma string vazia. Não sei ao certo qual é o seu ponto.
22413 Eddie
7

mantenha-o simples e use um padrão para loop:

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

ou apenas use apache commons-lang StringUtils.join ()

Gareth Davis
fonte
6

Os loops explícitos sempre funcionam melhor do que os implícitos.

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

Você deve agrupar tudo em uma declaração if, caso esteja lidando com uma matriz de comprimento zero.

S.Lott
fonte
Eu gosto dessa abordagem porque ela não executa desnecessariamente um teste a cada iteração. A única coisa que gostaria de acrescentar é que o construtor pode acrescentar números inteiros diretamente, portanto não há necessidade de usar "" + int, apenas acrescentar (array [0]).
Josh
Você precisa de mais um ao redor do lote para garantir que a matriz tenha pelo menos um elemento.
Tom Leys
2
por que todo mundo continua usando exemplos com concatenação de strings INSIDE de append? a " "+ x compila em new StringBuilder (",") .append (x) .ToString () ...
John Gardner
@ Jos e @ John: Apenas seguindo o exemplo n00b. Não queira introduzir muitas coisas ao mesmo tempo. Seu ponto é muito bom, no entanto.
S.Lott 12/11/2008
@ Tom: Certo. Eu acho que já disse isso. Nenhuma edição é necessária.
S.Lott 12/11/2008
4

Se você o converter em um loop de índice clássico, sim.

Ou você pode simplesmente excluir a última vírgula depois que terminar. Igual a:

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

Eu, eu apenas uso StringUtils.join()do commons-lang .

esplêndido
fonte
3

Você precisa de um separador de classes .

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

A implementação da classe Separatoré direta. Ele agrupa uma sequência retornada em todas as chamadas, toString()exceto a primeira, que retorna uma sequência vazia.

akuhn
fonte
3

Com base em java.util.AbstractCollection.toString (), sai cedo para evitar o delimitador.

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

É eficiente e elegante, mas não tão evidente quanto algumas das outras respostas.

13ren
fonte
Você esqueceu de incluir o retorno antecipado quando (! i.hasNext()), que é uma parte importante da robustez da abordagem geral. (As outras soluções aqui lidar com coleções vazias graciosamente, então o seu deve também :)!
Mike Clark
3

Aqui está uma solução:

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

Procure a primeira iteração :)  

FallenAvatar
fonte
3

Como o kit de ferramentas mencionado, no Java 8, agora temos coletores . Aqui está a aparência do código:

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

Eu acho que faz exatamente o que você está procurando, e é um padrão que você pode usar para muitas outras coisas.

b1tw153
fonte
1

Mais uma opção.

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
fonte
Fiz uma variação na outra questão
13ren
1

Muitas das soluções descritas aqui são um pouco exageradas, IMHO, especialmente aquelas que dependem de bibliotecas externas. Existe um bom idioma limpo e claro para obter uma lista separada por vírgulas que eu sempre usei. Ele se baseia no operador condicional (?):

Editar : Solução original correta, mas não ideal de acordo com os comentários. Tentando uma 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]); 

Lá está você, em 4 linhas de código, incluindo a declaração da matriz e o StringBuilder.

Julien Chastang
fonte
Mas você está criando um novo StringBuilder em todas as iterações (graças ao operador +).
Michael Myers
Sim, se você procurar no bytecode, está certo. Não acredito que uma pergunta tão simples possa ser tão complicada. Gostaria de saber se o compilador pode otimizar aqui.
Julien Chastang
Desde 2, espero que melhor solução.
Julien Chastang 22/03/2009
1

Aqui está uma referência do SSCCE que eu executei (relacionada ao que eu tinha que implementar) com estes resultados:

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

No meu exemplo, pelo menos, pular a verificação a cada iteração não é visivelmente mais rápido, especialmente para volumes sãos de dados, mas é mais 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
fonte
1
Por favor, não role seus próprios benchmarks e use o próprio micro harness de benchmarking do OpenJDKs (jmh) . Aquecerá seu código e evitará as armadilhas mais problemáticas.
René
0

Outra abordagem é manter o comprimento da matriz (se disponível) armazenado em uma variável separada (mais eficiente do que verificar novamente o comprimento toda vez). Em seguida, você pode comparar seu índice com esse comprimento para determinar se deseja adicionar ou não a vírgula final.

EDIT: Outra consideração é ponderar o custo de desempenho da remoção de um caractere final (que pode causar uma cópia de cadeia) contra a verificação condicional de cada iteração.

Brian
fonte
A remoção de um caractere final não deve causar uma cópia de seqüência de caracteres. Os métodos delete / deleteCharAt de StringBuffer eventualmente chamam o seguinte método dentro das matrizes idênticas de origem e destino da classe System: System -> array estático público estático void nativo (Object src, int srcPos, Object dest, int destPos, int length);
Buffalo1 de
0

Se você estiver transformando apenas uma matriz em uma matriz delimitada por vírgula, muitos idiomas terão uma função de junção exatamente para isso. Transforma uma matriz em uma string com um delimitador entre cada elemento.

Dinah
fonte
0

Nesse caso, não há realmente necessidade de saber se é a última repetição. Existem muitas maneiras de resolver isso. Uma maneira seria:

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

Dois caminhos alternativos aqui:

1: Utilitários de String do Apache Commons

2: mantenha um booleano chamado first, definido como true. Em cada iteração, se firstfor falso, anexe sua vírgula; depois disso, defina firstcomo false.

Paul Brinkley
fonte
1) O link está morto 2) Ele me levou mais tempo para ler a descrição em vez do código :)
Buffalo
0

Como é uma matriz fixa, seria mais fácil simplesmente evitar o aprimorado para ... Se o Object for uma coleção, um iterador seria mais 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
fonte