如何加入列表项,但为最后一项使用不同的分隔符?

时间:2015-04-29 15:53:02

标签: java

给出如下列表:

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

我想将它转换为这样的字符串:

George, John, Paul and Ringo

我可以用相当笨拙的StringBuilder这样的事情做到这一点:

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

有更优雅的方法吗?如果需要,我不介意使用库。

注意

  • 我可以使用带索引的旧for循环,但我不是在寻找这样的解决方案
  • 值(名称)
  • 中没有逗号

6 个答案:

答案 0 :(得分:2)

我不确定这是多么优雅,但它确实有效。令人讨厌的部分是你必须反转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);

但是,我认为最好的解决方案就是这个。

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

它清晰,高效,显然有效。我认为Stream不适合这个问题。

答案 1 :(得分:2)

正如你已经完成的大部分内容,我将介绍第二种方法&#34; replaceLast&#34;到目前为止,它不在java.lang.String的JDK中:

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

您也可以更改分隔符和参数。到目前为止测试您的要求:

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

}

答案 2 :(得分:1)

如果您不介意使用迭代器,则可行:

private static String specialJoin(Iterable<?> list, String sep, String lastSep) {
    StringBuilder result = new StringBuilder();
    final Iterator<?> i = list.iterator();
    if (i.hasNext()) {
        result.append(i.next());
        while (i.hasNext()) {
            final Object next = i.next();
            result.append(i.hasNext() ? sep : lastSep);
            result.append(next);
        }
    }
    return result.toString();
}

熟悉api的人可能很容易将其重写为收藏家。

答案 3 :(得分:1)

这是使用流API的优雅解决方案:

String nameList = names.stream().collect(naturalCollector(", ", " and "));

Unfortunatley,它取决于这个功能,可以隐藏在某个实用程序类中:

public static Collector<Object, Ack, String> naturalCollector(String sep, String lastSep) {
    return new Collector<Object, Ack, String>() {

        @Override public BiConsumer<Ack, Object> accumulator() {
            return (Ack a, Object o) -> a.add(o,  sep);
        }

        @Override public Set<java.util.stream.Collector.Characteristics> characteristics() {
            return Collections.emptySet();
        }

        @Override public BinaryOperator<Ack> combiner() {
            return (Ack one, Ack other) -> one.merge(other, sep);
        }

        @Override public Function<Ack, String> finisher() {
            return (Ack a) -> a.toString(lastSep);
        }

        @Override public Supplier<Ack> supplier() {
            return Ack::new;
        }

    };
}

...以及此类,它是上述函数中的内部stateholder,但是Collector API想要公开它:

class Ack {
    private StringBuilder result = null;
    private Object last;

    public void add(Object u, String sep) {
        if (last != null) {
            doAppend(sep, last);
        }
        last = u;
    }

    private void doAppend(String sep, Object t) {
        if (result == null) {
            result = new StringBuilder();
        } else {
            result.append(sep);
        }
        result.append(t);
    }

    public Ack merge(Ack other, String sep) {
        if (other.last != null) {
            doAppend(sep, last);
            if (other.result != null) {
                doAppend(sep, other.result);
            }
            last = other.last;
        }
        return this;
    }

    public String toString(String lastSep) {
        if (result == null) {
            return last == null ? "" : String.valueOf(last);
        }
        result.append(lastSep).append(last);
        return result.toString();
    }
}

答案 4 :(得分:0)

如果逗号永远不在值中,那么它就是单行:

String all = names.toString().replaceAll("^.|.$", "").replaceAll(",(?!.*,)", " and");

答案 5 :(得分:-2)

您可以编写自定义函数来添加最后一个分隔符,但是对于它们之间的分隔符,您可以使用StringUtils.join()来完成任务。检查此api

的链接