Java Lambda - попытка суммировать по 2 группам

У меня есть список объектов, который мне нужно сгруппировать по 2 различным атрибутам, а затем суммировать значения атрибута, структура моего объекта примерно такая:

private Long id1;
private Long id2;
private Double amountReserved;
private Double amountRequired;
//... more atributes and getters and setters

Итак, у меня есть список, например:

List<MyList> list = Arrays.asList(
list(1, A, 50, 200)
list(1, A, 50, 200)
list(1, B, 0, 100)
list(2, A, 10, 15)
list(2, A, 5, 15)
list(3, A, 0, 25));

То, что я пытаюсь достичь, - это новый список со следующей структурой:

list(1, A, 100, 100) 
list(1, B,   0, 100)
list(2, A,  15,   0)
list(3, A,   0,  25)

Выяснение того, что является необходимым, я пытаюсь достичь:

  1. Группировать объекты по id1 и id2
  2. суммировать amountReserved сгруппированного объекта
  3. вычесть amountRequired из суммированного amountReserved

Что у меня есть до сих пор:

Это дало мне группы, как я хотел

Map<Long, Map<String, List<MyList>>> map = null;
map = lista.stream().collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
                    Collectors.groupingBy(PreSeparacaoDto::getCodigoProduto)));

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

lista.stream()
        .collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
                Collectors.summingDouble(PreSeparacaoDto::getProdutoQuantidadeSeparada)))
        .forEach((codigoPedido, ProdutoQuantidadeSeparada) -> System.out.println( codigoPedido + ": " + ProdutoQuantidadeSeparada  ));

Моя проблема в том, что я не смог собрать их вместе (в соответствии с требованием 2) и даже не был близок к достижению моего требования 3.

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

Может ли кто-нибудь помочь мне и указать, где я не могу объединить сокращение с моей группой, или если это правильный путь, которому я должен следовать.

Есть ли определенность в отношении вычесть amountRequired из суммированного amountReserved, чтобы суммаRequired всегда была одинаковой для таких общих записей? Например, list(2, A, 10, 15) list(2, A, 5, 15) , я думаю, вы вычли 15 из 15, и на выходе получилось list(2, A, 15, 0)... Но что, если на входе было list(2, A, 10, 15) list(2, A, 5, 30) ?

Naman 01.03.2019 16:10

Хороший вопрос, забыл уточнить, что amountRequired является общим для всех элементов списка с одним и тем же id1 и id2.

Estevao Santiago 01.03.2019 17:10
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
2
128
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете выполнить потоковую передачу по списку ввода дважды.

В первый раз вы группируете по id1, id2 и вычисляете сумму зарезервированного количества. Во второй раз вы можете снова передать список, сгруппировать его (по id1 и id2), используя приведенный выше результат, чтобы найти разницу.

Map<Long, Map<Long, Double>> amountReservedGroup = list.stream()
        .collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
                Collectors.summingDouble(MyList::getAmountReserved))));


Map<Long, Map<Long, List<MyList>>> finalResult = list.stream()
        .collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
                Collectors.mapping(o -> new MyList(o.getId1(), o.getId2(),
                                amountReservedGroup.get(o.getId1()).get(o.getId2()),
                                o.getAmountRequired() - amountReservedGroup.get(o.getId1()).get(o.getId2())),
                        Collectors.toList()))));

Примечание: Это не относится к случаю, когда результат вычитания отрицательный!!

Как указал nullpointer@ в комментариях, будет ли значение amountRequired одинаковым для заданных id1 и id2?

Думаю, проще всего использовать Collectors.grouping : вы говорите ему, как группировать и что собирать.

