Regrouper par plusieurs noms de champs dans java 8

90

J'ai trouvé le code pour regrouper les objets par un nom de champ de POJO. Voici le code pour cela:

public class Temp {

    static class Person {

        private String name;
        private int age;
        private long salary;

        Person(String name, int age, long salary) {

            this.name = name;
            this.age = age;
            this.salary = salary;
        }

        @Override
        public String toString() {
            return String.format("Person{name='%s', age=%d, salary=%d}", name, age, salary);
        }
    }

    public static void main(String[] args) {
        Stream<Person> people = Stream.of(new Person("Paul", 24, 20000),
                new Person("Mark", 30, 30000),
                new Person("Will", 28, 28000),
                new Person("William", 28, 28000));
        Map<Integer, List<Person>> peopleByAge;
        peopleByAge = people
                .collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p, toList())));
        System.out.println(peopleByAge);
    }
}

Et la sortie est (ce qui est correct):

{24=[Person{name='Paul', age=24, salary=20000}], 28=[Person{name='Will', age=28, salary=28000}, Person{name='William', age=28, salary=28000}], 30=[Person{name='Mark', age=30, salary=30000}]}

Mais que faire si je souhaite regrouper par plusieurs champs? Je peux évidemment passer un POJO dans la groupingBy()méthode après avoir implémenté la equals()méthode dans ce POJO mais y a-t-il une autre option comme je peux grouper par plus d'un champs du POJO donné?

Par exemple, dans mon cas, je souhaite regrouper par nom et par âge.

Mital Pritmani
la source
1
Une astuce consiste simplement à générer une chaîne unique à partir de tous les champs.
Marko Topolnik
3
BTW en mappingtant que collecteur en aval est redondant dans le code que vous avez publié.
Marko Topolnik
8
La solution rapide et sale est people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Misha

Réponses:

163

Vous avez ici quelques options. Le plus simple est d'enchaîner vos collectionneurs:

