7

My list contains sets like [1,3,5][2,6,4] etc, all of the same size. I tried doing this but it doesn't seem to work.

List<TreeSet<T>> block;
    for(TreeSet<T> t : block){
        block.stream().sorted((n,m)->n.compareTo(m)).collect(Collectors.toSet());

    }

The end result I want is [1,2,3][4,5,6].

I could try to add all the elements in an ArrayList and sort that out then make a new List of TreeSet's. But is there is some kind of one liner?

UPDATE:

List<T> list=new ArrayList<T>();
    for(TreeSet<T> t : block){

        for(T t1 : t)
        {
            list.add(t1);   

        }
    }

    list=list.stream().sorted((n,m)->n.compareTo(m)).collect(Collectors.toList());

This works but could this be simplified?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
LexByte
  • 382
  • 4
  • 15
  • Just to be sure - you want to rearange the elements so each set has three elements, and the sets themselves are in ascending order? – Mureinik Jun 02 '17 at 21:16
  • sure there is a one liner, if you start far enough to the left, and set your margin far enough to the right. And once written, nobody would ever be able to read it. – Mike Nakis Jun 02 '17 at 21:18
  • Yeah that is the end result i want @Mureinik – LexByte Jun 02 '17 at 21:20
  • @MikeNakis An 50 inch monitor would be must in that case – LexByte Jun 02 '17 at 21:21
  • @LexByte This isn't an exact duplicate, but https://stackoverflow.com/q/43057690/2422776 should steer you in the right direction – Mureinik Jun 02 '17 at 21:23
  • @LexByte can you use guava? – Eugene Jun 02 '17 at 21:32
  • 4
    That code doesn't work because you're comparing sets with sets. However, it's evident that the sets are basically irrelevant - it looks like you need to flatten, and then sort *that*. – Oliver Charlesworth Jun 02 '17 at 21:36
  • @OliverCharlesworth yeah i was compering sets with sets.Is there any way to transform all those sets in an arraylist and collect it? I updated the post with a solution that kinda works. – LexByte Jun 02 '17 at 22:00
  • 6
    @LexByte you need to `flatMap` and then sort and then collect. `block.stream().flatMap(Set::stream).sorted().collect(Collectors.toList())`. Notice that `(n,m)->n.compareTo(m)` is not needed at all. – Eugene Jun 02 '17 at 22:34

3 Answers3

10

@Eugene's answer is sweet, because Guava is sweet. But if you happen to not have Guava in your classpath, here's another way:

List<Set<Integer>> list = block.stream()
    .flatMap(Set::stream)
    .sorted()
    .collect(partitioning(3));

First, I'm flatmapping all the sets into one stream, then I'm sorting all the elements and finally, I'm collecting the whole sorted stream to a list of sets. For this, I'm invoking a helper method that uses a custom collector:

private static <T> Collector<T, ?, List<Set<T>>> partitioning(int size) {
    class Acc {
        int count = 0;
        List<Set<T>> list = new ArrayList<>();

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

        Acc merge(Acc another) {
            another.list.stream().flatMap(Set::stream).forEach(this::add);
            return this;
        }
    }
    return Collector.of(Acc::new, Acc::add, Acc::merge, acc -> acc.list);
}

The method receives the size of each partition and uses the Acc local class as the mutable structure to be used by the collector. Inside the Acc class, I'm using a List that will contain LinkedHashSet instances, which will hold the elements of the stream.

The Acc class keeps the count of all the elements that have been already collected. In the add method, I calculate the index of the list and increment this count, and if there was no set in that position of the list, I append a new empty LinkedHashSet to it. Then, I add the element to the set.

As I'm calling sorted() on the stream to sort its elements before collecting, I need to use data structures that preserve insertion order. This is why I'm using ArrayList for the outer list and LinkedHashSet for the inner sets.

The merge method is to be used by parallel streams, to merge two previously accumulated Acc instances. I'm just adding all the elements of the received Acc instance to this Acc instance, by delegating to the add method.

Finally, I'm using Collector.of to create a collector based on the methods of the Acc class. The last argument is a finisher function, which just returns the Acc instance's list.

fps
  • 33,623
  • 8
  • 55
  • 110
  • 5
    Both well thought out and well explained. – Ole V.V. Jun 03 '17 at 05:20
  • 1
    Love the *single-purpose class defined within method*. Minor nitpick: it does not compile because `count++/size` is a `long`. Either `count` should be an `int`, or if kept as `long`, then `index` should be computed with `Math.toIntExact(count++ / size)` (although I'm not sure it would be that useful to support such big lists that we need to *sort*). – Hugues M. Jun 04 '17 at 13:06
  • Hi, @Hugues Thanks for pointing that out. `int count` should do it for this. – fps Jun 04 '17 at 13:13
  • 1
    Thanks for the quick edit @Federico. I find it difficult to implement `Collector` for custom needs, and this is a really good and inspiring example. – Hugues M. Jun 04 '17 at 13:28
  • 1
    @Eugene I guess he wants to keep the sorted order as iteration order – Didier L Jun 04 '17 at 14:42
  • 2
    @FedericoPeraltaSchaffner I've added another answer, to show that the combiner could be greatly simplified... – Eugene Jun 04 '17 at 18:25
3

If you have guava on the classpath this is a breeze:

        block
            .stream()
            .flatMap(Set::stream)
            .collect(Collectors.toCollection(TreeSet::new));

    Iterable<List<Integer>> result = Iterables.partition(sorted, 3);
Eugene
  • 117,005
  • 15
  • 201
  • 306
3

Adding another answer since this would be bigger than a comment. It's really what the accepted answer has done, but with a "smarter" combiner that does not have to stream all the time again.

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

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

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

        Acc merge(Acc right) {

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

            // they have both the same 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 List<Set<T>> finisher() {
            return list.stream().map(LinkedHashSet::new).collect(Collectors.toList());
        }

    }
    return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}
Eugene
  • 117,005
  • 15
  • 201
  • 306