Найпростіший спосіб розділити комою список?


104

Який найясніший спосіб розділити комою список на Java?

Я знаю кілька способів зробити це, але мені цікаво, що найкращий спосіб (де «найкращий» означає найясніший та / або найкоротший, не найефективніший.

У мене є список, і я хочу перетворити його, надрукувавши кожне значення. Я хочу надрукувати кому між кожним елементом, але не після останнього (ні перед першим).

List --> Item ( , Item ) *
List --> ( Item , ) * Item

Зразок розчину 1:

boolean isFirst = true;
for (Item i : list) {
  if (isFirst) {
    System.out.print(i);        // no comma
    isFirst = false;
  } else {
    System.out.print(", "+i);   // comma
  }
}

Зразок рішення 2 - створити підсклад:

if (list.size()>0) {
  System.out.print(list.get(0));   // no comma
  List theRest = list.subList(1, list.size());
  for (Item i : theRest) {
    System.out.print(", "+i);   // comma
  }
}

Зразок розчину 3:

  Iterator<Item> i = list.iterator();
  if (i.hasNext()) {
    System.out.print(i.next());
    while (i.hasNext())
      System.out.print(", "+i.next());
  }

Вони звертаються до першого пункту спеціально; замість цього можна було б ставитися до останнього спеціально.

До речі, ось як List toString буде реалізована (вона успадкувала від AbstractCollection), в Java 1.6:

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

Він виходить з циклу рано, щоб уникнути коми після останнього елемента. До речі: це перший раз, коли я згадую, що бачив "(цю колекцію)"; ось код, щоб спровокувати це:

List l = new LinkedList();
l.add(l);
System.out.println(l);

Я вітаю будь-яке рішення, навіть якщо вони використовують несподівані бібліотеки (regexp?); а також рішення в відмінних від Java мов (наприклад , я думає , Python / рубін має пересипати функцію - як що ? реалізовано).

Уточнення : під бібліотеками я маю на увазі стандартні бібліотеки Java. Для інших бібліотек я розглядаю їх іншими мовами, і мені цікаво знати, як вони реалізовані.

Інструментарій EDIT згадував аналогічне запитання: Остання ітерація покращеного циклу в java

І ще: чи заслуговує останній елемент у циклі окремої обробки?


Відповіді:


226

Java 8 та новіших версій

Використання StringJoinerкласу:

StringJoiner joiner = new StringJoiner(",");
for (Item item : list) {
    joiner.add(item.toString());
}
return joiner.toString();

Використання Streamта Collectors:

return list.stream().
       map(Object::toString).
       collect(Collectors.joining(",")).toString();

Java 7 і новіші версії

Дивіться також # 285523

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

1
Дуже дивовижний спосіб зберігання держави! Це майже OO поліморфний. Мені не подобається непотрібне призначення кожного циклу, але я думаю, що це більш ефективно, ніж якщо. Більшість рішень повторюють тест, який ми знаємо, що це не буде правдою - хоча вони неефективні, вони, мабуть, найясніші. Дякуємо за посилання!
13рін

1
Мені це подобається, але, оскільки це розумно і дивно, я вважав, що використовувати його не доречно (дивно, це не добре!). Однак я не міг собі допомогти. Я досі не впевнений у цьому; однак він настільки короткий, що його легко розробити за умови, що є коментар, який підкаже вас.
13рін

6
@ 13ren: Вам та дядькові Бобу потрібно поговорити про код, який "розумний і дивний".
Стефан Кендалл

Я відчуваю, що прожив роки, не знаючи цього ^^;
Озеро

Чому Java потрібно перетворити таке завдання в некрасивий код?
Едуардо

82
org.apache.commons.lang3.StringUtils.join(list,",");

1
Знайдено джерело: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… Метод приєднання має 9 перевантажень. На основі ітерації такі, як "Зразок рішення 3"; користувачі на основі індексу використовують: if (i> startIndex) {<add separator>}
13,

Я дійсно після впровадження, хоча. Це гарна ідея, щоб перетворити його на функцію, але на практиці мій роздільник є часом новим рядком, і кожен рядок також відступає на певну глибину. Якщо ... не приєднайтесь (список, "\ n" + відступ) ... це завжди працюватиме? Вибачте, просто думаю вголос.
13рен

2
На мою думку, це найкрасивіше і найкоротше рішення
Jesper Rønn-Jensen

ймовірно, list.iterator ()?
Вануан

32

Java 8 пропонує кілька нових способів зробити це:

  • joinСпосіб за String.
  • joiningКолектор для потоків з Collectorsкласу.
  • StringJoinerКлас.

Приклад:

// Util method for strings and other char sequences
List<String> strs = Arrays.asList("1", "2", "3");
String listStr1 = String.join(",", strs);

// For any type using streams and collectors
List<Object> objs = Arrays.asList(1, 2, 3);
String listStr2 = objs.stream()
    .map(Object::toString)
    .collect(joining(",", "[", "]"));

// Using the new StringJoiner class
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.setEmptyValue("<empty>");
for (Integer i : objs) {
  joiner.add(i.toString());
}
String listStr3 = joiner.toString();

Підхід із використанням потоків передбачає import static java.util.stream.Collectors.joining;.


Це IMHO, якщо ви використовуєте java 8, найкраща відповідь.
scravy


7

Якщо ви використовуєте Spring Framework, ви можете зробити це за допомогою StringUtils :

public static String arrayToDelimitedString(Object[] arr)
public static String arrayToDelimitedString(Object[] arr, String delim)
public static String collectionToCommaDelimitedString(Collection coll)
public static String collectionToCommaDelimitedString(Collection coll, String delim)

Дякую, але як це реалізується?
13рін

6

На основі реалізації списку Java toString:

Iterator i = list.iterator();
for (;;) {
  sb.append(i.next());
  if (! i.hasNext()) break;
  ab.append(", ");
}

Тут використовується така граматика:

List --> (Item , )* Item

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




3
for(int i=0, length=list.size(); i<length; i++)
  result+=(i==0?"":", ") + list.get(i);

Так, це трохи задумливо, але альтернатива тому, що вже розміщено.
cherouvim

1
Я думаю, що код виглядає чудово, IMHO - це більше, ніж ваша інша відповідь, і навряд чи «кричущий». Завершіть це методом під назвою Приєднатися і посміятися, все ж я би сподівався, що мова матиме кращий спосіб вирішення того, що насправді є загальною проблемою.
tarn

@tarn: ти вважаєш, що це виглядає добре, тому що ти маєш Python / perl фон;) Мені це також подобається
cherouvim