Map<String, Map<Integer, List<Person>>> map = people
    .collect(Collectors.groupingBy(Person::getName,
        Collectors.groupingBy(Person::getAge));

Ensuite, pour obtenir une liste de personnes de 18 ans appelées Fred, vous utiliseriez:

map.get("Fred").get(18);

Une deuxième option consiste à définir une classe qui représente le regroupement. Cela peut être à l'intérieur de Person. Ce code utilise a recordmais il pourrait tout aussi bien être une classe (avec equalset hashCodedéfinie) dans les versions de Java avant l'ajout de JEP 359:

class Person {
    record NameAge(String name, int age) { }

    public NameAge getNameAge() {
        return new NameAge(name, age);
    }
}

Ensuite, vous pouvez utiliser:

Map<NameAge, List<Person>> map = people.collect(Collectors.groupingBy(Person::getNameAge));

et recherchez avec

map.get(new NameAge("Fred", 18));

Enfin, si vous ne souhaitez pas implémenter votre propre enregistrement de groupe, de nombreux frameworks Java ont une pairclasse conçue pour ce type de chose. Par exemple: apache commons pair Si vous utilisez l'une de ces bibliothèques, vous pouvez attribuer à la clé de la carte une paire du nom et de l'âge:

Map<Pair<String, Integer>, List<Person>> map =
    people.collect(Collectors.groupingBy(p -> Pair.of(p.getName(), p.getAge())));

et récupérez avec:

map.get(Pair.of("Fred", 18));

Personnellement, je ne vois pas vraiment beaucoup de valeur dans les tuples génériques maintenant que les enregistrements sont disponibles dans la langue car les enregistrements affichent mieux l'intention et nécessitent très peu de code.

sprinter
la source
5
Function<T,U>cache également l'intention dans ce sens --- mais vous ne verrez personne déclarer sa propre interface fonctionnelle pour chaque étape de mappage; l'intention est déjà là dans le corps lambda. Idem avec les tuples: ils sont parfaits comme types de colle entre les composants d'API. Les classes de cas de BTW Scala sont à mon humble avis une grande victoire en termes de concision et d'exposition intentionnelle.
Marko Topolnik
1
Oui, je vois votre point. Je suppose (comme toujours) que cela dépend de la façon dont ils sont utilisés. L'exemple que j'ai donné ci-dessus - en utilisant une paire comme clé d'une carte - est un bon exemple de la façon de ne pas le faire. Je ne connais pas trop Scala - je devrai commencer à l'apprendre car j'entends de bonnes choses.
sprinter
1
Imaginez pouvoir déclarer NameAgecomme un paquebot: case class NameAge { val name: String; val age: Int }--- et vous obtenez equals, hashCodeet toString!
Marko Topolnik
1
Bien - un autre élément a été placé dans ma file d'attente "à faire". C'est le FIFO malheureusement!
sprinter le
@sprinter Le type dans le premier extrait de code n'est pas correct et devrait être changé enMap<String, Map<Integer, List<Person>>> map
kasur
38

Regardez ici le code:

Vous pouvez simplement créer une fonction et la laisser faire le travail pour vous, une sorte de style fonctionnel!

Function<Person, List<Object>> compositeKey = personRecord ->
    Arrays.<Object>asList(personRecord.getName(), personRecord.getAge());

Vous pouvez maintenant l'utiliser comme carte:

Map<Object, List<Person>> map =
people.collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

À votre santé!

Deepesh Rehi
la source
2
J'ai utilisé cette solution mais différente. Function <Person, String> compositeKey = personRecord -> StringUtils.join (personRecord.getName (), personRecord.getAge ());
bpedroso
8

La groupingByméthode a le premier paramètre est Function<T,K>où:

@param <T>le type des éléments d'entrée

@param <K>le type des clés

Si nous remplaçons lambda par la classe anonyme dans votre code, nous pouvons voir une sorte de cela:

people.stream().collect(Collectors.groupingBy(new Function<Person, int>() {
            @Override
            public int apply(Person person) {
                return person.getAge();
            }
        }));

Modifiez maintenant le paramètre de sortie <K>. Dans ce cas, par exemple, j'ai utilisé une classe de paires de org.apache.commons.lang3.tuple pour le regroupement par nom et âge, mais vous pouvez créer votre propre classe pour filtrer les groupes selon vos besoins.

people.stream().collect(Collectors.groupingBy(new Function<Person, Pair<Integer, String>>() {
                @Override
                public YourFilter apply(Person person) {
                    return Pair.of(person.getAge(), person.getName());
                }
            }));

Enfin, après avoir remplacé par lambda back, le code ressemble à ça:

Map<Pair<Integer,String>, List<Person>> peopleByAgeAndName = people.collect(Collectors.groupingBy(p -> Pair.of(person.getAge(), person.getName()), Collectors.mapping((Person p) -> p, toList())));
Andrei Smirnov
la source
Qu'en est-il de l'utilisation List<String>?
Alex78191
6

Salut, vous pouvez simplement concaténer votre groupingByKeytel que

Map<String, List<Person>> peopleBySomeKey = people
                .collect(Collectors.groupingBy(p -> getGroupingByKey(p), Collectors.mapping((Person p) -> p, toList())));



//write getGroupingByKey() function
private String getGroupingByKey(Person p){
return p.getAge()+"-"+p.getName();
}
Amandeep
la source
2

Définissez une classe pour la définition de clé dans votre groupe.

class KeyObj {

    ArrayList<Object> keys;

    public KeyObj( Object... objs ) {
        keys = new ArrayList<Object>();

        for (int i = 0; i < objs.length; i++) {
            keys.add( objs[i] );
        }
    }

    // Add appropriate isEqual() ... you IDE should generate this

}

Maintenant dans votre code,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));
Sarveshseri
la source
3
C'est juste réinventer Ararys.asList()--- ce qui est BTW un bon choix pour le cas d'OP.
Marko Topolnik
Et aussi similaire à l' Pairexemple mentionné dans l'autre exemple, mais sans limite d'argument.
Benny Bottema
Vous devez également rendre cela immuable. (et calculez le hashCode) une fois)
RobAu
2

Vous pouvez utiliser List comme classificateur pour de nombreux champs, mais vous devez encapsuler les valeurs nulles dans Facultatif:

Function<String, List> classifier = (item) -> List.of(
    item.getFieldA(),
    item.getFieldB(),
    Optional.ofNullable(item.getFieldC())
);

Map<List, List<Item>> grouped = items.stream()
    .collect(Collectors.groupingBy(classifier));
vinga
la source
1

J'avais besoin de faire rapport pour une entreprise de restauration qui sert des déjeuners pour divers clients. En d'autres termes, la restauration peut avoir une ou plusieurs entreprises qui prennent les commandes de la restauration, et elle doit savoir combien de déjeuners elle doit produire chaque jour pour tous ses clients!

Juste pour remarquer, je n'ai pas utilisé le tri, afin de ne pas trop compliquer cet exemple.

Voici mon code:

@Test
public void test_2() throws Exception {
    Firm catering = DS.firm().get(1);
    LocalDateTime ldtFrom = LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0);
    LocalDateTime ldtTo = LocalDateTime.of(2017, Month.MAY, 2, 0, 0);
    Date dFrom = Date.from(ldtFrom.atZone(ZoneId.systemDefault()).toInstant());
    Date dTo = Date.from(ldtTo.atZone(ZoneId.systemDefault()).toInstant());

    List<PersonOrders> LON = DS.firm().getAllOrders(catering, dFrom, dTo, false);
    Map<Object, Long> M = LON.stream().collect(
            Collectors.groupingBy(p
                    -> Arrays.asList(p.getDatum(), p.getPerson().getIdfirm(), p.getIdProduct()),
                    Collectors.counting()));

    for (Map.Entry<Object, Long> e : M.entrySet()) {
        Object key = e.getKey();
        Long value = e.getValue();
        System.err.println(String.format("Client firm :%s, total: %d", key, value));
    }
}
dobrivoje
la source
0

