Reputation: 574
I have two lists
List<Foo> foolist = getAll();
List<Bar> barList = getAll();
class Foo {
String name;
String age;
....
// more fields
}
class Bar {
String barName;
int codeBar;
....
// more field
}
We can make relationship between them with age<->codeBar and name<->barName I want to get Bar objects that are not into foolist, How can it be done using streams?
I see examples using stream().filter(list::contains) but it is not valid in this case.
Can anyone point me into right direction?
Thanks to everyone.
I'm looking something like this:
barList.stream().filter(b->fooList.stream().anyMatch(f->!b.getBarName().equalsIgnoreCase(f.getName) && b.getCodeBar() == Integer.valueOf(age))).collect(Collectors.toList());
I don't know yet if it's right
Upvotes: 0
Views: 1432
Reputation: 43689
A naive solution is to loop through both lists. Assuming you can formulate your equality condition as BiPredicate<Foo, Bar>
:
for (Bar bar: bars) {
boolean foundEqualFoo = false;
for(Foo foo: foos) {
if (predicate.test(foo, bar)) {
foundEqualFoo = true;
break;
}
}
if (!foundEqualFoo) {
barsNotInFoos.add(bar);
}
}
That's O(n*m)
time complexity.
A better solution would be to define a key class like FooBarKey
which would hold name
and age
and implement equals(...)
and hashCode()
correctly. You could then do the following:
Map<FooBarKey, List<Bar>> barsByKey = bars
.stream()
.collect(Collectors.groupingBy(
FooBarKey::ofBar,
HashMap::new,
Collectors.toList()));
Set<FooBarKey> fooKeys = foos
.stream()
.map(FooBarKey::ofFoo)
.collect(Collectors.toCollection(HashSet::new));
barsByKey.keySet().removeAll(fooKeys);
Set<Bar> uncommonBars = barsByKey
.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet());
Few notes on the solution:
Map<FooBarKey, ...>
is reasonable here to avoid constantly creating new FooBarKey
instances for the same Bar
s.Map<FooBarKey, List<Bar>>
is needed since there may be bars with the same key in the list.HashMap::new
and HashSet::new
suppliers are used where applicable to make sure hash tables are used to guarantee O(1)
on corresponding operations.That should be O(2*n + m)
.
Upvotes: 2
Reputation: 5578
You could do something like this:
public static void main(String[] args) {
List<Foo> fooList = asList(new Foo("1", "1"), new Foo("2", "2"), new Foo("3", "3"));
List<Bar> barList = asList(new Bar("4", 4), new Bar("3", 3), new Bar("5", 5));
Set<Blah> fooSet = fooList.stream().map(Main::fooToBlah).collect(toCollection(HashSet::new));
Set<Blah> barSet = barList.stream().map(Main::barToBlah).collect(toCollection(HashSet::new));
barList.stream()
.filter(bar -> !fooSet.contains(barToBlah(bar)))
.forEach(System.out::println);
fooList.stream()
.filter(foo -> !barSet.contains(fooToBlah(foo)))
.forEach(System.out::println);
}
static Blah fooToBlah(Foo foo) {
return new Blah(foo.name, foo.age);
}
static Blah barToBlah(Bar bar) {
return new Blah(bar.barName, "" + bar.codeBar);
}
static class Blah {
String name;
String age;
public Blah(String name, String age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
...
}
@Override
public int hashCode() {
...
}
}
Foo
objects and convert them to Blah
(name it appropriately of course) - this is needed because Foo
has other fields that we don't care about.Blah
objects into a HashSet
so that you don't end up with O(n*m)
time complexity. You could do just collect(toSet())
, but I prefer to be explicit about it here as it is important for performance.Bar
objects and if an object is not in the above set, that's the uncommon Bar
object. It needs to be converted to Blah
and unfortunately you cannot use Stream.map
as in the end it should still be Stream<Bar>
, not Stream<Blah>
.Foo
objects.Remember that equals
and hashCode
methods are needed on Blah
class. Foo
and Bar
classes don't need them.
EDIT:
You can convert the following code
Set<Blah> fooSet = fooList.stream().map(Main::fooToBlah).collect(toCollection(HashSet::new));
barList.stream()
.filter(bar -> !fooSet.contains(barToBlah(bar)))
.forEach(System.out::println);
into something like this
barList.stream()
.filter(bar -> fooList.stream()
.map(Main::fooToBlah)
.noneMatch(foo -> foo.equals(barToBlah(bar)))
)
.forEach(System.out::println);
or even remove Blah
class completely
barList.stream()
.filter(bar -> fooList.stream().noneMatch(foo -> Objects.equals(foo.name, bar.barName) && Objects.equals(foo.age, "" + bar.codeBar)
.forEach(System.out::println);
but you will end up with much worse time complexity and then there is also a question of readability.
Upvotes: 4