Najprostszy sposób rozdzielenia listy przecinkami?


104

Jaki jest najbardziej przejrzysty sposób rozdzielania listy przecinkami w Javie?

Znam kilka sposobów na zrobienie tego, ale zastanawiam się, jaki jest najlepszy (gdzie „najlepszy” oznacza najwyraźniejszy i / lub najkrótszy, a nie najskuteczniejszy.

Mam listę i chcę ją zapętlić, wypisując każdą wartość. Chcę wydrukować przecinek między każdą pozycją, ale nie po ostatniej (ani przed pierwszą).

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

Przykładowe rozwiązanie 1:

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

Przykładowe rozwiązanie 2 - utwórz podlistę:

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

Przykładowe rozwiązanie 3:

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

Te pierwsze traktują specjalnie; można zamiast tego potraktować specjalnie ostatnią osobę.

Nawiasem mówiąc, oto jak List toString jest zaimplementowany (jest dziedziczony z AbstractCollection), w Javie 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(", ");
    }
}

Wcześniej wychodzi z pętli, aby uniknąć przecinka po ostatnim elemencie. Przy okazji: po raz pierwszy widzę „(tę kolekcję)”; oto kod, który to sprowokuje:

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

Z zadowoleniem przyjmuję każde rozwiązanie, nawet jeśli używają nieoczekiwanych bibliotek (regexp?); a także rozwiązania w językach innych niż Java (np myślę Python / Ruby mają przeplatać funkcji - jak to jest , że ? zaimplementowane).

Wyjaśnienie : przez biblioteki mam na myśli standardowe biblioteki Java. W przypadku innych bibliotek rozważam je w innych językach i interesuje mnie, jak są zaimplementowane.

Zestaw narzędzi EDIT wspomniał o podobnym pytaniu: Ostatnia iteracja ulepszonej pętli for w java

I jeszcze: czy ostatni element w pętli zasługuje na osobne potraktowanie?


Odpowiedzi:


226

Java 8 i nowsze

Korzystanie z StringJoinerklasy:

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

Korzystanie Streami Collectors:

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

Java 7 i starsze

Zobacz także # 285523

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

1
Bardzo zaskakujący sposób przechowywania stanu! Jest prawie polimorficzny OO. Nie podoba mi się niepotrzebne przypisywanie każdej pętli, ale założę się, że jest bardziej wydajne niż jeśli. Większość rozwiązań powtarza test, o którym wiemy, że nie będzie prawdziwy - choć nieefektywne, są prawdopodobnie najbardziej przejrzyste. Dzięki za link!
13ren,

1
Podoba mi się to, ale ponieważ jest sprytne i zaskakujące, pomyślałem, że nie będzie odpowiednie (zaskoczenie nie jest dobre!). Jednak nie mogłem się powstrzymać. Nadal nie jestem tego pewien; jednak jest tak krótki, że łatwo go rozpracować, pod warunkiem, że pojawi się komentarz, który Cię ostrzeże.
13ren,

6
@ 13ren: Ty i wujek Bob musicie porozmawiać o kodzie, który jest „sprytny i zaskakujący”.
Stefan Kendall

Czuję, że zmarnowałem lata, nie wiedząc o tym ^^;
Jezioro

Dlaczego Java musi zamienić takie zadanie w brzydki kod?
Eduardo

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

1
Znaleziono źródło: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… Metoda łączenia ma 9 przeciążeń. Te oparte na iteracji są jak „Przykładowe rozwiązanie 3”; te oparte na indeksach używają: if (i> startIndex) {<add separator>}
13ren

Jednak naprawdę jestem po wdrożeniach. Dobrym pomysłem jest zamknięcie go w funkcji, ale w praktyce moim ogranicznikiem jest czasami nowa linia, a każda linia jest również wcięta do określonej głębokości. Chyba że ... dołącz (lista, "\ n" + wcięcie) ... czy to zawsze zadziała? Przepraszam, tylko głośno myślę.
13ren

2
Moim zdaniem to najładniejsze i najkrótsze rozwiązanie
Jesper Rønn-Jensen

prawdopodobnie list.iterator ()?
Vanuan

32

Java 8 udostępnia kilka nowych sposobów, aby to zrobić:

Przykład:

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

Podejście wykorzystujące strumienie zakłada import static java.util.stream.Collectors.joining;.


To jest IMHO, jeśli używasz java 8, najlepsza odpowiedź.
scravy


7

Jeśli używasz Spring Framework, możesz to zrobić za pomocą 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)

Dzięki, ale jak to jest realizowane?
13ren,

6

Na podstawie implementacji List toString w Javie:

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

Używa gramatyki takiej jak ta:

List --> (Item , )* Item

