How to use two filters in stream for different transformations











up vote
10
down vote

favorite
2












I need to perform transformations only for a particular condition.
I do this transformation:



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


But for the condition when there is more than the specified date, I do not need to convert anything, I just need to return this data:



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


Next, to combine these two filters - I combine the two lists:



listResult.addAll(listMoreByDate);


My question is, can this be done in one stream? Because filter 2 is absolutely useless, it simply returns a list for this condition.



Is it possible to perform these transformations with one continuous expression?



My full code:



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









share|improve this question
























  • There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
    – erickson
    Nov 6 at 22:23















up vote
10
down vote

favorite
2












I need to perform transformations only for a particular condition.
I do this transformation:



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


But for the condition when there is more than the specified date, I do not need to convert anything, I just need to return this data:



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


Next, to combine these two filters - I combine the two lists:



listResult.addAll(listMoreByDate);


My question is, can this be done in one stream? Because filter 2 is absolutely useless, it simply returns a list for this condition.



Is it possible to perform these transformations with one continuous expression?



My full code:



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









share|improve this question
























  • There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
    – erickson
    Nov 6 at 22:23













up vote
10
down vote

favorite
2









up vote
10
down vote

favorite
2






2





I need to perform transformations only for a particular condition.
I do this transformation:



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


But for the condition when there is more than the specified date, I do not need to convert anything, I just need to return this data:



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


Next, to combine these two filters - I combine the two lists:



listResult.addAll(listMoreByDate);


My question is, can this be done in one stream? Because filter 2 is absolutely useless, it simply returns a list for this condition.



Is it possible to perform these transformations with one continuous expression?



My full code:



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









share|improve this question















I need to perform transformations only for a particular condition.
I do this transformation:



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


But for the condition when there is more than the specified date, I do not need to convert anything, I just need to return this data:



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


Next, to combine these two filters - I combine the two lists:



listResult.addAll(listMoreByDate);


My question is, can this be done in one stream? Because filter 2 is absolutely useless, it simply returns a list for this condition.



Is it possible to perform these transformations with one continuous expression?



My full code:



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






java java-8 java-stream






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 5 at 15:38









nullpointer

34.1k1069137




34.1k1069137










asked Nov 5 at 12:32









FreeOnGoo

825




825












  • There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
    – erickson
    Nov 6 at 22:23


















  • There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
    – erickson
    Nov 6 at 22:23
















There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
– erickson
Nov 6 at 22:23




There's no inherent virtue to a Stream; they should be used to make code easier to read. This might be a case where a for-loop is better than a stream.
– erickson
Nov 6 at 22:23












3 Answers
3






active

oldest

votes

















up vote
10
down vote



accepted










I can’t see anything simpler than



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


as these two operations are fundamentally different. Building a Map in the first step is unavoidable, as it will be used to identify the items with equal getGroupId property.



That said, you should consider switching from using Date to the java.time API.






