Карта <K, V> возвращается к карте <V, карта <K, V >> после значения groupingBy вместо карты <Obj, List <Entry <K, V >>> - PullRequest
2 голосов
/ 24 мая 2019

Я изо всех сил пытаюсь поддерживать структуры данных, которые мне нужны для потоковых операций в Java, скорее всего из-за отсутствия надлежащего понимания и практики.

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3);

            //Group by
            Map <Integer, Long> countGrouped = list.stream().collect(
                    Collectors.groupingBy(
                            x -> x, Collectors.counting()));
            System.out.println("group by value, count " + countGrouped);

            //Sort desc
            Map <Integer, Long> descendingSorted = new LinkedHashMap<>();
            countGrouped.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .forEachOrdered(x -> descendingSorted.put(x.getKey(), x.getValue()));
            System.out.println("sorted " + descendingSorted);

            //filter
            Map <Integer, Long> filtered = new LinkedHashMap<>();
            descendingSorted.entrySet().stream()
                .filter(x -> x.getValue() >= 2)
                .forEach(x -> filtered.put(x.getKey(), x.getValue()));;
            System.out.println("filtered " + filtered);

            //Split groups
            Map<Object, List<Entry<Integer, Long>>> groups = filtered.entrySet().stream()
                    .collect(Collectors.groupingBy(x -> x.getValue()));
            System.out.println("grouped " + groups);
    }
}

В результате

group by value, count {1=3, 2=1, 3=4}
sorted {3=4, 1=3, 2=1}
filtered {3=4, 1=3}
grouped {3=[1=3], 4=[3=4]}

это правильно, но я вхожу в постепенно усложняющиеся структуры данных без особого смысла, как вы можете видеть, заканчивая (wtf?) Map<Object, List<Entry<Integer, Long>>>, как вы можете видеть. Хотя это может быть просто Map<Int, Map<Int, Int>>.

Таким образом, конкретный вопрос заключается в том, как преобразовать и содержать выходные данные структуры данных, полученные в результате потоковых операций?

Я видел, как Collector'ы предоставляют операции преобразования toMap (...), и я полагаю, что это правильный путь, но я не могу (думаю, из-за недостатка знаний) заставить его работать.

В этом случае, как мне кажется, мне очень поможет дидактическое объяснение, ссылка на исчерпывающие ресурсы для лучшего понимания потоков и функционального программирования или тому подобное, больше, чем фактическое решение для конкретного случая (что было бы будьте хороши для упражнения, но вы поняли)

Ответы [ 2 ]

2 голосов
/ 24 мая 2019

Немного удивительно, что у вас возникают трудности, так как вы уже продемонстрировали знание всех необходимых вещей.Вы знаете, что groupingBy может занять другое Collector, вы назвали правильное, уже toMap, и вы уже использовали функции для извлечения значений Map.Entry.

Объединение этих вещей даетyou

Map<Long, Map<Integer, Long>> groups = filtered.entrySet().stream()
    .collect(Collectors.groupingBy(x -> x.getValue(),
        Collectors.toMap(x -> x.getKey(), x -> x.getValue())));
System.out.println("grouped " + groups);

Чтобы лучше продемонстрировать операцию, я изменил ввод на

List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 4);

, что приводит к

grouped {3=[1=3, 4=3], 4=[3=4]}

, хотя нет смысла повторятьзначения, которые всегда совпадают с ключами внешних карт.Таким образом, альтернативой будет

Map<Long, List<Integer>> groups = filtered.entrySet().stream()
    .collect(Collectors.groupingBy(Map.Entry::getValue,
        Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
System.out.println("grouped " + groups);

, что приводит к

grouped {3=[1, 4], 4=[3]}

Обратите внимание, что вы не должны использовать forEach / forEachOrdered до put на карте.Ваши промежуточные шаги скорее должны быть

//Sort desc
Map<Integer, Long> descendingSorted = countGrouped.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
        (a,b) -> { throw new AssertionError(); }, LinkedHashMap::new));
System.out.println("sorted " + descendingSorted);

//filter
Map<Integer, Long> filtered = descendingSorted.entrySet().stream()
    .filter(x -> x.getValue() >= 2)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
        (a,b) -> { throw new AssertionError(); }, LinkedHashMap::new));
System.out.println("filtered " + filtered);

Сборщик toMap, принимающий фабрику карт, вынуждает нас предоставлять функцию слияния, но так как наш ввод уже является картой, которая должна иметь различные ключи, я предоставил всегдаВызов функции здесь, так как что-то было бы серьезно неправильно, если бы обнаружились дубликаты.

Но обратите внимание, что принуждение всех этих операций собирать в новые карты является излишне сложным и неэффективным.Также нет смысла сначала сортировать данные целиком, а затем уменьшать объем данных с помощью filter.Сначала фильтрация потенциально уменьшит работу этапа сортировки, тогда как результат операции фильтра не должен зависеть от порядка.

Гораздо лучше выполнить всю операцию в одном конвейере

List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 4);

Map<Integer, Long> countGrouped = list.stream().collect(
    Collectors.groupingBy(x -> x, Collectors.counting()));
System.out.println("group by value, count " + countGrouped);

Map<Long, List<Integer>> groups = countGrouped.entrySet().stream()
    .filter(x -> x.getValue() >= 2)
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .collect(Collectors.groupingBy(Map.Entry::getValue, LinkedHashMap::new, 
        Collectors.mapping(Map.Entry::getKey, Collectors.toList())));

System.out.println("grouped " + groups);

Обратите внимание, что в отличие от предыдущего кода, теперь последняя операция группировки также будет сохранять порядок, что приводит к

grouped {4=[3], 3=[1, 4]}

, т. Е. Группы сортируются по убыванию количества.

Посколькуcount является ключом полученной карты, мы также можем использовать внутренне отсортированную карту в качестве типа результата и пропустить этап сортировки:

Map<Long, List<Integer>> groups = countGrouped.entrySet().stream()
    .filter(x -> x.getValue() >= 2)
    .collect(Collectors.groupingBy(Map.Entry::getValue,
        () -> new TreeMap<>(Comparator.<Long>reverseOrder()),
        Collectors.mapping(Map.Entry::getKey, Collectors.toList())));

Основное различие заключается в поведении карты результатов после потоковая операция, например, если вы добавите в нее больше элементов, так как TreeMap вставит новые ключи в порядке убывания, тогда как LinkedHashMap добавит их в конец, поддерживая порядок вставки.

1 голос
/ 24 мая 2019

Подпись groupingBy равна public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier), но если я правильно понимаю, вы хотите сопоставить значение записи с картой, например:

Map<Object, Map.Entry<Integer, Long>> groups = filtered.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getValue, x -> x));
System.out.println("grouped " + groups);

Вывод

grouped {3=1=3, 4=3=4}
...