Выполнять инструкцию в цикле каждый раз, кроме последнего раза?

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

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

Вот мое неудовлетворительное решение:

//Write contents to the file
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size(); i++) {
    writer.write(lines.get(i));
    if(i < lines.size() - 1) writer.newLine();
}

Кажется расточительным проверить условия дважды через каждую итерацию цикла, и я чувствую, что должен быть лучший способ выполнить то, что я хочу, без смутного запаха кода. У кого-нибудь есть интересные советы или уловки, чтобы сделать это более элегантным?

Это также мешает мне использовать расширенный цикл, что меня огорчает.

lines - это List<String>.

Кроме того, для тех, кто говорит, что я должен просто присоединиться ко всем String s с помощью \n, это не адекватное решение. Во-первых, на самом деле это не касается общей практики кодирования. Во-вторых, при записи в файл с помощью BufferedWriter важно использовать newLine() , а не писать \n.

36 голосов | спросил asteri 13 FriEurope/Moscow2013-12-13T18:38:49+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 18:38:49 +0400 2013, 18:38:49

11 ответов


22

Я предполагаю, что строки lines представляют собой некоторую коллекцию. Один из вариантов, который имеет немного меньший запах (хотя он все еще является запаховым), заключается в использовании итератора, который будет по существу выполнять ту же работу, но будет более читабельным:

for (Iterator<String> it = lines.iterator(); it.hasNext();) {
    writer.write(it.next());
    if (it.hasNext()) {
        writer.newline();
    }
}

Как я уже сказал, все это делает его более удобочитаемым ....

Другими параметрами являются дублирование write - один раз в цикле, а затем последний за пределами цикла:

if (!lines.isEmpty()) {
    int limit = lines.size() - 1;
    for (int i = 0; i < limit; i++) {
        ....
    }
    writer.write(lines.get(limit));
}

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

if (!lines.isEmpty()) {
    writer.write(lines.get(0));
    // start index at 1 instead of 0.
    for (int i = 1; i < lines.size(); i++) {
        writer.newline();
        writer.write(lines.get(limit));
    }
}
ответил rolfl 13 FriEurope/Moscow2013-12-13T18:59:41+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 18:59:41 +0400 2013, 18:59:41
13

Этот случай обычно связан с присоединением строк. Для Apache Commons Lang существует метод:

StringUtils.join(lines, "\n");

Также приведен шаблон, который можно использовать с циклом foreach

StringBuilder buf = new StringBuilder();
for (String line : lines) {
    if(buf.length() > 0) {
        buf.append("\n");
    }
    buf.append(line);
}
ответил rzymek 13 FriEurope/Moscow2013-12-13T19:27:08+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 19:27:08 +0400 2013, 19:27:08
8

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

CharSequence concatSep(Iterable<?> items, CharSequence separator){
    if(!lines.iterator().hasNext()) return "";

    StringBuilder b = new StringBuilder();
    for(Object item: items)
        b.append(item.toString()).append(separator);
    return b.delete(b.length() - separator.length(), b.length());
}

где separator - желаемый разделитель элементов, будь то символ новой строки, запятая, точка с запятой, табуляция или более одного символа.

В вашем случае: concatSep(lines, System.getProperty("line.seperator")).

ответил AJMansfield 13 FriEurope/Moscow2013-12-13T21:02:49+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 21:02:49 +0400 2013, 21:02:49
6

В общем случае вы можете либо вытащить первую или последнюю строку из цикла, либо переместить выход цикла в середину цикла, используя оператор break - изменение примера rolfl:

Iterator<String> it = lines.iterator()
if (it.hasNext()) {
    while (true) {
        writer.write(it.next());
        if (!it.hasNext()) 
            break;
        writer.newline();
    }
}

«Структурированное программирование с операторами goto» - классическая статья по обработке нестандартных циклов.

ответил James Moughan 13 FriEurope/Moscow2013-12-13T22:34:43+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 22:34:43 +0400 2013, 22:34:43
5

Небольшой поворот на ответ @rolfls:

BufferedWriter writer = new BufferedWriter(new FileWriter(file));
if ( lines.size() > 0 ) {
    writer.write(lines.get(0));
}
for (int i = 1; i < lines.size(); i++) {
    writer.newLine();
    writer.write(lines.get(i));
}

Точная же идея, однако, переместите дополнительную проверку вне цикла.

ответил smassey 13 FriEurope/Moscow2013-12-13T19:42:58+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 19:42:58 +0400 2013, 19:42:58
5

Просто немного проще:

BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size(); i++) {
    if(i > 0) writer.newLine();
    writer.write(lines.get(i));
}
ответил leonbloy 13 FriEurope/Moscow2013-12-13T22:36:47+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 22:36:47 +0400 2013, 22:36:47
5

почему бы не развернуть его: сначала напишите новую строку, кроме первой строки:

boolean newline = false;
for(int i = 0; i < lines.size(); i++) {
    if(newline) writer.newLine();
    else newline = true;
    writer.write(lines.get(i));
}
ответил ratchet freak 13 FriEurope/Moscow2013-12-13T22:44:37+04:00Europe/Moscow12bEurope/MoscowFri, 13 Dec 2013 22:44:37 +0400 2013, 22:44:37
5

В коде есть две проблемы:

  1. Как вы заметили, код if проверяется в каждом цикле, хотя вы знаете, что он будет признан недействительным только в последнем цикле. Хорошим способом избежать этой проблемы является обработка первого или последнего элемента вашего списка специально до (или после) ввода цикла. Обработка первого элемента по отдельности намного проще.

    Обратите внимание, что проверка if может быть довольно дешевой, если вы считаете, что вычисление size является постоянным временем. (Обратите внимание, что размер может быть рассчитан один раз перед циклом.) Оптимизация, вероятно, будет полностью незначительной (особенно, поскольку вы выполняете дорогостоящие операции ввода-вывода в цикле).

  2. Возможно, более тонкое: если lines является List, может не индексироваться (например, lines является LinkedList). Вызов lines.get(i) может быть O (i) , и весь цикл будет O (n²) , хотя O (n) выполнимо. Использование Iterator по предложению @rolfl - лучший способ избежать этой проблемы. Это может или не может улучшить читаемость в зависимости от вашего опыта, но это, безусловно, значительно улучшит производительность в зависимости от характера вашего List.

Кстати, эта проблема решается в основном в Java API: посмотрите на реализацию toString в AbstractCollection (просто замените разделители на свой собственный и удалите тест для e == this, который довольно специфичен):

public String toString() {
    Iterator<E> it = iterator();
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

Я был бы удивлен, если бы более общая реализация с определяемыми пользователем разделителями не была найдена в Apache Commons или Google Guava.

В любом случае, вот окончательный код, используя BufferedWriter вместо StringBuilder:

private static void <E> write(BufferedWriter writer, List<E> lines) {
    Iterator<E> it = lines.iterator();
    if (!it.hasNext()) return;

    for (;;) {
        E e = it.next();
        writer.write(e);
        if (!it.hasNext()) return;
        writer.newLine();
    }
}
ответил scand1sk 14 SatEurope/Moscow2013-12-14T19:59:45+04:00Europe/Moscow12bEurope/MoscowSat, 14 Dec 2013 19:59:45 +0400 2013, 19:59:45
2
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
   int i =0;
   for(;i < lines.size()-1; i++)
   {
     writer.write(lines.get(i));
     writer.newLine();
   }
   if(lines.size()>0)
   {
   writer.write(lines.get(i));
   }

Таким образом вы можете избежать условного утверждения каждый раз, сохраняя код одинаковым.

ответил theinsaneone 14 SatEurope/Moscow2013-12-14T09:59:05+04:00Europe/Moscow12bEurope/MoscowSat, 14 Dec 2013 09:59:05 +0400 2013, 09:59:05
2

Определенно, определенно возьмите инструкцию if из цикла. Люди всегда говорят о «оптимизаторе», но оптимизаторы разные, и все, что вы можете сделать, чтобы помочь, вероятно, хорошая идея.

//Write contents to the file
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size() - 1; i++) {
    writer.write(lines.get(i));
    writer.newLine();
}

// Write the last one without extra newline
if( lines.size() )
  writer.write(lines.get(lines.size()-1));
ответил bobobobo 14 SatEurope/Moscow2013-12-14T08:55:52+04:00Europe/Moscow12bEurope/MoscowSat, 14 Dec 2013 08:55:52 +0400 2013, 08:55:52
1

Вам не нужно проверять состояние, если вы изменяете ограничение цикла из lines.size() в lines.size() -1. Это гарантирует, что последняя запись в строках lines будет пропущена. Затем, после цикла, вы пишете последний текст line.

//Write contents to the file
 BufferedWriter writer = new BufferedWriter(new FileWriter(file));
 int last = lines.size()-1;
 for(int i = 0; i < last; i++) {
     writer.write(lines.get(i));
     writer.newLine();  //no condition check anymore
  }

 //write last line content
  writer.write(lines.get(last)); 
ответил blitz 1 +04002014-10-01T09:14:25+04:00312014bEurope/MoscowWed, 01 Oct 2014 09:14:25 +0400 2014, 09:14:25

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132