Opierając się na ostatniej zamiast na pierwszej, może sprawdzić, czy nie ma przecinka z pominięciem tego samego testu, aby sprawdzić koniec listy. Myślę, że ten jest bardzo elegancki, ale nie jestem pewien co do przejrzystości.


4
String delimiter = ",";
StringBuilder sb = new StringBuilder();
for (Item i : list) {
    sb.append(delimiter).append(i);
}
sb.toString().replaceFirst(delimiter, "");

4

Jest na to ładny sposób za pomocą Java 8:

List<String> list = Arrays.asList(array);
String joinedString = String.join(",", list);

TextUtils.join (",", lista);
Arul Pandian

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

Tak, to trochę tajemnicze, ale alternatywa dla tego, co już zostało opublikowane.
cherouvim

1
Myślę, że kod wygląda dobrze, IMHO jest ładniejszy niż twoja inna odpowiedź i prawie nie jest „tajemniczy”. Podsumuj to metodą zwaną Dołącz i śmiej się, ale spodziewałbym się, że język będzie miał lepszy sposób rozwiązania tak naprawdę częstego problemu.
Tarn

@tarn: myślisz, że wygląda dobrze, bo masz tło w Pythonie / Perlu;) Ja też to lubię
cherouvim

3

Jedną z opcji pętli foreach jest:

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

Aktualizacja : Jak wspomniał Dave Webb w komentarzach, może to nie dawać poprawnych wyników, jeśli pierwsze pozycje na liście to puste ciągi.


meh .. nadal ma if w pętli.
sfossen

Możesz użyć pętli foreach, aby ją skrócić.
Peter Lawrey,

To nie zadziała, jeśli pierwsza pozycja na liście jest pustym ciągiem.
Dave Webb,

@Dave Webb: Tak, dzięki. Pytanie nie ma jednak takiego wymagania.
cherouvim

@cherovium: Pytanie nie mówi, że żaden z elementów nie będzie pustym ciągiem, więc jest to niejawne wymaganie. Jeśli tworzysz plik CSV, puste pola mogą być dość powszechne.
Dave Webb,

2

Zwykle robię to:

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

Chociaż myślę, że od teraz będę sprawdzał to zgodnie z implementacją Java;)


To ta sama logika, co próbka 3, podoba mi się, ponieważ nie robi nic zbędnego. Jednak nie wiem, czy jest to najwyraźniejsze. BTW: jest nieco bardziej wydajne, jeśli umieścisz while wewnątrz if, unikając dwóch testów, gdy lista jest pusta. Dla mnie to również trochę jaśniejsze.
13ren,

Na litość boską, użyj StringBuilder
scravy

2

Jeśli możesz użyć Groovy (który działa na JVM):

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

dzięki, ale interesują mnie wdrożenia. Jak to jest realizowane?
13ren

2

(Skopiuj moją własną odpowiedź stąd .) Wiele z opisanych tutaj rozwiązań jest nieco przesadzonych, IMHO, zwłaszcza te, które opierają się na bibliotekach zewnętrznych. Istnieje ładny, przejrzysty idiom do tworzenia listy oddzielonej przecinkami, którego zawsze używałem. Opiera się na operatorze warunkowym (?):

Edycja : Oryginalne rozwiązanie poprawne, ale według komentarzy nieoptymalne. Próbuję po raz drugi:

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

Proszę bardzo, w 4 wierszach kodu, w tym deklaracja tablicy i StringBuilder.

2. edycja : Jeśli masz do czynienia z Iteratorem:

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

To i stackoverflow.com/questions/668952/… powyżej są podobne. Jest krótki i jasny, ale jeśli masz tylko iterator, najpierw musisz dokonać konwersji. Nieefektywne, ale może warto ze względu na przejrzystość tego podejścia?
13ren

Ugh, konkatenacja ciągów tworząca tymczasowy StringBuilder w ramach użycia StringBuilder. Nieskuteczny. Lepsze (bardziej podobne do Javy, ale lepiej działający kod) byłoby „if (i> 0) {builder.append (',');}”, a następnie builder.append (tablica [i]);
Eddie

Tak, jeśli spojrzysz na kod bajtowy, masz rację. Nie mogę uwierzyć, że takie proste pytanie może być tak skomplikowane. Zastanawiam się, czy kompilator może tutaj zoptymalizować.
Julien Chastang

Dostarczono drugie, miejmy nadzieję, lepsze rozwiązanie.
Julien Chastang

Lepiej byłoby je podzielić (aby ludzie mogli głosować na jeden lub drugi).
13ren,

1

Zwykle używam czegoś podobnego do wersji 3. Działa dobrze c / c ++ / bash / ...: P


1

Nie skompilowałem tego ... ale powinno działać (lub być blisko pracy).

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

