Объедините несколько функций `Collectors :: groupBy` с потоками Java - PullRequest
0 голосов
/ 28 мая 2018

У меня проблема с правильным объединением нескольких Collectors::groupingBy функций и последующим применением их всех сразу к заданному входу.

Допустим, у меня есть некоторый класс, реализующий следующий интерфейс:

interface Something {
    String get1();
    String get2();
    String get3();
    String get4();
}

И теперь я могу иметь некоторый список комбинаций методов из этого интерфейса, то есть эти списки могут быть: [Something::get1, Something::get3], [Something::get2, Something::get1, Something::get3].

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

Я имею в виду, что, например, для списка [Something::get1, Something::get3] и списка [Something1, Something2, ...] Я хочу получить список чего-то, сгруппированных сначала по get1, а затем get2.

Этого можно достичь следующим образом:

var lst = List.of(smth1, smth2, smth3);
lst.stream()
   .collect(Collectors.groupingBy(Something::get1, Collectors.groupingBy(Something::get3)))

Что если у меня есть произвольный список методов, которые я хотел бы применить к группировке?

Я думал о чем-то подобном (разумеется, это не работает, но вы поймете):

Предположим, что List<Function<Something, String>> groupingFunctions - это наш список методов, которые мы хотим применить к группировке.

var collector = groupingFunctions.stream()
                                 .reduce((f1, f2) -> Collectors.groupingBy(f1, Collectors.groupingBy(f2)))

, а затем

List.of(smth1, smth2, smth3).stream().collect(collector)

Но этот подход не работает.Как добиться результата, о котором я думаю?

Ответы [ 2 ]

0 голосов
/ 28 мая 2018

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

var collector = groupingFunctions.stream()
    .<Collector<Something,?,?>>reduce(
        Collectors.toList(),
        (c,f) -> Collectors.groupingBy(f, c),
        (c1,c2) -> { throw new UnsupportedOperationException("can't handle that"); });

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

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

Collector<Something,?,?> collector = Collectors.toList();
for(var i = groupingFunctions.listIterator(groupingFunctions.size()); i.hasPrevious(); )
    collector = Collectors.groupingBy(i.previous(), collector);

Вы можете использовать коллектор как

Object o = lst.stream().collect(collector);

, но для обработки Map s… * необходимо указать instanceof и набрать приведение типа

Было бы удобнее создать одну, не вложенную Map с List клавишами, которые отражают функции группировки:

Map<List<String>,List<Something>> map = lst.stream().collect(Collectors.groupingBy(
    o -> groupingFunctions.stream().map(f -> f.apply(o))
                          .collect(Collectors.toUnmodifiableList())));

Это позволило бы запрашивать записи типа map.get(List.of(arguments, matching, grouping, functions))

0 голосов
/ 28 мая 2018

Вы можете сделать это:

public static Collector createCollector(Function<A, String>... groupKeys) {
    Collector collector = Collectors.toList();
    for (int i = groupKeys.length - 1; i >= 0; i--) {
        collector = Collectors.groupingBy(groupKeys[i], collector);
    }
    return collector;
}

Это даст вам необработанный коллектор, следовательно, ваш результат потока после группировки также будет сырым.

Collector collector = createCollector(Something::get1, Something::get2);

Вы можете использовать это collectorнапример:

Object result = somethingList.stream().collect(collector);

Поскольку вы знаете, сколько groupingBy вы передали коллектору, вы можете привести его к соответствующему результату Map.В этом случае применяется два groupingBy:

Map<String, Map<String, List<Something>>> mapResult = (Map<String, Map<String, List<Something>>>) result
...