Filter objects from a list that have the same member
Asked Answered
S

4

5

I have a list of objects. The object looks like this:

public class Slots {
  String slotType;
  Visits visit;
}


public class Visits {
  private long visitCode;
  private String agendaCode;
  private String scheduledTime;
  private String resourceType;
  private String resourceDescription;
  private String visitTypeCode;
  ...
}

I need to find the elements that have the same agendaCode, visitTypeCode and scheduledTime and for the life of me I can't get it done.

I tried this:

Set<String> agendas = slotsResponse.getContent().stream()
    .map(Slots::getVisit)
    .map(Visits::getAgendaCode)
    .collect(Collectors.toUnmodifiableSet());

Set<String> visitTypeCode = slotsResponse.getContent().stream()
    .map(Slots::getVisit)
    .map(Visits::getVisitTypeCode)
    .collect(Collectors.toUnmodifiableSet());

Set<String> scheduledTime = slotsResponse.getContent().stream()
    .map(Slots::getVisit)
    .map(Visits::getScheduledTime)
    .collect(Collectors.toUnmodifiableSet());

List<Slots> collect = slotsResponse.getContent().stream()
    .filter(c -> agendas.contains(c.getVisit().getAgendaCode()))
    .filter(c -> visitTypeCode.contains(c.getVisit().getVisitTypeCode()))
    .filter(c -> scheduledTime.contains(c.getVisit().getScheduledTime()))
    .collect(Collectors.toList());

But it's not doing what I thought it would. Ideally I would have a list of lists, where each sublist is a list of Slots objects that share the same agendaCode, visitTypeCode and scheduledTime. I struggle with functional programming so any help or pointers would be great!

This is Java 11 and I'm also using vavr.

Slurry answered 22/2, 2019 at 13:58 Comment(1)
You could possibly take a look at the filter logic in this answer, that's what shall solve what you're up to I believe.Caddoan
C
3

Since you mentioned you're using vavr, here is the vavr way to solve this question.

Supposed you have your io.vavr.collection.List (or Array or Vector or Stream or similar vavr collection) of visits:

List<Visits> visits = ...;

final Map<Tuple3<String, String, String>, List<Visits>> grouped =
    visits.groupBy(visit ->
        Tuple.of(
            visit.getAgendaCode(),
            visit.getVisitTypeCode(),
            visit.getScheduledTime()
        )
    );

Or with a java.util.List of visits:

List<Visits> visits = ...;

Map<Tuple3<String, String, String>, List<Visits>> grouped = visits.stream().collect(
    Collectors.groupingBy(
        visit ->
            Tuple.of(
                visit.getAgendaCode(),
                visit.getVisitTypeCode(),
                visit.getScheduledTime()
            )
    )
);
Calorimeter answered 22/2, 2019 at 15:24 Comment(0)
R
1

The easiest way is to define a new class with necessaries fields (agendaCode, visitTypeCode and scheduledTime). Don't forget about equals/hashcode.

public class Visits {
    private long visitCode;
    private String resourceType;
    private String resourceDescription;
    private Code code;
    ...
}
class Code {
    private String agendaCode;
    private String scheduledTime;
    private String visitTypeCode;
    ...
    @Override
    public boolean equals(Object o) {...}
    @Override
    public int hashCode() {...}
}

Then you can use groupingBy like:

Map<Code, List<Slots>> map = slotsResponse.getContent().stream()
            .collect(Collectors.groupingBy(s -> s.getVisit().getCode()));

Also you can just implement equals method inside Visits only for agendaCode, visitTypeCode and scheduledTime. In this case use groupingBy by s.getVisit()

Rem answered 22/2, 2019 at 14:12 Comment(0)
T
1

I love Ruslan's idea of using Collectors::groupingBy. Nevertheless, I don't like creating a new class or defining a new equals method. Both of them coerces you to a single Collectors::groupingBy version. What if you want to group by other fields in other methods?

Here is a piece of code that should let you overcome this problem:

slotsResponse.getContent()
    .stream()
    .collect(Collectors.groupingBy(s -> Arrays.asList(s.getVisit().getAgendaCode(), s.getVisit().getVisitTypeCode(), s.getVisit().getScheduledTime())))
    .values();

My idea was to create a new container for every needed field (agendaCode, visitTypeCode, scheludedTime) and compare slots on these newly created containers. I would have liked doing so with a simple Object array, but it doesn't work - arrays should be compared with Arrays.equals which is not the comparison method used by Collectors::groupingBy.

Please note that you should store somewhere or use a method to define which fields you want to group by.

Topic answered 22/2, 2019 at 14:58 Comment(0)
G
1

The fields you want to group by are all strings. You can define a function which concatenate those fields values and use that as key for your groups. Example

Function<Slots,String> myFunc = s -> s.getVisit().agendaCode + s.getVisit().visitTypeCode + s.getVisit().scheduledTime;
                               // or s.getVisit().agendaCode +"-"+ s..getVisit().visitTypeCode +"-"+ s.getVisit().scheduledTime;

And then group as below:

Map<String,List<Slots>> result = slotsResponse.getContent().stream()
                               .collect(Collectors.groupingBy(myFunc));
Gamut answered 22/2, 2019 at 15:31 Comment(3)
The syntax seems invalid, might just want to try it out on a compiler and the output would be a single entry mapped to a key based on the same logic as myFunc to extract the List<Slots>. But the approach is good IMHO.Caddoan
I meant in the direction that -- 1. the fields are a part of Visits and not Slots so the Function definition needs to be updated to make it compile. 2. While looking up in the map result, one would have to do the same logic of s.agendaCode + s.visitTypeCode + s.scheduledTime to form a correct key to lookup.(result.get) <-- though that should just be myFunc apply if the Slot would have been known.Caddoan
Also, x=a+b+c=d+e+f they would be grouped into the same group while having different values for the individual components. So concatenation is not guaranteed to be correct.Laural

© 2022 - 2024 — McMap. All rights reserved.