4
votes

Java Lambda - Tentative de somme par 2 groupes

J'ai une liste d'objets, que je dois regrouper par 2 atributs différents, puis additionner les valeurs d'un attribut, la structure de mon objet est quelque chose comme ceci:

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

Donc, J'ai alors une liste, par exemple:

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

Ce que j'essaie de réaliser est une nouvelle liste avec la structure ci-dessous:

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


2 commentaires

Existe-t-il une certitude concernant soustraire amountRequired de la somme de amountReserved , de sorte que amountRequired serait toujours le même pour ces entrées courantes? Par exemple list (2, A, 10, 15) list (2, A, 5, 15) , je pense que vous avez déduit 15 de 15 et le résultat est list (2, A, 15, 0) ... Mais que faire si l'entrée était list (2, A, 10, 15) list (2, A, 5, 30) ?


Bon point, j'ai oublié de préciser que le amountRequired est partagé entre toutes les listes d'itens des mêmes id1 et id2.


4 Réponses :


1
votes

Vous pouvez diffuser deux fois sur la liste d'entrée.

Pour la première fois, vous groupez par id1, id2 et calculez la somme du montant réservé. La deuxième fois, vous pouvez à nouveau diffuser la liste, la regrouper (par id1 et id2) en utilisant le résultat ci-dessus pour trouver la différence.

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

Remarque: Cela ne gère pas le cas où le résultat de la soustraction est négatif !!

Comme indiqué par nullpointer @ dans les commentaires, la valeur de amountRequired sera-t-elle la même pour un étant donné id1 et id2?


0 commentaires

2
votes

Je pense qu'un moyen simple est d'utiliser Collectors.grouping : vous lui indiquez comment grouper et quoi collecter.

Voici un exemple, ne calculant que la somme de 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));
    }

}

HTH!


0 commentaires

1
votes

Vous cherchez peut-être simplement Collectors.toMap comme:

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


5 commentaires

Remarque: vous pouvez mettre à jour la mergeFunction en fonction de la réponse à ce commentaire également. Il s'agit davantage de l'approche consistant à choisir une meilleure clé dans la carte, puis éventuellement à trouver la liste de valeurs.


Je ne concaténerais pas deux types d'entiers, car la chaîne résultante pourrait entrer en collision avec une autre concaténation de deux entiers, c'est-à-dire 2 + 35 est égal à 23 + 5 .


@MCEmperor Eh bien, l'idée en tête était d'en créer une fonction. (pour le premier et le deuxième id). Bien que d'accord avec le fait que la mise en œuvre que j'ai partagée ici est imparfaite dans des cas comme vous l'avez partagé.


OK, donc vous concaténez id1 et id2, il n'est donc pas nécessaire de créer un 'groupe d'un groupe', et en utilisant "-" vous évitez le cas mentionné par Emperor. Je viens de me perdre sur a -> a, (myList1, myList2) -> . Je ne comprends pas vraiment ce qui se passe avec cette fonction fléchée, pouvez-vous m'indiquer des informations pour que je puisse comprendre ce qui se passe là-dedans? J'avoue que j'ai du mal à comprendre exactement ce dont j'ai besoin sur Google ...


Juste une note, le amountRequired n'est pas calculé correctement dans la fonction. Pour une raison quelconque, quand il y a des regroupements à faire, le calcul ne fonctionne pas, j'ai donc exécuté cette partie du code en dehors du flux, en parcourant à nouveau la liste en utilisant un simple foreach. Comme pour le reste, jusqu'à présent, votre code a résolu mon problème.



0
votes

vous pouvez commander par id1, puis commander id2 (pour vous assurer que les éléments de la même liste et de la sous-liste sont les uns après les autres), puis vous faire imbriquer foreach (avant d'itérer la sous-liste, vous initialisez result_reserved_amount à 0 et result_required_amount à la valeur initiale) alors vous faites si les mêmes identifiants (si id1 = previous_id1 et id2 = previous_id2) font result_reserved_amount + = current_reserved_amount et result_required_amount - = current_reserved_amount, sinon mettez à jour previous_id1, previous_id2, result_reserved_amount, result_required_amount


1 commentaires

Merci d'essayer d'aider. C'est juste que la question nécessite une solution utilisant java lambda et stream. Bien que votre suggestion soit susceptible de fonctionner, elle ne répond pas aux exigences d'une réponse acceptable. Néanmoins, merci pour le temps que vous avez consacré à une solution possible.