3

Одним із варіантів циклу foreach є:

StringBuilder sb = new StringBuilder();
for(String s:list){
  if (sb.length()>0) sb.append(",");
  sb.append(s);
}

2
StringBuffer result = new StringBuffer();
for(Iterator it=list.iterator; it.hasNext(); ) {
  if (result.length()>0)
    result.append(", ");
  result.append(it.next());
}

Оновлення : Як згадував Дейв Вебб у коментарях, це може не дати правильних результатів, якщо перші пункти у списку є порожніми рядками.


meh .. все ще є if у циклі.
sfossen

Ви можете використовувати петлю передбачення, щоб зробити її коротшою.
Пітер Лорі

Це не спрацює, якщо перший елемент у списку порожній рядок.
Дейв Вебб

@Dave Webb: Так, дякую. Питання не має такої вимоги.
cherouvim

@cherovium: Питання не говорить про те, що жоден із елементів не буде порожнім рядком, тому це неявна вимога. Якщо ви створюєте файл CSV, порожні поля можуть бути досить поширеними.
Дейв Вебб

2

Я зазвичай роблю це:

StringBuffer sb = new StringBuffer();
Iterator it = myList.iterator();
if (it.hasNext()) { sb.append(it.next().toString()); }
while (it.hasNext()) { sb.append(",").append(it.next().toString()); }

Хоча я думаю, що зараз я перевіряю цю перевірку відповідно до реалізації Java;)


Така ж логіка, що і для зразка 3, мені це подобається, оскільки вона не робить нічого зайвого. Однак я не знаю, чи це найясніше. BTW: трохи ефективніше, якщо ви помістите час всередині if, уникаючи двох тестів, коли список порожній. Це також робить мене трохи чіткіше.
13рен

Заради небес використовуйте StringBuilder
scravy

2

Якщо ви можете використовувати Groovy (який працює на JVM):

def list = ['a', 'b', 'c', 'd']
println list.join(',')

дякую, але мені цікаві реалізації. Як це реалізується?
13рен

2

(Скопіюйте вставку моєї власної відповіді звідси .) Багато описаних тут рішень трохи вище, IMHO, особливо ті, які покладаються на зовнішні бібліотеки. Є приємна чиста, чітка ідіома для досягнення списку, розділеного комами, який я завжди використовував. Він покладається на умовного (?) Оператора:

Редагувати : оригінальне рішення правильне, але неоптимальне відповідно до коментарів. Спробуйте вдруге:

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

Там ви переходите до чотирьох рядків коду, включаючи оголошення масиву та StringBuilder.

2-е редагування : якщо ви маєте справу з ітератором:

    List<Integer> list = Arrays.asList(1, 2, 3);
    StringBuilder builder = new StringBuilder();
    for (Iterator it = list.iterator(); it.hasNext();)
        builder.append(it.next()).append(it.hasNext() ? "," : "");

