Juan
Juan

Reputation: 574

How to get uncommon objects from two list of diferent objects

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

Answers (2)

lexicore
lexicore

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:

  • I think creating Map<FooBarKey, ...> is reasonable here to avoid constantly creating new FooBarKey instances for the same Bars.
  • A multi-map 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

Jaroslaw Pawlak
Jaroslaw Pawlak

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() {
        ...
    }
}
  1. Take all 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.
  2. Put 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.
  3. Go over 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>.
  4. Repeat above three steps for the other list to find all uncommon 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

Related Questions