Последняя итерация расширенного цикла 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.