Последняя итерация расширенного цикла for в java


142

Есть ли способ определить, повторяется ли цикл в последний раз. Мой код выглядит примерно так:

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

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

Дело в том, что я не хочу добавлять запятую на последней итерации. Теперь есть способ определить, является ли это последней итерацией, или я застрял в цикле for или с помощью внешнего счетчика для отслеживания.


1
Да уж! Забавно, я просто хотел задать тот же вопрос!
— PhiLho

Тот же вопрос возвращается (и будет так). Теперь зачем вам создавать цикл, если один элемент требует другой обработки? stackoverflow.com/questions/156650/…
— xtofl

Если у вас есть фиксированный массив, зачем использовать расширенный для? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
— AnthonyJClink

Ответы:


223

Другой вариант - добавить запятую перед добавлением i, но не на первой итерации. (Пожалуйста, не используйте "" + i, кстати, конкатенацию здесь не нужно, а StringBuilder имеет отличную перегрузку append (int).)

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

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

Приятно то, что он будет работать с любым Iterable- вы не всегда можете индексировать вещи. («Добавьте запятую, а затем удалите ее в конце» - это хороший совет, когда вы действительно используете StringBuilder, но он не работает для таких вещей, как запись в потоки. Хотя, возможно, это лучший подход для этой точной проблемы. )


2
Хороший шаблон, но builder.length ()! = 0 хрупкий - представьте, что что-то добавляется (условно!) В буфер перед вашим циклом. Вместо этого используйте isFirstлогический флаг. Бонус: тоже быстрее.
— Джейсон Коэн,

2
@Jason: Я, конечно, использую шаблон isFirst, в котором построитель не будет пустым на первой итерации. Однако, когда в этом нет необходимости, по моему опыту, это значительно увеличивает объем реализации.
— Джон Скит,

3
@Liverpool (и т.д.): 1000 ненужных проверок очень и очень маловероятно, чтобы иметь какое-либо значительное влияние на производительность. Я также мог бы указать, что дополнительный символ, добавленный решением Дины, может привести к расширению StringBuilder, удвоив размер последней строки с помощью (продолжение)
— Джон Скит,

2
ненужное буферное пространство. Однако немаловажный момент - читаемость. Я нахожу свою версию более читаемой, чем версию Дины. Если вы чувствуете обратное, это нормально. Я бы рассмотрел влияние на производительность ненужных «если» только после того, как нашел узкое место.
— Джон Скит,

3
Кроме того, мое решение является более универсальным, поскольку оно зависит только от способности писать. Вы можете взять мое решение и изменить его для записи в поток и т. Д. - где вы, возможно, не сможете впоследствии вернуть ненужные данные. Мне нравятся общие шаблоны.
— Джон Скит,

146

Другой способ сделать это:

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

Обновление: для Java 8 теперь у вас есть сборщики


1
Пришлось удалить свой аналогичный ответ - это научит меня не публиковать, пока не буду более внимательно смотреть на другие ответы.
— Майкл Берр,

1
Также обратите внимание, что это решает потенциальную проблему, о которой Роберт Полсон упомянул в другом потоке комментариев - этот метод не зависит от того, что StringBuilder пуст при добавлении значений массива.
— Майкл Берр

1
Хотя код более приятный / изящный / забавный, он менее очевиден, чем версия if. Всегда используйте более очевидную / читаемую версию.
— Bill K

8
@ Билл: Это не очень хорошее правило; бывают случаи, когда «умное» решение оказывается «более правильным». Более того, неужели вы действительно, что эту версию трудно читать? В любом случае специалисту по сопровождению придется пройти через это - я не думаю, что разница значительна.
— Грег Кейс,

Это работает, хотя вы пишете в другой ресурс, например, в запросы записи файловой системы. Хорошая идея.
— Tuxman

39

Может быть проще всегда добавлять. А затем, когда вы закончите цикл, просто удалите последний символ. Тоже на тонну меньше условных выражений.

Вы можете использовать StringBuilder«S deleteCharAt(int index)с индексом бытияlength() - 1


5
наиболее эффективное решение этой проблемы. +1.
— Настоящий красный.

9
Может быть, это я, но мне действительно не нравится тот факт, что вы
— удаляете

2
Фортега: Я не люблю каждый раз проверять то, что я делаю в 99% случаев. Кажется более логичным (и это быстрее) применить решение Дины или Слэнди.
— Buffalo

1
На мой взгляд, самое элегантное решение. Спасибо!
— Чарльз Морин

32

Возможно, вы используете неправильный инструмент для работы.

Это больше руководство, чем то, что вы делаете, но это более элегантно, если не немного "старой школы"

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


14

Другое решение (пожалуй, самое эффективное)

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

Это естественное решение, за исключением того, что оно зависит от того, что массив не пуст.
— orcmid

5
@orcmid: если массив пуст, это все равно дает правильный результат - пустую строку. Я не уверен, о чем вы говорите.
— Эдди,


6

Явные циклы всегда работают лучше, чем неявные.

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

