Как отсортировать список TreeSets с потоками java8

Мой список содержит такие наборы, как [1,3,5][2,6,4] и т. д., все одного размера. Я пытался сделать это, но, похоже, это не работает.

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

    }

Конечный результат, который я хочу, это [1,2,3][4,5,6].

Я мог бы попытаться добавить все элементы в ArrayList и отсортировать их, а затем создать новый List из TreeSet. Но есть ли какой-то один лайнер?

ОБНОВЛЕНИЕ:

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

Это работает, но можно ли это упростить?


person LexByte    schedule 02.06.2017    source источник
comment
На всякий случай — вы хотите переставить элементы так, чтобы в каждом наборе было по три элемента, а сами наборы располагались в порядке возрастания?   -  person Mureinik    schedule 03.06.2017
comment
уверен, что есть один лайнер, если вы начнете достаточно далеко влево и установите поле достаточно далеко вправо. И когда она будет написана, никто никогда не сможет ее прочитать.   -  person Mike Nakis    schedule 03.06.2017
comment
Да, это конечный результат, которого я хочу @Mureinik   -  person LexByte    schedule 03.06.2017
comment
@MikeNakis В этом случае необходим 50-дюймовый монитор.   -  person LexByte    schedule 03.06.2017
comment
@LexByte Это не точный дубликат, но stackoverflow.com/q/43057690/2422776 должен направить вас в нужное русло направление   -  person Mureinik    schedule 03.06.2017
comment
@LexByte, ты можешь использовать гуаву?   -  person Eugene    schedule 03.06.2017
comment
Этот код не работает, потому что вы сравниваете наборы с наборами. Однако очевидно, что наборы в основном не имеют значения - похоже, вам нужно сгладить, а затем отсортировать это.   -  person Oliver Charlesworth    schedule 03.06.2017
comment
@OliverCharlesworth да, я сравнивал наборы с наборами. Есть ли способ преобразовать все эти наборы в массив и собрать их? Я обновил сообщение с решением, которое вроде работает.   -  person LexByte    schedule 03.06.2017
comment
@LexByte вам нужно flatMap, а затем отсортировать, а затем собрать. block.stream().flatMap(Set::stream).sorted().collect(Collectors.toList()). Обратите внимание, что (n,m)->n.compareTo(m) вообще не нужен.   -  person Eugene    schedule 03.06.2017


Ответы (3)


Ответ Юджина сладкий, потому что гуава сладкая. Но если у вас нет Guava в вашем пути к классам, вот еще один способ:

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

Сначала я объединяю все наборы в один поток, затем сортирую все элементы и, наконец, собираю весь отсортированный поток в список наборов. Для этого я вызываю вспомогательный метод, который использует пользовательский сборщик:

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

Метод получает размер каждого раздела и использует локальный класс Acc в качестве изменяемой структуры, которую будет использовать сборщик. Внутри класса Acc я использую List, который будет содержать LinkedHashSet экземпляров, в которых будут храниться элементы потока.

Класс Acc ведет подсчет всех уже собранных элементов. В методе add я вычисляю индекс списка и увеличиваю этот счетчик, а если набора в этой позиции списка не было, я добавляю к нему новый пустой LinkedHashSet. Затем я добавляю элемент в набор.

Поскольку я вызываю sorted() в потоке для сортировки его элементов перед сбором, мне нужно использовать структуры данных, которые сохраняют порядок вставки. Вот почему я использую ArrayList для внешнего списка и LinkedHashSet для внутренних наборов.

Метод merge должен использоваться параллельными потоками для объединения двух ранее накопленных экземпляров Acc. Я просто добавляю все элементы полученного экземпляра Acc в этот экземпляр Acc путем делегирования методу add.

Наконец, я использую Collector.of для создать сборщик на основе методов класса Acc. Последний аргумент — функция завершения, которая просто возвращает список экземпляров Acc.

person fps    schedule 03.06.2017
comment
И хорошо продумано, и хорошо объяснено. - person Ole V.V.; 03.06.2017
comment
Нравится одноцелевой класс, определенный в методе. Незначительная загвоздка: он не компилируется, потому что count++/size является long. Либо count должно быть int, либо, если оставить как long, то index должно быть вычислено с Math.toIntExact(count++ / size) (хотя я не уверен, что было бы полезно поддерживать такие большие списки, которые нам нужно сортировать) . - person Hugues M.; 04.06.2017
comment
Привет, @Hugues Спасибо, что указали на это. int count должен сделать это для этого. - person fps; 04.06.2017
comment
Спасибо за быстрое редактирование @Federico. Мне трудно реализовать Collector для пользовательских нужд, и это действительно хороший и вдохновляющий пример. - person Hugues M.; 04.06.2017
comment
@ Юджин, я думаю, он хочет сохранить отсортированный порядок как порядок итерации. - person Didier L; 04.06.2017
comment
@FedericoPeraltaSchaffner Я добавил еще один ответ, чтобы показать, что объединитель можно значительно упростить... - person Eugene; 04.06.2017

Если у вас есть guava в пути к классам, это очень просто:

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

    Iterable<List<Integer>> result = Iterables.partition(sorted, 3);
person Eugene    schedule 02.06.2017

Добавление еще одного ответа, так как это будет больше, чем комментарий. Это действительно то, что сделал принятый ответ, но с «более умным» объединителем, которому не нужно снова и снова выполнять потоковую передачу.

 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);
}
person Eugene    schedule 04.06.2017