Как использовать группировку по сокращению без получения дополнительного - PullRequest
2 голосов
/ 08 марта 2019

Для этого очень упрощенного примера моей проблемы у меня есть объект Stat с полем year и три других поля статистики. Представьте, что это результаты ежегодной статистики по количеству пациентов каждого типа животных из ветвей ветеринарной сети, и я хочу получить суммы по всем ветвям по годам.

Другими словами, из списка Stat объектов я бы хотел вернуть Map<Integer, Stat>, где целое число - это год, а объект Stat имеет год и суммы для каждого из четырех полей.

public class Stat
{
    int year;
    public int getYear() { return year; }

    long cats;
    public long getCats() { return cats; }

    long dogs;
    public long getDogs() { return dogs; }

    long pigeons;
    public long getPigeons() { return pigeons; }

    public Stat(int year, long cats, long dogs, long pigeons)
    {
        this.year = year;
        this.cats = cats;
        this.dogs = dogs;
        this.pigeons = pigeons;
    }

    public Stat(Stat left, Stat right)
    {
        if (left.year != right.year)
            throw new IllegalArgumentException("Only allow combining for same year.");
        this.year = left.year;
        this.cats = left.cats + right.cats;
        this.dogs = left.dogs + right.dogs ;
        this.pigeons = left.pigeons + right.pigeons;
    }

    @Override
    public String toString()
    {
        return String.format("%d c=%d d=%d p=%d", year, cats, dogs, pigeons);
    }
}
@Test
public void testStat()
{
    List<Stat> items = Arrays.asList(
        new Stat(2017, 5, 8, 12),
        new Stat(2017, 123, 382, 15),
        new Stat(2018, 1, 2, 3)
        );
    Map<Integer, Optional<Stat>> result = items.stream()
        .collect(Collectors.groupingBy(Stat::getYear,
            Collectors.reducing(Stat::new)
        ));
    System.out.println(result);
}

Optional не является необходимым, поскольку groupingBy никогда не создаст List, который нуждается в reducing, если бы не было элементов.

Есть ли способ получить Map<Integer, Stat>, желательно без необходимости создания пустого объекта "личность"?

Если мне нужно прибегнуть к созданию функции создания идентификатора для reducing, то у конструктора объединения объекта Stat должен быть год (см. Конструктор), так как же конструктору идентификаторов можно передать ему год?

1 Ответ

2 голосов
/ 08 марта 2019

Вы могли бы достичь этого, используя Collectors.toMap как:

Map<Integer, Stat> result = items.stream()
        .collect(Collectors.toMap(Stat::getYear, 
                Function.identity(), (one, another) -> sumStatsOfSameYear(one, another)));

, где sumAttributes определяется как

// stat from the same year
private static Stat sumStatsOfSameYear(Stat one, Stat another) {
    new Stat(one.getYear(), one.getCats() + another.getCats(),
            one.getDogs() + another.getDogs(), one.getPigeons() + another.getPigeons()))
}
...