C'est ainsi que j'ai fait le regroupement par plusieurs champs branchCode et prdId, il suffit de le poster pour quelqu'un dans le besoin

    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**
     *
     * @author charudatta.joshi
     */
    public class Product1 {

        public BigInteger branchCode;
        public BigInteger prdId;
        public String accountCode;
        public BigDecimal actualBalance;
        public BigDecimal sumActBal;
        public BigInteger countOfAccts;

        public Product1() {
        }

        public Product1(BigInteger branchCode, BigInteger prdId, String accountCode, BigDecimal actualBalance) {
            this.branchCode = branchCode;
            this.prdId = prdId;
            this.accountCode = accountCode;
            this.actualBalance = actualBalance;
        }

        public BigInteger getCountOfAccts() {
            return countOfAccts;
        }

        public void setCountOfAccts(BigInteger countOfAccts) {
            this.countOfAccts = countOfAccts;
        }

        public BigDecimal getSumActBal() {
            return sumActBal;
        }

        public void setSumActBal(BigDecimal sumActBal) {
            this.sumActBal = sumActBal;
        }

        public BigInteger getBranchCode() {
            return branchCode;
        }

        public void setBranchCode(BigInteger branchCode) {
            this.branchCode = branchCode;
        }

        public BigInteger getPrdId() {
            return prdId;
        }

        public void setPrdId(BigInteger prdId) {
            this.prdId = prdId;
        }

        public String getAccountCode() {
            return accountCode;
        }

        public void setAccountCode(String accountCode) {
            this.accountCode = accountCode;
        }

        public BigDecimal getActualBalance() {
            return actualBalance;
        }

        public void setActualBalance(BigDecimal actualBalance) {
            this.actualBalance = actualBalance;
        }

        @Override
        public String toString() {
            return "Product{" + "branchCode:" + branchCode + ", prdId:" + prdId + ", accountCode:" + accountCode + ", actualBalance:" + actualBalance + ", sumActBal:" + sumActBal + ", countOfAccts:" + countOfAccts + '}';
        }

        public static void main(String[] args) {
            List<Product1> al = new ArrayList<Product1>();
            System.out.println(al);
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "001", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "002", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "003", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "004", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "005", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("13"), "006", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "007", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "008", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "009", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "010", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "011", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("13"), "012", new BigDecimal("10")));
            //Map<BigInteger, Long> counting = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.counting()));
            // System.out.println(counting);

            //group by branch code
            Map<BigInteger, List<Product1>> groupByBrCd = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.toList()));
            System.out.println("\n\n\n" + groupByBrCd);

             Map<BigInteger, List<Product1>> groupByPrId = null;
              // Create a final List to show for output containing one element of each group
            List<Product> finalOutputList = new LinkedList<Product>();
            Product newPrd = null;
            // Iterate over resultant  Map Of List
            Iterator<BigInteger> brItr = groupByBrCd.keySet().iterator();
            Iterator<BigInteger> prdidItr = null;    



            BigInteger brCode = null;
            BigInteger prdId = null;

            Map<BigInteger, List<Product>> tempMap = null;
            List<Product1> accListPerBr = null;
            List<Product1> accListPerBrPerPrd = null;

            Product1 tempPrd = null;
            Double sum = null;
            while (brItr.hasNext()) {
                brCode = brItr.next();
                //get  list per branch
                accListPerBr = groupByBrCd.get(brCode);

                // group by br wise product wise
                groupByPrId=accListPerBr.stream().collect(Collectors.groupingBy(Product1::getPrdId, Collectors.toList()));

                System.out.println("====================");
                System.out.println(groupByPrId);

                prdidItr = groupByPrId.keySet().iterator();
                while(prdidItr.hasNext()){
                    prdId=prdidItr.next();
                    // get list per brcode+product code
                    accListPerBrPerPrd=groupByPrId.get(prdId);
                    newPrd = new Product();
                     // Extract zeroth element to put in Output List to represent this group
                    tempPrd = accListPerBrPerPrd.get(0);
                    newPrd.setBranchCode(tempPrd.getBranchCode());
                    newPrd.setPrdId(tempPrd.getPrdId());

                    //Set accCOunt by using size of list of our group
                    newPrd.setCountOfAccts(BigInteger.valueOf(accListPerBrPerPrd.size()));
                    //Sum actual balance of our  of list of our group 
                    sum = accListPerBrPerPrd.stream().filter(o -> o.getActualBalance() != null).mapToDouble(o -> o.getActualBalance().doubleValue()).sum();
                    newPrd.setSumActBal(BigDecimal.valueOf(sum));
                    // Add product element in final output list

                    finalOutputList.add(newPrd);

                }

            }

            System.out.println("+++++++++++++++++++++++");
            System.out.println(finalOutputList);

        }
    }

La sortie est comme ci-dessous:

+++++++++++++++++++++++
[Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}]

Après l'avoir formaté:

[
Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, 
Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}
]
Charudatta Joshi
la source