Це та stackoverflow.com/questions/668952/… вище подібні. Це коротко і зрозуміло, але якщо у вас є лише ітератор, спочатку потрібно здійснити перетворення. Неефективний, але, може, варто того, щоб зрозуміти такий підхід?
13рен

Фу, строкове об'єднання, створюючи тимчасовий StringBuilder в рамках використання StringBuilder. Неефективна. Краще (більше подобається Java, але краще працює код) було б "якщо (i> 0) {builder.append (',');}", а за ним - builder.append (масив [i]);
Едді

Так, якщо ви подивитеся на байт-код, ви маєте рацію. Я не можу повірити, що таке просте запитання може стати таким складним. Цікаво, чи може тут компілятор оптимізувати.
Жульєн Частанг

За умови 2-го, сподіваємось, кращого рішення.
Жульєн Частанг

Можливо, краще розділити їх (щоб люди могли голосувати за тих чи інших).
13рен

1

Зазвичай я використовую щось подібне до версії 3. Це добре працює c / c ++ / bash / ...: P


1

Я не компілював його ... але повинен працювати (або бути близьким до роботи).

public static <T> String toString(final List<T> list, 
                                  final String delim)
{
    final StringBuilder builder;

    builder = new StringBuilder();

    for(final T item : list)
    {
        builder.append(item);
        builder.append(delim);
    }

    // kill the last delim
    builder.setLength(builder.length() - delim.length());

    return (builder.toString());
}

Мені це подобається, я просто віддаю перевагу іншому форматуванню. Але зазначити одне невелике виправлення: якщо список порожній, це призведе до виключення IndexOutOfBounds. Отже, або робіть перевірку перед setLength, або використовуйте Math.max (0, ...)
tb-

1

Це дуже коротко, дуже ясно, але надає моїм чутливості повзучі жахи. Також трохи незручно адаптуватися до різних роздільників, особливо якщо String (а не char).

for (Item i : list)
  sb.append(',').append(i);
if (sb.charAt(0)==',') sb.deleteCharAt(0);

Натхненний: Остання ітерація посиленого циклу в Java


1
StringBuffer sb = new StringBuffer();

for (int i = 0; i < myList.size(); i++)
{ 
    if (i > 0) 
    {
        sb.append(", ");
    }

    sb.append(myList.get(i)); 
}

Мені здається, що цей елемент дуже легко вбудувати у статичний клас корисності, а також його дуже легко читати та розуміти. Він також не потребує додаткових змінних або станів, крім самого списку, циклу та роздільника.
Йоахім Х. Скіє

Це забезпечить щось на зразок:Item1Item2, Item3, Item4,

Заради небес використовуйте StringBuilder
scravy

1

Мені дещо подобається такий підхід, який я знайшов у своєму блозі деякий час тому. На жаль, я не пам’ятаю ім'я / URL блогу.

Ви можете створити клас утиліти / помічника, який виглядає приблизно так:

private class Delimiter
{
    private final String delimiter;
    private boolean first = true;

    public Delimiter(String delimiter)
    {
        this.delimiter = delimiter;
    }

    @Override
    public String toString()
    {
        if (first) {
            first = false;
            return "";
        }

        return delimiter;
    }
}

Використовувати клас помічників просто:

StringBuilder sb = new StringBuilder();
Delimiter delimiter = new Delimiter(", ");

for (String item : list) {
    sb.append(delimiter);
    sb.append(item);
}

Лише зауваження: він повинен бути "роздільником", а не "деліметром".
Майкл Майерс

1

Оскільки ваш роздільник є ",", ви можете використовувати будь-яке з наступного:

public class StringDelim {

    public static void removeBrackets(String string) {
        System.out.println(string.substring(1, string.length() - 1));
    }

    public static void main(String... args) {
        // Array.toString() example
        String [] arr = {"Hi" , "My", "name", "is", "br3nt"};
        String string = Arrays.toString(arr);
        removeBrackets(string);

        // List#toString() example
        List<String> list = new ArrayList<String>();
        list.add("Hi");
        list.add("My");
        list.add("name");
        list.add("is");
        list.add("br3nt");
        string = list.toString();
        removeBrackets(string);

        // Map#values().toString() example
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("1", "Hi");
        map.put("2", "My");
        map.put("3", "name");
        map.put("4", "is");
        map.put("5", "br3nt");
        System.out.println(map.values().toString());
        removeBrackets(string);

        // Enum#toString() example
        EnumSet<Days> set = EnumSet.allOf(Days.class);
        string = set.toString();
        removeBrackets(string);
    }