Вы должны заключить все это в оператор if на тот случай, если вы имеете дело с массивом нулевой длины.


Мне нравится этот подход, потому что он не требует ненужного тестирования на каждой итерации. Единственное, что я хотел бы добавить, это то, что построитель может напрямую добавлять целые числа, поэтому нет необходимости использовать "" + int, просто добавьте (array [0]).
— Джош

Вам понадобится еще один, если вокруг всего участка, чтобы убедиться, что в массиве есть хотя бы один элемент.
— Tom Leys

2
почему все продолжают использовать примеры с конкатенацией строк ВНУТРИ добавления? "," + x компилируется в новый StringBuilder (",") .append (x) .toString () ...
— Джон Гарднер,

@Josh и @John: Просто следую примеру n00b. Не хочу вводить слишком много вещей одновременно. Однако ваша точка зрения очень хорошая.
— S.Lott

@Tom: Верно. Думаю, я уже сказал это. Редактирование не требуется.
— S.Lott

4

Если преобразовать его в классический цикл индекса, да.

Или вы можете просто удалить последнюю запятую после того, как это будет сделано. Вот так:

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

Я просто использую StringUtils.join()с обычного языка .


4

Как уже упоминалось в наборе инструментов, в Java 8 теперь есть коллекторы . Вот как будет выглядеть код:

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

Я думаю, что это именно то, что вы ищете, и это шаблон, который вы могли бы использовать для многих других вещей.


3

Вам нужен разделитель классов .

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

Реализация класса Separatorпроста. Он обертывает строку, которая возвращается при каждом вызове, toString()кроме первого вызова, который возвращает пустую строку.


3

На основе java.util.AbstractCollection.toString () он завершает работу раньше, чтобы избежать использования разделителя.

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

Это эффективно и элегантно, но не так очевидно, как некоторые другие ответы.


Вы забыли включить раннее возвращение, когда (! i.hasNext())это является важной частью надежности общего подхода. (Другие решения здесь изящно обрабатывают пустые коллекции, так что и вам тоже! :)
— Майк Кларк,

3

Вот решение:

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

Ищите первую итерацию :)  


1

Еще один вариант.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);

Я сделал вариацию по другому вопросу
— 13рен,

1

Многие из описанных здесь решений немного чрезмерны, ИМХО, особенно те, которые полагаются на внешние библиотеки. Есть хорошая чистая, ясная идиома для создания списка, разделенного запятыми, который я всегда использовал. Он основан на условном операторе (?):

Изменить : исходное решение правильное, но неоптимальное согласно комментариям. Пробуем второй раз:

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

Вот и все, 4 строки кода, включая объявление массива и StringBuilder.


Но вы создаете новый StringBuilder на каждой итерации (благодаря оператору +).
— Майкл Майерс

Да, если вы посмотрите в байт-код, вы правы. Не могу поверить, что такой простой вопрос может стать таким сложным. Интересно, может ли компилятор здесь оптимизироваться.
— Жюльен Частанг,

Обеспечено второе, надеюсь, лучшее решение.
— Жюльен Частанг,

1

Вот тест SSCCE, который я запустил (связанный с тем, что мне пришлось реализовать) со следующими результатами:

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

На моем примере , по крайней мере, пропустив проверку на каждой итерации не заметно быстрее , особенно для здравомыслящих объемов данных, но это быстрее.

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

1
Пожалуйста, не сворачивайте свои собственные тесты и используйте собственный набор тестов OpenJDK (jmh) . Это разогреет ваш код и поможет избежать самых проблемных ловушек.
— Рене

0

Другой подход - сохранить длину массива (если имеется) в отдельной переменной (что более эффективно, чем повторная проверка длины каждый раз). Затем вы можете сравнить свой индекс с этой длиной, чтобы определить, добавлять ли последнюю запятую.

РЕДАКТИРОВАТЬ: Еще одним соображением является взвешивание затрат на производительность удаления последнего символа (что может привести к копированию строки) по сравнению с проверкой условного выражения на каждой итерации.


Удаление последнего символа не должно приводить к копированию строки. Методы delete / deleteCharAt StringBuffer в конечном итоге вызывают следующий метод внутри идентичных массивов источника и назначения класса System: System -> public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length);
— Buffalo

0

Если вы просто превращаете массив в массив с разделителями-запятыми, во многих языках есть функция соединения именно для этого. Он превращает массив в строку с разделителем между каждым элементом.


0

В этом случае действительно нет необходимости знать, последнее ли это повторение. Есть много способов решить эту проблему. Один из способов:

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

0

Здесь есть два альтернативных пути:

1. Строковые утилиты Apache Commons

2: Оставить логическое значение firstна уровне true. На каждой итерации, если firstложно, добавляйте запятую; после этого установите firstзначение false.


1) Ссылка мертва 2) Мне потребовалось больше времени, чтобы прочитать описание, а не код :)
— Buffalo

0

Поскольку это фиксированный массив, было бы проще просто избежать расширенного for ... Если объект является коллекцией, итератор будет проще.

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(",");
   }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.