Вот пример вычисления только суммы AmountReserved :

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class GroupedSums {

    static class MyList {
        Long id1;
        char id2;
        Double amountReserved;
        Double amountRequired;
        public Long getId1() {
            return id1;
        }
        public char getId2() {
            return id2;
        }
        public Double getAmountReserved() {
            return amountReserved;
        }
        public Double getAmountRequired() {
            return amountRequired;
        }
        public MyList(Long id1, char id2, Double amountReserved, Double amountRequired) {
            super();
            this.id1 = id1;
            this.id2 = id2;
            this.amountReserved = amountReserved;
            this.amountRequired = amountRequired;
        }

        Key key() {
            return new Key(id1, id2);
        }

    }

    private static MyList list(Long id1, char id2, Double amountReserved, Double amountRequired) {
        return new MyList(id1, id2, amountReserved, amountRequired);
    }

    public GroupedSums() {      
    }

    private static class Key {

        Long id1;
        char id2;
        public Long getId1() {
            return id1;
        }
        public char getId2() {
            return id2;
        }
        public Key(Long id1, char id2) {
            super();
            this.id1 = id1;
            this.id2 = id2;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id1 == null) ? 0 : id1.hashCode());
            result = prime * result + id2;
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Key other = (Key) obj;
            if (id1 == null) {
                if (other.id1 != null)
                    return false;
            } else if (!id1.equals(other.id1))
                return false;
            if (id2 != other.id2)
                return false;
            return true;
        }
        @Override
        public String toString() {
            return "[id1 = " + id1 + ", id2 = " + id2 + "]";
        }   

    }

    public static void main(String[] args) {
        List<MyList> list = Arrays.asList(
                list(1L, 'A', 50d, 200d),
                list(1L, 'A', 50d, 200d),
                list(1L, 'B', 0d, 100d),
                list(2L, 'A', 10d, 15d),
                list(2L, 'A', 5d, 15d),
                list(3L, 'A', 0d, 25d));

        list.stream().collect(Collectors.groupingBy(MyList::key, Collectors.summingDouble(MyList::getAmountReserved)))
        .forEach((k,v)->System.out.println("" + k + " :" + v));
    }

}

ХТХ!

Ответ принят как подходящий

Возможно, вы просто ищете просто Collectors.toMap как:

List<MyList> output = new ArrayList<>(lista.stream()
        .collect(Collectors.toMap(a -> a.getId1() + "-" + a.getId2(), a -> a, (myList1, myList2) -> {
            myList1.amountReserved = myList1.amountReserved + myList2.amountReserved;
            myList1.amountRequired = myList1.amountRequired - myList1.amountReserved;
            return myList1;
        })).values());

Примечание. Вы также можете обновить mergeFunction на основе ответа на этот комментарий. Это больше похоже на подход к выбору лучшего ключа на карте, а затем, возможно, к поиску списка значений.

Naman 01.03.2019 16:16

Я бы не стал объединять два целочисленных типа, так как результирующая строка может столкнуться с другой конкатенацией двух целых чисел, то есть 2 + 35 равно 23 + 5.

MC Emperor 01.03.2019 16:26

@MCEmperor Ну, идея заключалась в том, чтобы фактически создать из этого функцию. (как для первого, так и для второго идентификатора). Хотя согласен с тем фактом, что реализация, которой я поделился здесь, ошибочна в случаях, подобных тем, которыми вы поделились.

Naman 01.03.2019 16:29

Итак, вы объединяете id1 и id2, поэтому нет необходимости создавать «группу группы», и, используя «-», вы избегаете случая, упомянутого Императором. Я просто потерялся на a -> a, (myList1, myList2) ->. Я действительно не понимаю, что происходит с этой функцией стрелки, не могли бы вы указать мне некоторую информацию, чтобы я мог понять, что там происходит? Признаюсь, мне трудно понять, что именно мне нужно для поиска в Google...

Estevao Santiago 01.03.2019 20:35

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

Estevao Santiago 05.03.2019 22:35

вы можете сделать заказ по id1, а затем заказать id2 (чтобы убедиться, что элементы одного и того же списка и подсписка находятся друг за другом), а затем вы делаете вложенный foreach (перед тем, как вы итерируете подсписок, вы устанавливаете result_reserved_amount в 0 и result_required_amount в начальное значение ) то вы делаете это, если одинаковые идентификаторы (если id1 = предыдущее_id1 и id2 = предыдущее_id2) делают result_reserved_amount+= current_reserved_amount и result_required_amount -= current_reserved_amount, в противном случае обновите предыдущее_id1, предыдущее_id2, result_reserved_amount, result_required_amount

Спасибо за попытку помочь. Просто вопрос требует решения с использованием java lambda и stream. Хотя ваше предложение, вероятно, сработает, оно не соответствует требованиям приемлемого ответа. Тем не менее, спасибо за то, что написали возможное решение.

Estevao Santiago 05.03.2019 22:38

Другие вопросы по теме