Как использовать два фильтра в потоке для разных преобразований - программирование
Подтвердить что ты не робот

Как использовать два фильтра в потоке для разных преобразований

Мне нужно выполнять преобразования только для определенного условия. Я делаю это преобразование:

// filter 1: less date - group by max date by groupId
        List<Info> listResult = new ArrayList<>(listInfo.stream()
                .filter(info -> info.getDate().getTime() < date.getTime())
                .collect(Collectors.groupingBy(Info::getGroupId, Collectors.collectingAndThen(
                        Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
                        Optional::get))).values());

Но для условия, когда есть больше, чем указанная дата, мне не нужно ничего преобразовывать, мне просто нужно вернуть эти данные:

// filter 2: more date - nothing change in list
        List<Info> listMoreByDate = listInfo.stream()
                .filter(info -> info.getDate().getTime() >= date.getTime())
                .collect(Collectors.toList());

Далее, чтобы объединить эти два фильтра - я объединю два списка:

listResult.addAll(listMoreByDate);

Мой вопрос, это можно сделать в одном потоке? Поскольку фильтр 2 абсолютно бесполезен, он просто возвращает список для этого условия.

Можно ли выполнить эти преобразования одним непрерывным выражением?

Мой полный код:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) throws ParseException {
        Info info1 = new Info(1L, getDateFromStr("2018-02-02T10:00:00"), 3L);
        Info info2 = new Info(2L, getDateFromStr("2018-02-02T12:00:00"), 3L);
        Info info3 = new Info(3L, getDateFromStr("2018-02-05T12:00:00"), 6L);
        Info info4 = new Info(4L, getDateFromStr("2018-02-05T10:00:00"), 6L);

        Date date = getDateFromStr("2018-02-03T10:10:10");

        List<Info> listInfo = new ArrayList<>();
        listInfo.add(info1);
        listInfo.add(info2);
        listInfo.add(info3);
        listInfo.add(info4);

        // filter 1: less date - group by max date by groupId
        List<Info> listResult = new ArrayList<>(listInfo.stream()
                .filter(info -> info.getDate().getTime() < date.getTime())
                .collect(Collectors.groupingBy(Info::getGroupId, Collectors.collectingAndThen(
                        Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
                        Optional::get))).values());

        // filter 2: more date - nothing change in list
        List<Info> listMoreByDate = listInfo.stream()
                .filter(info -> info.getDate().getTime() >= date.getTime())
                .collect(Collectors.toList());

        listResult.addAll(listMoreByDate);

        System.out.println("result: " + listResult);
    }

    private static Date getDateFromStr(String dateStr) throws ParseException {
        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(dateStr);
    }
}

class Info {
    private Long id;
    private Date date;
    private Long groupId;

    public Info(Long id, Date date, Long groupId) {
        this.id = id;
        this.date = date;
        this.groupId = groupId;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Long getGroupId() {
        return groupId;
    }

    public void setGroupId(Long groupId) {
        this.groupId = groupId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Info info = (Info) o;
        return Objects.equals(id, info.id) &&
                Objects.equals(date, info.date) &&
                Objects.equals(groupId, info.groupId);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, date, groupId);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Info{");
        sb.append("id=").append(id);
        sb.append(", date=").append(date);
        sb.append(", groupId=").append(groupId);
        sb.append('}');
        return sb.toString();
    }
}
4b9b3361

Ответ 1

Я не вижу ничего проще, чем

List<Info> listResult = Stream.concat(
    listInfo.stream()
        .filter(info -> info.getDate().getTime() < date.getTime())
        .collect(Collectors.toMap(Info::getGroupId, Function.identity(),
            BinaryOperator.maxBy(Comparator.comparing(Info::getDate))))
        .values().stream(),
    listInfo.stream()
        .filter(info -> info.getDate().getTime() >= date.getTime())
    )
    .collect(Collectors.toList());

так как эти две операции принципиально разные. Создание Map на первом этапе неизбежно, так как она будет использоваться для идентификации элементов с одинаковым свойством getGroupId.

Тем не менее, вам следует подумать о переходе с использования Date на java.time API.

Ответ 2

Да, вы можете объединить два условия с помощью сборщика partitioningBy следующим образом:

 List<Info> resultSet = 
      listInfo.stream()
              .collect(collectingAndThen(partitioningBy(info -> info.getDate().getTime() < date.getTime()),
                     map -> Stream.concat(map.get(true)
                            .stream()
                            .collect(toMap(Info::getGroupId,
                                    Function.identity(),
                                    (Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2))
                            .values().stream(), map.get(false).stream())
                            .collect(Collectors.toCollection(ArrayList::new))));

По существу, он использует сборщик partitioningBy для организации элементов таким образом, что все элементы, соответствующие критериям info.getDate().getTime() < date.getTime() а также где это ложно, т.е. где info → info.getDate().getTime() >= date.getTime() имеет значение true для Map<Boolean, List<T>>.

Кроме того, мы используем сборщик collectingAndThen чтобы применить завершающую функцию к Map<Boolean, List<T>> возвращенному сборщиком partitioningBy, в этом случае мы объединяем результат применения логики:

.collect(Collectors.groupingBy(Info::getGroupId, 
          Collectors.collectingAndThen(Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
           Optional::get))))
.values();

который я упростил до:

.collect(toMap(Info::getGroupId, Function.identity(), (Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2)))
.values();

с возвращенными элементами, где info.getDate().getTime() < date.getTime() вернул false (map.get(false).stream()).

Наконец, мы собираем результат в реализацию ArrayList с toCollection сборщика toCollection.

Ответ 3

Другой подход (еще более многословный по определению, но гораздо менее многословный при использовании сайта) заключается в создании настраиваемого Collector:

List<Info> listResult = listInfo.stream().collect(dateThresholdCollector(date));

где

private static Collector<Info, ?, List<Info>> dateThresholdCollector(Date date) {
    return Collector.of(
            () -> new ThresholdInfoAccumulator(date), ThresholdInfoAccumulator::accept,
            ThresholdInfoAccumulator::combine, ThresholdInfoAccumulator::addedInfos
    );
}

а также

class ThresholdInfoAccumulator {

    private final Date date;
    private final List<Info> addedInfos = new ArrayList<>();

    ThresholdInfoAccumulator(Date date) {
        this.date = date;
    }

    List<Info> addedInfos() {
        return addedInfos;
    }

    ThresholdInfoAccumulator accept(Info newInfo) {
        if (canAdd(newInfo)) {
            addedInfos.add(newInfo);
        }
        return this;
    }

    boolean canAdd(Info newInfo) {
        if (newInfo.getDate().compareTo(date) < 0) { // lower date - max date by groupId
            return addedInfos.removeIf(addedInfo -> isEarlierDateInSameGroup(addedInfo, newInfo));
        }
        return true; // greater or equal date - no change
    }

    private boolean isEarlierDateInSameGroup(Info addedInfo, Info newInfo) {
        return addedInfo.getGroupId().equals(newInfo.getGroupId())
                && addedInfo.getDate().compareTo(newInfo.getDate()) < 0;
    }

    ThresholdInfoAccumulator combine(ThresholdInfoAccumulator other) {
        other.addedInfos().forEach(this::accept);
        return this;
    }
}

Примечание: это не будет так эффективно, если у вас есть огромное количество групп /Infos, потому что это не группа по getGroupId (он перебирает весь список для каждого Info будет добавлен).