1

With the following code:

public class Main {

    public static void main(String[] args) {
        final List<Integer> items = 
             IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList());

        final String s = items
            .stream()
            .map(Object::toString)
            .collect(Collectors.joining(","))
            .toString()
            .concat(".");

        System.out.println(s);
    }
}

I get:

0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23.

What I would like to do, is to break the line every 10 items, in order to get:

0,1,2,3,4,5,6,7,8,9,

10,11,12,13,14,15,16,17,18,19,

20,21,22,23.

I have try a lot of things after googling without any success ! Can you help me ?

Thanks,

Olivier.

ols
  • 173
  • 7
  • 3
    https://stackoverflow.com/questions/44571715/is-there-a-java-8-equivalent-of-the-collate-method-in-grails/44584619#44584619 – Eugene Jun 21 '17 at 16:20
  • you can slightly adjust that answer to fit your needs – Eugene Jun 21 '17 at 16:22
  • 3
    Possible duplicate of [Split list into multiple lists with fixed number of elements in java 8](https://stackoverflow.com/questions/28210775/split-list-into-multiple-lists-with-fixed-number-of-elements-in-java-8) – Alexis C. Jun 21 '17 at 16:26
  • Thanks for your help. But do I really need 50 lines for such a basic functionality ? – ols Jun 21 '17 at 16:28
  • 1
    Writing your own Spliterator that takes the stream, groups the elements, and wraps itself in a new Stream is the usual way to go. For this specific example though, you could also write your own collector/reduction that produces a list of lists of numbers. When collecting the next number, check if there is space left in the last list, otherwise add a new empty list. Then stream the result, join the lists' elements with comma and the resulting strings with newline. The downside is that you produce a collection with all the elements, which might be a memory concern (Spliterator approach doesn't). – Malte Hartwig Jun 21 '17 at 16:34
  • 1
    By the way, the last part of Holger's answer to the question @AlexisC. mentioned produces the reduction I mentioned in a really concise way. So if you can live with creating the intermediate collection, it is a pretty cool solution. – Malte Hartwig Jun 21 '17 at 16:43
  • 1
    My two cents using StreamEx's groupRuns : `IntStreamEx.rangeClosed(0, 23).boxed().groupRuns((i1, i2) -> i1 / 10 == i2 / 10).collect(Collector.of(() -> new StringJoiner(System.lineSeparator(), "", "."), (joiner, numbers) -> joiner.add(String.join(",", numbers.stream().map(String::valueOf).toArray(String[]::new))), StringJoiner::merge, StringJoiner::toString));` – Jean-François Savard Jun 21 '17 at 18:27

3 Answers3

4

If you're open to using a third-party library, the following will work using Eclipse Collections Collectors2.chunk(int).

String s = IntStream.rangeClosed(0, 23)
        .boxed()
        .collect(Collectors2.chunk(10))
        .collectWith(MutableList::makeString, ",")
        .makeString("", ",\n", ".");

The result of Collectors2.chunk(10) will be a MutableList<MutableList<Integer>>. At this point I switch from the Streams APIs to using native Eclipse Collections APIs which are available directly on the collections. The method makeString is similar to Collectors.joining(). The method collectWith is like Stream.map() with the difference that a Function2 and an extra parameter are passed to the method. This allows a method reference to be used here instead of a lambda. The equivalent lambda would be list -> list.makeString(",").

If you use just Eclipse Collections APIs, this problem can be simplified as follows:

String s = Interval.zeroTo(23)
        .chunk(10)
        .collectWith(RichIterable::makeString, ",")
        .makeString("", ",\n", ".");

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
3

If all you want to do is process these ascending numbers, you can do it like

String s = IntStream.rangeClosed(0, 23).boxed()
    .collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new,
            Collectors.mapping(Object::toString, Collectors.joining(","))))
    .values().stream()
    .collect(Collectors.joining(",\n", "", "."));

This solution can be adapted to work on an arbitrary random access list as well, e.g.

List<Integer> items = IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList());

String s = IntStream.range(0, items.size()).boxed()
    .collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new,
            Collectors.mapping(ix -> items.get(ix).toString(), Collectors.joining(","))))
    .values().stream()
    .collect(Collectors.joining(",\n", "", "."));

However, there is no simple and elegant solution for arbitrary streams, a limitation which applies to all kind of tasks having a dependency to the element’s position.

Holger
  • 285,553
  • 42
  • 434
  • 765
2

Here is an adaptation of the already linked in the comments Collector:

    private static Collector<String, ?, String> partitioning(int size) {
    class Acc {
        int count = 0;

        List<List<String>> list = new ArrayList<>();

        void add(String elem) {
            int index = count++ / size;
            if (index == list.size()) {
                list.add(new ArrayList<>());
            }
            list.get(index).add(elem);
        }

        Acc merge(Acc right) {

            List<String> lastLeftList = list.get(list.size() - 1);
            List<String> firstRightList = right.list.get(0);
            int lastLeftSize = lastLeftList.size();
            int firstRightSize = firstRightList.size();

            // they are both size, simply addAll will work
            if (lastLeftSize + firstRightSize == 2 * size) {
                System.out.println("Perfect!");
                list.addAll(right.list);
                return this;
            }

            // last and first from each chunk are merged "perfectly"
            if (lastLeftSize + firstRightSize == size) {
                System.out.println("Almost perfect");
                int x = 0;
                while (x < firstRightSize) {
                    lastLeftList.add(firstRightList.remove(x));
                    --firstRightSize;
                }
                right.list.remove(0);
                list.addAll(right.list);
                return this;
            }

            right.list.stream().flatMap(List::stream).forEach(this::add);
            return this;
        }

        public String finisher() {
            return list.stream().map(x -> x.stream().collect(Collectors.joining(",")))
                    .collect(Collectors.collectingAndThen(Collectors.joining(",\n"), x -> x + "."));
        }

    }
    return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}

And usage would be:

String result = IntStream.rangeClosed(0, 24)
            .mapToObj(String::valueOf)
            .collect(partitioning(10));
Eugene
  • 117,005
  • 15
  • 201
  • 306