    public enum Days {
        MON("Monday"),
        TUE("Tuesday"),
        WED("Wednesday"),
        THU("Thursday"),
        FRI("Friday"),
        SAT("Saturday"),
        SUN("Sunday");

        private final String day;

        Days(String day) {this.day = day;}
        public String toString() {return this.day;}
    }
}

Якщо ваш роздільник є іншим, це не допоможе вам.


1

Мені подобається таке рішення:

String delim = " - ", string = "";

for (String item : myCollection)
    string += delim + item;

string = string.substring(delim.length());

Я припускаю, що він також може використовувати StringBuilder.


1

На мою думку, це найпростіше прочитати і зрозуміти:

StringBuilder sb = new StringBuilder();
for(String string : strings) {
    sb.append(string).append(',');
}
sb.setLength(sb.length() - 1);
String result = sb.toString();

0

У Python це легко

",". приєднатися (ваш список)

У C # є статичний метод класу String

String.Join (",", yourlistofstrings)

Вибачте, не впевнений у Java, але подумав, що я розкажу, як ви запитали про інші мови. Я впевнений, що у Java буде щось подібне.


IIRC, ви отримуєте трейлінг у версії .Net. :-(

1
Щойно перевірено, без обмежувача в .NET або Python
tarn

1
Дякую! Я знайшов джерело для нього: svn.python.org/view/python/branches/release22-branch/Objects/… шукаю "Catenate all". Він пропускає роздільник для останнього елемента.
13рен

@ 13ren Чудово зроблено! Чи допомагає це? У мене був замок і швидко згадав, чому я вирішу не програмувати на C сьогодні: P
tarn

@tarn, так, це цікавий приклад, оскільки він додає учасника, лише якщо це не останній пункт: if (i <seqlen - 1) {<add separator>}
13,

0

Ви також можете беззастережно додати рядок відмежувача, а після циклу видалити зайвий роздільник в кінці. Тоді "якщо список порожній, тоді поверніть цей рядок" на початку дозволить вам уникнути перевірки в кінці (оскільки ви не можете видалити символи з порожнього списку)

Тож питання справді таке:

"З огляду на цикл і" if ", що, на ваш погляд, є найяснішим способом об'єднати їх?"


0
public static String join (List<String> list, String separator) {
  String listToString = "";

  if (list == null || list.isEmpty()) {
   return listToString;
  }

  for (String element : list) {
   listToString += element + separator;
  }

  listToString = listToString.substring(0, separator.length());

  return listToString;
}

1
Я думаю, ти мав намір сказатиlistToString.substring(0, listToString.length()-separator.length());
13

0
if (array.length>0)          // edited in response Joachim's comment
  sb.append(array[i]);
for (int i=1; i<array.length; i++)
  sb.append(",").append(array[i]);

На основі Clearest способу обмеження комою списку (Java)?

Використання цієї ідеї: чи заслуговує останній елемент у циклі окремої обробки?


1
Щоб це працювало, вам потрібно знати, що в списку є щонайменше 1 елемент, інакше ваш перший for-цикл вийде з ладу з IndexOutOfBoundsException.
Йоахім Х. Скії

@Joachim спасибі, ти, звичайно, маєш рацію. Яке потворне рішення я написав! Я перейду for (int i=0; i<1; i++)на if (array.length>0).
13н.

0
public String toString(List<Item> items)
{
    StringBuilder sb = new StringBuilder("[");

    for (Item item : items)
    {
        sb.append(item).append(", ");
    }

    if (sb.length() >= 2)
    {
        //looks cleaner in C# sb.Length -= 2;
        sb.setLength(sb.length() - 2);
    }

    sb.append("]");

    return sb.toString();
}

1
Щойно помітив, це схоже на відповідь, яку дав
TofuBeer

0

Жодна з відповідей поки не використовує рекурсії ...

public class Main {

    public static String toString(List<String> list, char d) {
        int n = list.size();
        if(n==0) return "";
        return n > 1 ? Main.toString(list.subList(0, n - 1), d) + d
                  + list.get(n - 1) : list.get(0);
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"1","2","3"});
        System.out.println(Main.toString(list, ','));
    }

}

0
private String betweenComma(ArrayList<String> strings) {
    String united = "";
    for (String string : strings) {
        united = united + "," + string;
    }
    return united.replaceFirst(",", "");
}

-1

Метод

String join(List<Object> collection, String delimiter){
    StringBuilder stringBuilder = new StringBuilder();
    int size = collection.size();
    for (Object value : collection) {
        size --;
        if(size > 0){
            stringBuilder.append(value).append(delimiter);
        }
    }

    return stringBuilder.toString();
}

Використання

Враховуючи масив [1,2,3]

join(myArray, ",") // 1,2,3
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.