4

First time question asker here so please go easy on me. :) Anyway, I am not sure if this is possible using a Java 8 stream but I am very interested in learning.

Let's say I have the following ordered list of numbers:

List<Integer> myList = Arrays.asList(1, 2, 3, 7, 9, 12, 13, 15);

Now, I want to split this list into multiple lists when the difference between elements is greater than 2. Therefore, the end result would be three different lists:

{1, 2, 3}
{7, 9}
{12, 13, 15}

I could easily do this exercise using a for loop and comparing the current element to the previous while looping. However, I am wondering if there is a concise way to accomplish this using a Java 8 stream? Like I said before, this is only for my own learning and understanding of Java 8 so if it isn't possible then that's okay.

Thanks in advance for any comments or answers.

A. Brown
  • 43
  • 3
  • 1
    At first sight a job for `Collectors.groupingBy`, but that wouldn't work because you can't classify the numbers without knowing every other one (or at least the previous one if the list is sorted). You need a context-aware classifier, which I guess is best implemented by the kind of plain old for-loop you mention. – Aaron Aug 29 '17 at 14:22
  • 1
    Possible duplicate of [Group sequences of values](https://stackoverflow.com/questions/35234128/group-sequences-of-values) – Flown Aug 29 '17 at 14:50
  • Thanks @Aaron! I had the same thought process as you. I couldn't quite make it work with groupingBy. – A. Brown Aug 30 '17 at 15:35
  • Also, thanks to @Flown for pointing me at the thread with great discussion on a similar topic. Some great stuff to think about. – A. Brown Aug 30 '17 at 15:36

2 Answers2

5

Well I can only think of a custom collector, since you need some previous state, but this is by far not concise (unless you hide it behind a method):

 private static <T> Collector<Integer, ?, List<List<Integer>>> diffCollector() {

    class Acc {

        private Integer previous;

        private List<List<Integer>> result = new ArrayList<>();

        void accumulate(Integer elem) {
            if (previous == null) {
                previous = elem;
                List<Integer> list = new ArrayList<>();
                list.add(previous);
                result.add(list);
                return;
            }

            if (elem - previous > 2) {
                List<Integer> oneMore = new ArrayList<>();
                oneMore.add(elem);
                result.add(oneMore);
                previous = elem;
            } else {
                result.get(result.size() - 1).add(elem);
                previous = elem;
            }
        }

        Acc combine(Acc other) {

            throw new UnsupportedOperationException("Not for parallel");
        }

        List<List<Integer>> finisher() {
            return result;
        }

    }
    return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::finisher);
}

And usage would be:

 List<Integer> myList = Arrays.asList(1, 2, 3, 7, 9, 12, 13, 15);
 System.out.println(myList.stream().collect(diffCollector()));
Eugene
  • 117,005
  • 15
  • 201
  • 306
0

Improvising @Eugene answer without creating/implementing collectors and reducing the lines

    List<Integer> e = Arrays.asList(1,2,3,7,9,12,14,15);
    List<Integer> grpMarker = new ArrayList<>();
    grpMarker.add(0);
    grpMarker.add(0);

    Collection<List<Integer>>  o = e.stream().collect(Collectors.groupingBy(s-> {
        if (s - grpMarker.get(0) > 2) { grpMarker.add(1,grpMarker.get(1)+1); }
        grpMarker.remove(0);
        grpMarker.add(0,s);
        return grpMarker.get(1);
    })).values();
    System.out.println(o);

grpMarker is enabler to track the last element (index 0) and arbitrary group value (index 1).

Baski
  • 829
  • 8
  • 14