1

I have two list ListA listA = new ArrayList() and ListB listB = new ArrayList() both contain object of type Position object and Position contain these variables.

Position {
    String account;
    String Date;
    String Cycle;
    String Status;
} 

and if for example my lists has values like this

ListA = ["ACC1","20-Jan-23","1","open"],
        ["ACC1","20-Jan-23","2","closing"],
        ["ACC2","20-Jan-23","1","open"],
        ["ACC2","20-Jan-23","2","closing"],
        ["ACC3","20-Jan-23","1","open"],
        ["ACC3","20-Jan-23","2","closing"]

ListB = ["ACC1","20-Jan-23","1","open"],
        ["ACC1","20-Jan-23","2","closing"],
        ["ACC2","20-Jan-23","1","open"],
        ["ACC2","20-Jan-23","2","closed"],
        ["ACC3","20-Jan-23","1","open"]

now my requirement is from the above both lists, I need to find out and extract all accounts that exactly matches in the other list but uniquely, meaning

"ACC1" having two objects in listA and same exists in ListB so this the right candidate that i needed to extract

"ACC2" having two objects in both lists but only one matching exactly same with listB, but other record doesnt match because the status values differs ('closing' and 'closed') so i need to exclude ACC2

"ACC3" having two objects in listA but not in list B, so i need to exclude this ACC3 as well

so ACC1 is what i'm interested in

Is there any way we can achieve this efficiently using java streams or usual standard way

Thanks

3 Answers 3

1

Assuming you have all the necessary getters and have overriden the equals and hashCode methods, for example like this:

class Position {
    String account;
    String Date;
    String Cycle;
    String Status;

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final Position position = (Position) o;
        return Objects.equals(account, position.account) && Objects.equals(Date, position.Date)
               && Objects.equals(Cycle, position.Cycle) && Objects.equals(Status, position.Status);
    }

    @Override
    public int hashCode() {
        return Objects.hash(account, Date, Cycle, Status);
    }
}

You could stream over both lists, order them in an identical way and group them by account and use the resulting maps to filter accounts having the same list of Position objects. Example code:

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

public class Example2 {
    public static void main(String[] args) {

        List<Position> listA = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
                                       new Position("ACC1", "20-Jan-23", "2", "closing"),
                                       new Position("ACC2", "20-Jan-23", "1", "open"),
                                       new Position("ACC2", "20-Jan-23", "2", "closing"),
                                       new Position("ACC3", "20-Jan-23", "1", "open"),
                                       new Position("ACC3", "20-Jan-23", "2", "closing"));

        List<Position> listB = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
                                       new Position("ACC1", "20-Jan-23", "2", "closing"),    
                                       new Position("ACC2", "20-Jan-23", "1", "open"),
                                       new Position("ACC2", "20-Jan-23", "2", "closed"),
                                       new Position("ACC3", "20-Jan-23", "1", "open"));

        Comparator<Position> comparator = Comparator.comparing(Position::getAccount)
                                                    .thenComparing(Position::getDate)
                                                    .thenComparing(Position::getCycle)
                                                    .thenComparing(Position::getStatus);
        Map<String, List<Position>> mapA = listA.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
        Map<String, List<Position>> mapB = listB.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));

        List<String> result = mapA.keySet()
                                  .stream()
                                  .filter(key -> mapA.get(key).equals(mapB.get(key)))
                                  .toList();

        System.out.println(result);

    }

    @AllArgsConstructor
    @Getter
    @ToString
    static class Position {
        String account;
        String Date;
        String Cycle;
        String Status;

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            final Position position = (Position) o;
            return Objects.equals(account, position.account) && Objects.equals(Date, position.Date)
                   && Objects.equals(Cycle, position.Cycle) && Objects.equals(Status, position.Status);
        }

        @Override
        public int hashCode() {
            return Objects.hash(account, Date, Cycle, Status);
        }
    }
}

UPDATE

..been told not to override equal and hascode method in PositionEntity... is there any other way to compare the equality of the 2 list of objects without using equals method?

you can do the field by field comparison manually. I have added an example using BiPredicates. May be there are some eleganter ways to do this with some 3rd party libraries. But without changing the first approach too much the below should give you the same result without the need to override equals and hashCode.

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

public class Example2 {
    public static void main(String[] args) {

        List<Position> listA = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
                                       new Position("ACC1", "20-Jan-23", "2", "closing"),
                                       new Position("ACC2", "20-Jan-23", "1", "open"),
                                       new Position("ACC2", "20-Jan-23", "2", "closing"),
                                       new Position("ACC3", "20-Jan-23", "1", "open"),
                                       new Position("ACC3", "20-Jan-23", "2", "closing"));