share|improve this answer




























    up vote
    6
    down vote













    Yes, you can merge the two conditions by using the partitioningBy collector as follows:



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


    This essentially uses the partitioningBy collector to organise the elements in such a way that all the elements passing the criteria info.getDate().getTime() < date.getTime() aswell as where it's false i.e. where info -> info.getDate().getTime() >= date.getTime() is true into a Map<Boolean, List<T>>.



    Further, we utilise the collectingAndThen collector to apply a finishing function upon the Map<Boolean, List<T>> returned by the partitioningBy collector, in this case we concatenate the result of the applying the logic of:



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


    which I've simplified to:



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


    with the elements returned where info.getDate().getTime() < date.getTime() returned false (map.get(false).stream()).



    Finally, we collect the result into a ArrayList implementation with the toCollection collector.






    share|improve this answer




























      up vote
      1
      down vote













      Another approach (even more verbose in definition but much less verbose at use site) is to create a custom Collector:



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


      where



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


      and



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


      Note: it won't be that effective if you have huge number of groups/infos because it does not group by getGroupId (it iterates the entire list for every Info to be added).






      share|improve this answer





















        Your Answer






        StackExchange.ifUsing("editor", function () {
        StackExchange.using("externalEditor", function () {
        StackExchange.using("snippets", function () {
        StackExchange.snippets.init();
        });
        });
        }, "code-snippets");

        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "1"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        convertImagesToLinks: true,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: 10,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });














         

        draft saved


        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53154515%2fhow-to-use-two-filters-in-stream-for-different-transformations%23new-answer', 'question_page');
        }
        );

        Post as a guest
































        3 Answers
        3






        active

        oldest

        votes








        3 Answers
        3






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes








        up vote
        10
        down vote



        accepted










        I can’t see anything simpler than



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


        as these two operations are fundamentally different. Building a Map in the first step is unavoidable, as it will be used to identify the items with equal getGroupId property.



        That said, you should consider switching from using Date to the java.time API.






        share|improve this answer

























          up vote
          10
          down vote



          accepted










          I can’t see anything simpler than



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


          as these two operations are fundamentally different. Building a Map in the first step is unavoidable, as it will be used to identify the items with equal getGroupId property.



          That said, you should consider switching from using Date to the java.time API.






          share|improve this answer























            up vote
            10
            down vote



            accepted







            up vote
            10
            down vote



            accepted






            I can’t see anything simpler than



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


            as these two operations are fundamentally different. Building a Map in the first step is unavoidable, as it will be used to identify the items with equal getGroupId property.



            That said, you should consider switching from using Date to the java.time API.






            share|improve this answer












            I can’t see anything simpler than



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


            as these two operations are fundamentally different. Building a Map in the first step is unavoidable, as it will be used to identify the items with equal getGroupId property.



            That said, you should consider switching from using Date to the java.time API.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 5 at 13:43









            Holger

            157k22216414




            157k22216414
























                up vote
                6
                down vote













                Yes, you can merge the two conditions by using the partitioningBy collector as follows:



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


                This essentially uses the partitioningBy collector to organise the elements in such a way that all the elements passing the criteria info.getDate().getTime() < date.getTime() aswell as where it's false i.e. where info -> info.getDate().getTime() >= date.getTime() is true into a Map<Boolean, List<T>>.



                Further, we utilise the collectingAndThen collector to apply a finishing function upon the Map<Boolean, List<T>> returned by the partitioningBy collector, in this case we concatenate the result of the applying the logic of:



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


                which I've simplified to:



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


                with the elements returned where info.getDate().getTime() < date.getTime() returned false (map.get(false).stream()).



                Finally, we collect the result into a ArrayList implementation with the toCollection collector.






                share|improve this answer

























                  up vote
                  6
                  down vote













                  Yes, you can merge the two conditions by using the partitioningBy collector as follows:



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


                  This essentially uses the partitioningBy collector to organise the elements in such a way that all the elements passing the criteria info.getDate().getTime() < date.getTime() aswell as where it's false i.e. where info -> info.getDate().getTime() >= date.getTime() is true into a Map<Boolean, List<T>>.



                  Further, we utilise the collectingAndThen collector to apply a finishing function upon the Map<Boolean, List<T>> returned by the partitioningBy collector, in this case we concatenate the result of the applying the logic of:



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


                  which I've simplified to:



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


                  with the elements returned where info.getDate().getTime() < date.getTime() returned false (map.get(false).stream()).



                  Finally, we collect the result into a ArrayList implementation with the toCollection collector.






                  share|improve this answer























                    up vote
                    6
                    down vote










                    up vote
                    6
                    down vote









                    Yes, you can merge the two conditions by using the partitioningBy collector as follows:



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


                    This essentially uses the partitioningBy collector to organise the elements in such a way that all the elements passing the criteria info.getDate().getTime() < date.getTime() aswell as where it's false i.e. where info -> info.getDate().getTime() >= date.getTime() is true into a Map<Boolean, List<T>>.



                    Further, we utilise the collectingAndThen collector to apply a finishing function upon the Map<Boolean, List<T>> returned by the partitioningBy collector, in this case we concatenate the result of the applying the logic of:



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


                    which I've simplified to:



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


                    with the elements returned where info.getDate().getTime() < date.getTime() returned false (map.get(false).stream()).



                    Finally, we collect the result into a ArrayList implementation with the toCollection collector.






                    share|improve this answer












                    Yes, you can merge the two conditions by using the partitioningBy collector as follows:



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


                    This essentially uses the partitioningBy collector to organise the elements in such a way that all the elements passing the criteria info.getDate().getTime() < date.getTime() aswell as where it's false i.e. where info -> info.getDate().getTime() >= date.getTime() is true into a Map<Boolean, List<T>>.



                    Further, we utilise the collectingAndThen collector to apply a finishing function upon the Map<Boolean, List<T>> returned by the partitioningBy collector, in this case we concatenate the result of the applying the logic of:



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


                    which I've simplified to:



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


                    with the elements returned where info.getDate().getTime() < date.getTime() returned false (map.get(false).stream()).



                    Finally, we collect the result into a ArrayList implementation with the toCollection collector.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Nov 5 at 13:39









                    Aomine

                    31.1k52653




                    31.1k52653






















                        up vote
                        1
                        down vote













                        Another approach (even more verbose in definition but much less verbose at use site) is to create a custom Collector:



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


                        where



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


                        and



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


                        Note: it won't be that effective if you have huge number of groups/infos because it does not group by getGroupId (it iterates the entire list for every Info to be added).






                        share|improve this answer

























                          up vote
                          1
                          down vote













                          Another approach (even more verbose in definition but much less verbose at use site) is to create a custom Collector:



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


                          where



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


                          and



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


                          Note: it won't be that effective if you have huge number of groups/infos because it does not group by getGroupId (it iterates the entire list for every Info to be added).






                          share|improve this answer























                            up vote
                            1
                            down vote










                            up vote
                            1
                            down vote









                            Another approach (even more verbose in definition but much less verbose at use site) is to create a custom Collector:



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


                            where



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


                            and



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


                            Note: it won't be that effective if you have huge number of groups/infos because it does not group by getGroupId (it iterates the entire list for every Info to be added).






                            share|improve this answer












                            Another approach (even more verbose in definition but much less verbose at use site) is to create a custom Collector:



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


                            where



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


                            and



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


                            Note: it won't be that effective if you have huge number of groups/infos because it does not group by getGroupId (it iterates the entire list for every Info to be added).







                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Nov 5 at 13:55









                            Tomasz Linkowski

                            2,630821




                            2,630821






























                                 

                                draft saved


                                draft discarded



















































                                 


                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53154515%2fhow-to-use-two-filters-in-stream-for-different-transformations%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest




















































































                                這個網誌中的熱門文章

                                Hercules Kyvelos

                                Tangent Lines Diagram Along Smooth Curve

                                Yusuf al-Mu'taman ibn Hud