Reputation: 16671
My Structure
A {
String id;
String bid;
}
B {
String id;
}
Given
List<A> aList = Arrays.asList(
new A (1,2),
new A (2,5),
new A (3,9),
new A (4,10),
new A (5, 20),
new A (6, 8),
new A (7, 90)
new A (8, 1)
);
List<B> bList = Arrays.asList(
new B (2),
new B (9),
new B (10)
);
Now i want the elements of A which don't match with any B's element should be collected in another collection and these elements should be deleted from A collection itself.
Result
List<A> aList = Arrays.asList(
new A (1,2),
new A (3,9),
new A (4,10)
);
List<A> aListBin = Arrays.asList(
new A (2,5),
new A (5, 20),
new A (6, 8),
new A (7, 90)
new A (8, 1)
);
MY take
I can think of iterating A using iterator and for each element in A iterate through B and if found keep else keep adding to separate list and delete using iterator remove.
Is there a better way to do this using stream magic? Thanks
Upvotes: 0
Views: 578
Reputation: 40062
Since you are dealing with two different classes you can't compare them directly. So you need to reduce to the least common denominator which is the integer ID's.
// start a stream of aList.
List<A> aListBin = aList.stream()
// Convert the bList to a collection of
// of ID's so you can filter.
.filter(a -> !bList.stream()
// get the b ID
.map(b->b.id)
// put it in a list
.collect(Collectors.toList())
// test to see if that list of b's ID's
// contains a's bID
.contains(a.bid))
//if id doesn't contain it, then at to the list.
.collect(Collectors.toList());
To finish up, remove the newly created list from the aList.
aList.removeAll(aListBin);
They are displayed as follows:
System.out.println("aListBin = " + aListBin);
System.out.println("aList = " + aList);
aListBin = [[2, 5], [5, 20], [6, 8], [7, 90], [8, 1]]
aList = [[1, 2], [3, 9], [4, 10]]
Note:
Upvotes: 0
Reputation: 23017
Collectors#partitionBy is your friend.
First, we'll extract the id
s from the list of B
s into a bare Set<Integer>
, so we can use that for lookup:
Set<Integer> bSet = bList.stream()
.map(b -> b.id)
.collect(Collectors.toSet());
As mentioned by JB Nizet, a HashSet
is fit for the job.
Then it's just as simple as this – we'll partition by a given predicate. The predicate is whether A.bid
is contained in any B.id
(which we stored into bSet
for convenience).
Map<Boolean, List<A>> map = aList.stream()
.collect(Collectors.partitioningBy(a -> bSet.contains(a.bid)));
Now map.get(true)
contains all items contained in B
, map.get(false)
all others.
In order to replace aList
, simply reassign aList
:
aList = map.get(true);
Upvotes: 1
Reputation: 428
You can use Collectors.partitioningBy
. Is it better? Depends on your definition of better. It is much more concise code, however it is not as efficient as the simple iterator loop you described.
I cannot think of a more efficient way than the iterator route, except maybe using a string hashset for lookup of the B class id.
However, if you prefer concise code, here is the code using partitioningBy:
class A {
int id;
int bid;
public A(int id, int bid){
this.id = id;
this.bid = bid;
}
public boolean containsBId(List<B> bList) {
return bList.stream().anyMatch(b -> bid == b.id);
}
}
class B {
int id;
public B(int id){
this.id = id;
}
}
class Main {
public static void main(String[] args) {
List<A> aList = Arrays.asList(
new A (1,2),
new A (2,5),
new A (3,9),
new A (4,10),
new A (5, 20),
new A (6, 8),
new A (7, 90),
new A (8, 1)
);
List<B> bList = Arrays.asList(
new B (2),
new B (9),
new B (10)
);
Map<Boolean, List<A>> split = aList.stream()
.collect(Collectors.partitioningBy(a -> a.containsBId(bList)));
aList = split.get(true);
List<A> aListBin = split.get(false);
}
Upvotes: 0
Reputation: 11
Yes, you can use Java 8 Streams.
Here's the full example from your input:
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.toList;
public class MyClass {
public static void main(String args[]) {
class A {
public int id;
public int bid;
public A(int id, int bid) { this.id = id; this.bid = bid; }
public String toString() { return "(" + id + "," + bid + ")"; }
};
class B {
public int id;
public B(int id) { this.id = id; }
public String toString() { return "" + id; }
};
List<A> aList = Arrays.asList(
new A (1,2), // not removed
new A (2,5), // removed
new A (3,9), // not removed
new A (4,10), // not removed
new A (5, 20),// not removed
new A (6, 8), // not removed
new A (7, 90),// not removed
new A (8, 1)// not removed
);
List<B> bList = Arrays.asList(
new B (2),
new B (9),
new B (10)
);
List<A> aListBin = new ArrayList<>();
aList.stream()
.forEach( a -> {
if (bList.stream().noneMatch(b -> b.id == a.bid )) {
aListBin.add(a);
}
});
aList = aList.stream()
.filter( a -> bList.stream().anyMatch(b -> b.id == a.bid))
.collect(toList());
System.out.println("Alist-> " + aList);
System.out.println("Blist-> " + bList);
System.out.println("Removed-> " + aListBin);
}
}
Output:
Alist-> [(1,2), (3,9), (4,10)]
Blist-> [2, 9, 10]
Removed-> [(2,5), (5,20), (6,8), (7,90), (8,1)]
Upvotes: 0
Reputation: 1072
You can't remove elements from a list created with Arrays.asList(). It returns a view of the array you pass as argument, so calling remove will throw UnsupportedOperationException.
Assuming you have ArrayList instead, I still don't think you can achieve this on a single step, and certainly not with "stream magic", since streams won't allow you to modify the original collection.
In two steps, something like:
List<A> newList = aList.stream()
.filter(a -> !bList.contains(a.bid))
.collect(Collectors.toList());
aList.removeAll(newList);
If performance is an issue, use Set or Map(with id as key) instead of List in order to perform the contains() and the removeAll() in O(1) and O(n) respectively.
Upvotes: 0