0

How can i reduce or collect a list of string delimitted by comma and prefixed "and" only to the last element using Java 8 Streams?

eg.

List<String> ls = Arrays.asList("tom","terry","john","kevin","steve");

String result = ls.stream().map(String::toString)
                .collect(Collectors.joining(", "));

System.out.println(result);

This statement prints => tom, terry, john, kevin, steve. But i wanted to print the list as tom, terry, john, kevin and steve.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Srivatsan
  • 81
  • 1
  • 9
  • You need to make a new line and use 4 spaces as indent to highlight your code. It will make it much more easier to read! – sascha10000 Aug 09 '16 at 23:07

3 Answers3

2

Two solutions.

Clumsy, taking sublist actually:

    String result = ls.stream()
            .limit(ls.size() - 1)
            .collect(Collectors.joining(", ")) + " and " + ls.get(ls.size() - 1);

Using a class to shift one place:

    class SbS {
        StringBuilder sb = new StringBuilder();
        String s = "";

        @Override
        public String toString() {
            return sb + (s.empty() ? "" : " and " + s);
        }
    }
    result = ls.stream()
            .collect(SbS::new,
                (a, s) -> {
                    a.sb.append(a.sb.length() == 0 ? "" : ", ").append(a.s); a.s = s;
                },
                (a1, a2) -> {}).toString();

Mind: the class definition should be placed inside the method, just as above. Otherwise static would be needed. The lambda for the (a, s) could be a method in SbS. The support for parallelism is left as excercise to the reader (a1, a1).

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
1

Streams do not make this easier. You might just as well use String::replaceAll:

String input = "tom, terry, john, kevin, steve";
String result = input.replaceAll(", (\\w+)$", " and $1");

System.out.println(result);

Output:

tom, terry, john, kevin and steve
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • I don't think that `map()` is needed - it calls `toString`...on each string. Speaking of, I don't think even the stream is needed given that in this case it's just a more verbose way of doing [String.join(", ", ls)](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#join-java.lang.CharSequence-java.lang.Iterable-) – VLAZ Aug 10 '16 at 00:31
  • @Vld You are right. But I suspect OP has a case where the inputs are not strings, but some other object, and this was the closest example to the actual situation. I mostly just ignored how the input for my line was created. – Jorn Vernee Aug 10 '16 at 10:31
  • I would go one step further: `ls.toString().replaceAll("^.|.$", "").replaceAll(", (\\w+)$", " and $1")` and bypass to whole stream train wreck – Bohemian Aug 11 '16 at 05:05
0

Stream API does not provide a standard way to achieve this. My StreamEx library which extends standard Stream API has a mapLast method which is helpful here. It allows to map only last stream element leaving everything else as is:

System.out.println(StreamEx.of(ls).mapLast("and "::concat).joining(", "));

The result is the following:

tom, terry, john, kevin, and steve

Note that it adds a comma before "and" which is ok (see Oxford Comma). If you don't like it (or actually using another language which forbids such punctuation), then it can be achieved in slightly longer manner:

StreamEx.of(ls)
        .mapLastOrElse(", "::concat, " and "::concat)
        .joining().substring(", ".length());
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • The problem with the Oxford Comma is that it shouldn’t appear when there are exactly two elements… – Holger Aug 22 '16 at 10:20
  • @Holger, that's definitely a problem. Adding a special collector to my library which solves all these cases would be too specific as other languages may have different punctuation rules. Probably project like ICU4J could offer such collector if it will be Java 8 oriented... – Tagir Valeev Aug 22 '16 at 16:05
  • 1
    Well, formatting data structures to a particular human language is not a task for which there has to be an out of the box solution (nor does it have to be Stream based). In case of UI, the state of the art still is having a resource bundle with pre-built template strings… – Holger Aug 22 '16 at 16:14