Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to join items of list, but use a different delimiter for the last item?

Tags:

java

Given list like:

List<String> names = Lists.newArrayList("George", "John", "Paul", "Ringo")

I'd like to transform it to a string like this:

George, John, Paul and Ringo

I can do it with rather clumsy StringBuilder thing like so:

String nameList = names.stream().collect(joining(", "));
        if (nameList.contains(",")) {
            StringBuilder builder = new StringBuilder(nameList);
            builder.replace(nameList.lastIndexOf(','), nameList.lastIndexOf(',') + 1, " and");
            return builder.toString();
        }

Is there a bit more elegant approach? I don't mind using a library if needed.

NOTES:

  • I could use an old for loop with an index, but I am not looking for such a solution
  • There are no commas within the values (names)
like image 644
Xorty Avatar asked Apr 29 '15 15:04

Xorty


2 Answers

As you already did most of it I would introduce a second method "replaceLast" which is not in the JDK for java.lang.String so far:

import java.util.List;
import java.util.stream.Collectors;

public final class StringUtils {
 private static final String AND = " and ";
 private static final String COMMA = ", ";

 // your initial call wrapped with a replaceLast call
 public static String asLiteralNumeration(List<String> strings) {
    return replaceLast(strings.stream().collect(Collectors.joining(COMMA)), COMMA, AND);
 }

 public static String replaceLast(String text, String regex, String replacement) {
    return text.replaceFirst("(?s)" + regex + "(?!.*?" + regex + ")", replacement);
 }
}

You might change the delimiters and params as well. Here the test for your requirements so far:

@org.junit.Test
public void test() {
 List<String> names = Arrays.asList("George", "John", "Paul", "Ringo");
 assertEquals("George, John, Paul and Ringo", StringUtils.asLiteralNumeration(names));

 List<String> oneItemList = Arrays.asList("Paul");
 assertEquals("Paul", StringUtils.asLiteralNumeration(oneItemList));

 List<String> emptyList = Arrays.asList("");
 assertEquals("", StringUtils.asLiteralNumeration(emptyList));

}
like image 71
swinkler Avatar answered Oct 09 '22 19:10

swinkler


I'm not sure how elegant this is, but it works. The annoying part is that you have to reverse the List.

List<String> list = Arrays.asList("George", "John", "Paul", "Ringo");
String andStr = " and ";
String commaStr = ", ";
int n = list.size();
String result = list.size() == 0 ? "" :
        IntStream.range(0, n)
                 .mapToObj(i -> list.get(n - 1 - i))
                 .reduce((s, t) -> t + (s.contains(andStr) ? commaStr : andStr) + s)
                 .get();
System.out.println(result);

However, I think the best solution is this.

StringBuilder sb = new StringBuilder();
int n = list.size();
for (String string : list) {
    sb.append(string);
    if (--n > 0)
        sb.append(n == 1 ? " and " : ", ");
}
System.out.println(sb);

It's clear, efficient, and obviously works. I don't think Streams are a good fit for this problem.

like image 30
Paul Boddington Avatar answered Oct 09 '22 20:10

Paul Boddington