Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Proper way of creating a list containing all not-in-intersect elements from two given lists based on a specific attribute?

Tags:

java

Given two Lists of Objects, I'd be able to tell which items are not in their intersect based on one of their attributes. Let's look at the following example:

I have a class Foo that has two attributes: boo and placeholder

class Foo {
    private int boo;
    private int placeholder = 1;

    public Foo(int boo) {
        this.boo = boo;
    }

    public int getBoo() {
        return boo;
    }
}

Now I am creating two Lists from that (let's say this is my input)

    List<Foo> list1 = new ArrayList<Foo>();
    list1.add(new Foo(1));
    list1.add(new Foo(2));
    list1.add(new Foo(3));

    List<Foo> list2 = new ArrayList<Foo>();
    list2.add(new Foo(0));
    list2.add(new Foo(1));
    list2.add(new Foo(2));

And now I'd like to say which Items are in list1 and not in list2 or in list2 and not in list1 based on their attribute boo. So in the above example I want a List<Foo> notInIntersectList that contains one Foo(0) and one Foo(3).

    List<Foo> notInIntersectList = new ArrayList<Foo>();
    list1.forEach(li1foo -> {
        boolean inBothLists = false;
        list2.forEach(li2foo -> {
            if (li1foo.getBoo() == li2foo.getBoo()) {
                inBothLists = true;
            }
        });
        if (!inBothLists) {
            notInIntersectList.add(li1foo);
        }
    });
    //now I covered all items in list1 but not in list2. Now do this again with lists swapped, so I can also cover those.
    //...

Sadly I am getting Local variable inBothLists defined in an enclosing scope must be final or effectively final as an error. How is this issue solved properly, since this seems not to be the "right" solution?

like image 967
Rüdiger Avatar asked Jan 01 '19 14:01

Rüdiger


People also ask

How do you check if a list contains any value from another list in Java?

contains() in Java. ArrayList contains() method in Java is used for checking if the specified element exists in the given list or not. Returns: It returns true if the specified element is found in the list else it returns false.

How do you add all elements of a list to another list in Java?

Create a List by passing another list as a constructor argument. List<String> copyOflist = new ArrayList<>(list); Create a List and use addAll method to add all the elements of the source list.

How do you check if two elements in a list are the same Java?

List equals() Method in Java with Examples. This method is used to compare two lists. It compares the lists as, both lists should have the same size, and all corresponding pairs of elements in the two lists are equal. Parameters: This function has a single parameter which is object to be compared for equality.


2 Answers

If you cannot create an equals and hashCode on your class (maybe they have them already but not based on boo), what I would do is:

  • Create a Set<Integer> (or a BitSet) containing all the boo values in list 1. Call it set1
  • Create a Set<Integer> (or a BitSet) containing all the boo values in list 2. Call it set2
  • Use set1.retainAll(set2) to get the intersection of the two sets.
  • Use the following to create my list:

    Stream.concat(list1.stream(),list2.stream())
          .filter(item-> ! set1.contains(item.getBoo()))
          .collect(Collectors.toList);
    

This is O(m+n) and also ensures the order of the items in the original lists is maintained, as well as any duplicates (items having the same boo).

like image 30
RealSkeptic Avatar answered Oct 24 '22 05:10

RealSkeptic


You cannot mutate variables inside a lambda expression (See: Variable used in lambda expression should be final or effectively final)

Here's a way to fix your code (fun with Streams)

List<Foo> notInIntersectList = list1.stream()
        .filter(fooElementFromList1 -> list2
                .stream()
                .noneMatch(fooElementFromList2 -> fooElementFromList2.getBoo() == fooElementFromList1.getBoo()))
        .collect(Collectors.toCollection(ArrayList::new));

list2.stream()
        .filter(fooElementFromList2 -> list1
            .stream()
            .noneMatch(fooElementFromList1 -> fooElementFromList1.getBoo() == fooElementFromList2.getBoo()))
        .forEach(notInIntersectList::add);

The complexity of this is O(n*m) (where n and m are the number of elements in list1 and list2 respectively).

To do this in O(n+m), you can use a Set. For this, you need a equals and hashcode method on the Foo class. This considers two Foo instances as equal only based on the value of the instance variable boo.

class Foo {
    ....

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Foo other = (Foo) obj;
        return boo == other.boo;
    }

    @Override
    public int hashCode() {
        return boo;
    }
}

And use a Set for this as

Set<Foo> fooSet1 = new HashSet<>(list1);
Set<Foo> fooSet2 = new HashSet<>(list2);

fooSet1.removeAll(list2);
fooSet2.removeAll(list1);

List<Foo> notInIntersectList = Stream.concat(fooSet1.stream(), fooSet2.stream())
            .collect(Collectors.toList());
like image 57
user7 Avatar answered Oct 24 '22 06:10

user7