Best Loop Idiom для специального корпуса последнего элемента - PullRequest
48 голосов
/ 24 июня 2010

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

Есть ли лучшая практика идиома или элегантная форма, которая не требует дублирования кода или добавления в if, иначе в цикле.

Например, у меня есть список строк, которые я хочу напечатать в списке через запятую. (решение do while уже предполагает, что список содержит 2 или более элементов, в противном случае это будет так же плохо, как и более правильный цикл for с условным выражением).

например. List = ("собака", "кошка", "летучая мышь")

Я хочу напечатать "[собака, кошка, летучая мышь]"

Я представляю 2 метода

  1. Для цикла с условным кодом

    public static String forLoopConditional(String[] items) {
    
    String itemOutput = "[";
    
    for (int i = 0; i < items.length; i++) {
        // Check if we're not at the last element
        if (i < (items.length - 1)) {
            itemOutput += items[i] + ", ";
        } else {
            // last element
            itemOutput += items[i];
        }
    }
    itemOutput += "]";
    
    return itemOutput;
     }
    
  2. делать, пока цикл заполняет цикл

    public static String doWhileLoopPrime(String[] items) {
    String itemOutput = "[";
    int i = 0;
    
    itemOutput += items[i++];
    if (i < (items.length)) {
        do {
            itemOutput += ", " + items[i++];
        } while (i < items.length);
    }
    itemOutput += "]";
    
    return itemOutput;
    }
    

    Класс тестера:

    public static void main(String[] args) {
        String[] items = { "dog", "cat", "bat" };
    
        System.out.println(forLoopConditional(items));
        System.out.println(doWhileLoopPrime(items));
    
    }
    

В классе Java AbstractCollection он имеет следующую реализацию (немного многословно, поскольку содержит все проверки ошибок в крайнем случае, но неплохо).

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

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

Ответы [ 18 ]

43 голосов
/ 24 июня 2010

Я обычно пишу это так:

static String commaSeparated(String[] items) {
    StringBuilder sb = new StringBuilder();
    String sep = "";
    for (String item: items) {
        sb.append(sep);
        sb.append(item);
        sep = ",";
    }
    return sb.toString();
}
24 голосов
/ 26 июня 2010

В этих ответах много циклов for, но я считаю, что цикл Iterator и while читается намного легче.Например:

Iterator<String> itemIterator = Arrays.asList(items).iterator();
if (itemIterator.hasNext()) {
  // special-case first item.  in this case, no comma
  while (itemIterator.hasNext()) {
    // process the rest
  }
}

Этот подход принят Столяром в коллекциях Google, и я считаю его очень читабельным.

14 голосов
/ 24 июня 2010
string value = "[" + StringUtils.join( items, ',' ) + "]";
7 голосов
/ 24 июня 2010

Обычно я проверяю, равна ли переменная индекса нулю, например:

var result = "[ ";
for (var i = 0; i < list.length; ++i) {
    if (i != 0) result += ", ";
    result += list[i];
}
result += " ]";

Но, конечно, это только в том случае, если мы говорим о языках, у которых нет Array.join (",") метод.; -)

6 голосов
/ 24 июня 2010

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

public static String prettyPrint(String[] items) {
    String itemOutput = "[";
    boolean first = true;

    for (int i = 0; i < items.length; i++) {
        if (!first) {
            itemOutput += ", ";
        }

        itemOutput += items[i];
        first = false;
    }

    itemOutput += "]";
    return itemOutput;
}
2 голосов
/ 24 июня 2010

Мне нравится использовать флаг для первого элемента.

 ArrayList<String> list = new ArrayList()<String>{{
       add("dog");
       add("cat");
       add("bat");
    }};
    String output = "[";
    boolean first = true;
    for(String word: list){
      if(!first) output += ", ";
      output+= word;
      first = false;
    }
    output += "]";
2 голосов
/ 24 июня 2010

Так как ваш случай просто обрабатывает текст, вам не нужно условие внутри цикла.Пример AC:

char* items[] = {"dog", "cat", "bat"};
char* output[STRING_LENGTH] = {0};
char* pStr = &output[1];
int   i;

output[0] = '[';
for (i=0; i < (sizeof(items) / sizeof(char*)); ++i) {
    sprintf(pStr,"%s,",items[i]);
    pStr = &output[0] + strlen(output);
}
output[strlen(output)-1] = ']';

Вместо добавления условного выражения, чтобы избежать генерации запятой, продолжайте и генерируйте ее (чтобы ваш цикл был простым и безусловным) и просто перезапишите его в конце.Много раз я нахожу более понятным генерировать особый случай, как и любую другую итерацию цикла, а затем вручную заменять его в конце (хотя, если код «заменить его» занимает более пары строк, этот метод на самом деле может стать сложнеечтения).

2 голосов
/ 08 ноября 2016

Java 8 решение, если его кто-то ищет:

String res = Arrays.stream(items).reduce((t, u) -> t + "," + u).get();
2 голосов
/ 24 июня 2010

Я бы пошел с вашим вторым примером, т.е.обработайте специальный случай вне цикла, просто напишите его немного проще:

String itemOutput = "[";

if (items.length > 0) {
    itemOutput += items[0];

    for (int i = 1; i < items.length; i++) {
        itemOutput += ", " + items[i];
    }
}

itemOutput += "]";
1 голос
/ 24 июня 2010

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

String[] items = { "dog", "cat", "bat" };
String result = "[" + joinListOfStrings(items, ", ") + "]"

с

public static String joinListOfStrings(String[] items, String sep) {
    StringBuffer result;
    for (int i=0; i<items.length; i++) {
        result.append(items[i]);
        if (i < items.length-1) buffer.append(sep);
    }
    return result.toString();
}

Если у вас есть Collection вместо String[], вы также можете использовать итераторы и метод hasNext(), чтобы проверить, последний это или нет.

...