        List<Position> listB = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
                                       new Position("ACC1", "20-Jan-23", "2", "closing"),
                                       new Position("ACC2", "20-Jan-23", "1", "open"),
                                       new Position("ACC2", "20-Jan-23", "2", "closed"),
                                       new Position("ACC3", "20-Jan-23", "1", "open"));

        Comparator<Position> comparator = Comparator.comparing(Position::getAccount)
                                                    .thenComparing(Position::getDate)
                                                    .thenComparing(Position::getCycle)
                                                    .thenComparing(Position::getStatus);
        Map<String, List<Position>> mapA = listA.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
        Map<String, List<Position>> mapB = listB.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));

        //Since you cannot modify equals and hashCode you can do the field by field comparison manually
        //there are maybe some 3rd party libraries which might do this elegantly,
        // but something like below should work fine
        BiPredicate<Position,Position> positionsEqual = (position1, position2) ->
                position1.getAccount().equals(position2.getAccount()) &&
                position1.getDate().equals(position2.getDate()) &&
                position1.getCycle().equals(position2.getCycle()) &&
                position1.getStatus().equals(position2.getStatus());

        //Same approach to test the equality of two lists index by index using above predicate
        BiPredicate<List<Position>,List<Position>> listsEqual = (list1, list2) -> {
            if (list1.size() != list2.size()){
                return false;
            }
            return IntStream.range(0, list1.size()).allMatch(i -> positionsEqual.test(list1.get(i), list2.get(i)));
        };

        //using the predicate to filter
        List<String> result = mapA.keySet()
                                  .stream()
                                  .filter(key -> listsEqual.test(mapA.get(key),(mapB.get(key))))
                                  .toList();

        System.out.println(result);

    }

    @AllArgsConstructor
    @Getter
    @ToString
    static class Position {
        String account;
        String Date;
        String Cycle;
        String Status;

        //Constructor, getter, toString..
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Nice work @Eritrean
@Eritrean, excellent this seems working fine, however i am struggling understand this line how it actually filters. what does this actually do ? filter(key -> mapA.get(key).equals(mapB.get(key)))
@EnthuLeo Added a discription.
Superb explanation thank you and got it, i was missing the point that we can compare the two lists in single line.
This worked fine until i have been told not to override equal and hascode method in PositionEntity as its used by various models and it will break their tests, hence is there any other way to compare the equality of the 2 list of objects without using equals method.
|
0

As Eritrean stated above, you need to implement hashCode() and equals() methods. Instead of streams you could use HashSet:

    Set<Position> positionSet = new HashSet<>();
    listA.stream().forEach(position -> positionSet.add(position));
    Set<String> result = new ArrayList<>();
    for(Position position : listB){
        if(positionSet.contains(position)){
            result.add(position.getAccount());
        }
    }

So you find all Positions that are in both lists and then just add their accounts to the result set.

Comments

0

This solution presumes you have overridden equals and hashCode which is a requirement. This is required for the map, and set data structures. I also made use of getters.

Putting the objects in sets would also make the lookups faster.

  • This puts the objects in Map<Boolean, Set<Position>>. I used two loops to avoid accessing external state in a stream. I also didn't feel it was warranted to combine the lists to use a single loop.
Map<Boolean, Set<Position>> map = new HashMap<>();

for (Position p : setA) {   
    map.computeIfAbsent(setA.contains(p) && setB.contains(p),
            v -> new HashSet<>()).add(p);
}
for (Position p : setB) {   
    map.computeIfAbsent(listA.contains(p) && setB.contains(p),
            v -> new HashSet<>()).add(p);
}
  • Once the values are separated into two sets, those accounts that were not in both sets are retrieved from the map. This is done by filtering on the boolean in the streamed entrySet.

Set<Position> partial = map.entrySet().stream().filter(Entry::getKey)
        .flatMap(e -> e.getValue().stream())
        .collect(Collectors.toCollection(HashSet::new));
  • Now that a partial list of possible successes has been retrieved, the Accounts that were not in both lists must be removed from the partial list.
for(Position p : map.get(false)) {
     partial.removeIf(pos -> pos.getAccount().equals(p.getAccount()));
}

results.forEach(System.out::println);

prints

Position [ACC1, 20-Jan-23, 1, open]
Position [ACC1, 20-Jan-23, 2, closing]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.