Java 8'de birden çok alan adına göre gruplama


95

POJO'dan bazı alan adlarına göre nesneleri gruplamak için kod buldum. Bunun kodu aşağıdadır:

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

Ve çıktı (doğru olan):

{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}]}

Peki ya birden çok alana göre gruplamak istersem? Bu POJO'da groupingBy()yöntemi uyguladıktan sonra yöntemde bazı POJO'ları açıkça geçirebilirim equals(), ancak verilen POJO'dan birden fazla alana göre gruplandırabileceğim gibi başka bir seçenek var mı?

Örneğin burada benim durumumda, isme ve yaşa göre gruplamak istiyorum.


1
İşin püf noktası, tüm alanlardan benzersiz bir dize oluşturmaktır.
Marko Topolnik

3
mappingAşağı akış toplayıcı olarak BTW , gönderdiğiniz kodda fazlalıktır.
Marko Topolnik

8
Hızlı ve kirli çözümdür people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Misha

Yanıtlar:


170

Burada birkaç seçeneğiniz var. En basit yolu, koleksiyonerlerinizi zincirlemek:

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

O zaman Fred adlı 18 yaşındaki kişilerin bir listesini almak için şunları kullanırsınız:

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

İkinci bir seçenek, gruplamayı temsil eden bir sınıf tanımlamaktır. Bu Kişi'nin içinde olabilir. Bu kod a kullanır, recordancak JEP 359 eklenmeden önce Java sürümlerinde bir sınıf (ile equalsve hashCodetanımlanmış) olabilir:

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

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

O zaman şunları kullanabilirsiniz:

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

ve ile ara

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

Son olarak, kendi grup kaydınızı uygulamak istemiyorsanız, etrafındaki birçok Java çerçevesinin pairbu tür şeyler için tasarlanmış bir sınıfı vardır. Örneğin: apache commons çifti Bu kitaplıklardan birini kullanıyorsanız, haritanın anahtarını bir ad ve yaş çifti yapabilirsiniz:

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

ve şununla geri alın:

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

Kişisel olarak, kayıtlar amacı daha iyi gösterdiğinden ve çok az kod gerektirdiğinden, kayıtların dilde mevcut olduğu için jenerik kayıtlarda pek bir değer görmüyorum.


5
Function<T,U>bu anlamda da amacı gizler - ancak her eşleme adımı için kendi işlevsel arayüzünü ilan eden birini görmezsiniz; niyet zaten lambda gövdesindedir. Tuple'larla aynı: API bileşenleri arasındaki tutkal türleri kadar harikadırlar. BTW Scala'nın vaka sınıfları , IMHO'nun hem özlü hem de kasıtlı açıklık açısından büyük bir kazançtır.
Marko Topolnik

1
Evet, ne demek istediğini anlıyorum. Sanırım (her zaman olduğu gibi) nasıl kullanıldıklarına bağlı. Yukarıda verdiğim örnek - bir Haritanın anahtarı olarak bir Çift kullanmak - bunu nasıl yapmayacağınıza dair iyi bir örnek. Scala'ya çok aşina değilim - iyi şeyler duyduğum için onu öğrenmeye başlamam gerekecek.
sprinter

1
Sadece beyan edememek hayal NameAge: Tek astar olarak case class NameAge { val name: String; val age: Int }--- ve almak equals, hashCodeve toString!
Marko Topolnik

1
Güzel - 'yapmam gerekenler' sırasına başka bir öğe itildi. Maalesef FIFO!
sprinter

@sprinter İlk kod parçacığındaki tür doğru değil ve şu şekilde değiştirilmelidirMap<String, Map<Integer, List<Person>>> map
kasur

39

İşte koda bakın:

Basitçe bir İşlev yaratabilir ve işi sizin için yapmasına izin verebilirsiniz, bir tür işlevsel Tarz!

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

Şimdi onu harita olarak kullanabilirsiniz:

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

Şerefe!


2
Bu çözümü kullandım ama farklı. İşlev <Kişi, Dize> compositeKey = personRecord -> StringUtils.join (personRecord.getName (), personRecord.getAge ());
bpedroso

8

groupingByYöntem, birinci parametre olan Function<T,K>burada:

@param <T>giriş öğelerinin türü

@param <K>anahtarların türü

Lambda'yı kodunuzda anonim sınıfla değiştirirsek, bir çeşit görebiliriz:

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

Hemen şimdi çıktı parametresini değiştirin <K>. Bu durumda, örneğin, isme ve yaşa göre gruplama için org.apache.commons.lang3.tuple'dan bir çift sınıf kullandım, ancak ihtiyaç duyduğunuzda grupları filtrelemek için kendi sınıfınızı oluşturabilirsiniz.

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

Son olarak, lambda back ile değiştirdikten sonra, kod şöyle görünür:

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

Kullanmaya ne dersin List<String>?
Alex78191

7

Merhaba Siz sadece arada kullanabilirsiniz groupingByKeygibi

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

2

Grubunuzdaki anahtar tanım için bir sınıf tanımlayın.

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

}

Şimdi kodunuzda,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));

3
Bu sadece yeniden icat ediyor Ararys.asList()- ki bu BTW, OP'nin durumu için iyi bir seçim.
Marko Topolnik

Ve ayrıca Pairdiğer örnekte bahsedilen örneğe benzer , ancak bağımsız değişken sınırı yoktur.
Benny Bottema

Ayrıca bunu değişmez kılmalısınız. (ve hesaplayın hashCode) bir kez)
RobAu

2

Listeyi birçok alan için sınıflandırıcı olarak kullanabilirsiniz, ancak boş değerleri İsteğe bağlı olarak sarmalamanız gerekir:

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

1

Çeşitli müşterilere öğle yemekleri sunan bir catering firmasına rapor vermem gerekiyordu. Başka bir deyişle, yemek hizmeti, yemek hizmetlerinden sipariş alan bir veya daha fazla firmaya sahip olabilir ve tüm müşterileri için her gün kaç öğle yemeği üretmesi gerektiğini bilmelidir!

Sadece dikkat etmek için, bu örneği fazla karmaşıklaştırmamak için sıralama kullanmadım.

Bu benim kodum:

@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));
    }
}

0

BranchCode ve prdId alanlarına göre gruplamayı bu şekilde yaptım, sadece ihtiyacı olan biri için gönderiyorum

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

        }
    }

Çıktı aşağıdaki gibidir:

+++++++++++++++++++++++
[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}]

Biçimlendirdikten sonra:

[
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}
]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.