Podoba mi się ten sposób, wolałbym po prostu inne formatowanie. Ale żeby wspomnieć o jednej drobnej poprawce: jeśli lista jest pusta, spowoduje to zgłoszenie wyjątku IndexOutOfBounds. Więc albo sprawdź przed setLength, albo użyj Math.max (0, ...)
tb-

1

To jest bardzo krótkie, bardzo jasne, ale daje mojej wrażliwości pełzające okropności. Trochę niewygodne jest również dostosowanie się do różnych ograniczników, zwłaszcza jeśli jest to ciąg (nie znak).

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

Zainspirowany: Ostatnia iteracja ulepszonej pętli for w Javie


1
StringBuffer sb = new StringBuffer();

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

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

Uważam, że ten jest bardzo łatwy do osadzenia w statycznej klasie narzędziowej, a jednocześnie jest bardzo łatwy do odczytania i zrozumienia. Nie wymaga również żadnych dodatkowych zmiennych ani stanu poza samą listą, pętlą i ogranicznikiem.
Joachim H. Skeie

Zapewni to coś takiego:Item1Item2, Item3, Item4,

Na litość boską, użyj StringBuilder
scravy

1

Trochę mi się podoba to podejście, które jakiś czas temu znalazłem na blogu. Niestety nie pamiętam nazwy / adresu URL bloga.

Możesz utworzyć klasę narzędziową / pomocniczą, która wygląda następująco:

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

Korzystanie z klasy pomocnika jest proste:

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

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

Uwaga: powinno to być „separator”, a nie „separator”.
Michael Myers

1

Ponieważ separatorem jest „”, możesz użyć dowolnego z poniższych:

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

Jeśli Twoim ogranicznikiem jest Cokolwiek innego, to nie zadziała.


1

Podoba mi się to rozwiązanie:

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

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

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

Zakładam, że może też wykorzystać StringBuilder.


1

Moim zdaniem najłatwiej to przeczytać i zrozumieć:

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

0

W Pythonie jest to łatwe

„,”. dołącz (twoja lista)

W języku C # w klasie String istnieje metoda statyczna

String.Join (",", yourlistofstrings)

Przepraszam, nie jestem pewien co do Javy, ale pomyślałem, że spytam, gdy pytasz o inne języki. Jestem pewien, że coś podobnego byłoby w Javie.


IIRC, otrzymujesz końcowy w wersji .Net. :-(

1
Właśnie przetestowano, bez końcowego separatora w .NET lub Pythonie
tarn

1
dzięki! Znalazłem jego źródło: svn.python.org/view/python/branches/release22-branch/Objects/ ... wyszukaj „Catenate Everything”. Pomija separator dla ostatniej pozycji.
13ren

@ 13ren Świetnie! Czy to pomaga? Miałem blokadę i szybko przypomniałem sobie, dlaczego zdecydowałem się nie programować w C w tych dniach: P
tarn

@tarn, tak, to interesujący przykład, ponieważ dodaje komendę tylko wtedy, gdy nie jest to ostatnia pozycja: if (i <seqlen - 1) {<add separator>}
13ren

0

Możesz także bezwarunkowo dodać ciąg separatora, a po pętli usunąć dodatkowy separator na końcu. Wtedy „jeśli lista jest pusta, to zwróć ten ciąg” na początku pozwoli ci uniknąć sprawdzania na końcu (ponieważ nie możesz usunąć znaków z pustej listy)

Więc tak naprawdę pytanie brzmi:

„Biorąc pod uwagę pętlę i„ jeśli ”, jak myślisz, jaki jest najlepszy sposób na połączenie tych elementów?


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
Myślę, że chciałeś powiedziećlistToString.substring(0, listToString.length()-separator.length());
13ren,

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

Oparty na najjaśniejszym sposobie rozdzielania listy przecinkami (Java)?

Posługując się tym pomysłem: czy ostatni element w pętli zasługuje na osobne potraktowanie?


1
Aby to zadziałało, musisz wiedzieć, że na liście znajduje się co najmniej 1 element, w przeciwnym razie pierwsza pętla for ulegnie awarii z wyjątkiem IndexOutOfBoundsException.
Joachim H. Skeie

@Joachim dzięki, masz oczywiście rację. Co za brzydkie rozwiązanie, które napisałem! Zmienię się for (int i=0; i<1; i++)na if (array.length>0).
13ren

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
Właśnie zauważyłem, że jest podobna do odpowiedzi, której udzielił TofuBeer
Eblis

0

Żadna z odpowiedzi nie używa dotychczas rekursji ...

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

metoda

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

Stosowanie

Biorąc pod uwagę tablicę [1, 2, 3]

join(myArray, ",") // 1,2,3
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.