I want to use Java lambda expression for an intersection of two lists and then ever with lambda expression I want to delete from the list.
Example: I have
List<Person> first = new ArrayList<Person>();
List<Person> second = new ArrayList<Person>();
Suppose that both lists have some Person Object.
I want put into List temp intersection of two list filtered by name for example:
List<Person> temp = new ArrayList<>();
for (Person one : first) {
for (Person two : second) {
if(one.getName.equals(two.getName)) {
temp.add(two);
}
}
}
Then I want to remove some Person from temp using a filter, for example, using the Surname.
for (Person tmp : temp) {
for (Person one : first) {
if (one.getSurname.equals(tmp.getSurname)) {
temp.remove(tmp);
}
}
}
I want to use lambda expression, How i can do?
Intersection between first and second:
List<Person> intersection = first.stream()
.filter(p1 -> second.stream().anyMatch(p2 -> p2.getName().equals(p1.getName())))
.collect(Collectors.toList());
Remove elements from myList based on elements from first:
first.stream()
.filter(p1 -> myList.stream().anyMatch(p2 -> p1.getSurName().equals(p2.getSurName())))
.forEach(myList::remove);
You may do it like so,
Map<String, Set<String>> nameToSurname = second.stream().collect(
Collectors.groupingBy(Person::getName,
Collectors.mapping(Person::getSurname, Collectors.toSet())));
List<Person> temp = first.stream().
filter(p -> nameToSurname.containsKey(p.getName()))
.filter(p -> !nameToSurname.get(p.getName()).contains(p.getSurname()))
.collect(Collectors.toList());
First create a map from mame to all the surnames with that name using the second list. Then iterate over the first list, for each person, check whether there's a value in the map by passing the name as the key. If it does, then check whether the surnames matches that of the current person. If both the criteria are satisfied, then collect it into a container.
Both for-loops might be compressed into two Stream::filter methods:
List<Person> temp = second.stream()
.filter(s -> first.stream().anyMatch(f -> f.getName().equals(s.getName())))
.filter(s -> !first.stream().anyMatch(f -> f.getSurame().equals(s.getSurame())))
.collect(Collectors.toList());
Lambdas are not always the best solution.
You can use retainAll method from Collection<E>.
fist.retainAll(second);
Here is solution by method reference and not.
second.stream()
.filter(first.stream.map(Person::getName).collect(toSet())::contains)
.filter(not(first.stream.map(Person::getSurname).collect(toSet())::contains))
.collect(toList());
I didn't write the code in IDE. there may be some compiler errors.
Related
I have two different list. I want to find and filter by field not on the other list. For example.
List<ObjectOne> List<ObjectTwo>
field | value field | value
{id=5, name="aaa"} {xId=4, text="aaa"}
{id=6, name="bbb"} {xId=6, text="bbb"}
{id=7, name="ccc"} {xId=5, text="ccc"}
If I want to filter one list, I am using org.springframework.cglib.core.CollectionUtils like that
CollectionUtils.filter(objectOne, s -> (
(ObjectOne) s).getId() == anyObject.getXId()
&& (ObjectOne) s).getName() == anyObject.getText());
But I want to compare two List, and I want to find noncontains value like that
objectOne = {id=5, name="aaa"} , {id=7, name="ccc"}
How am I filter with streamApi or any third-party libraries ?
noneMatch helps you here.
objectOnes.stream()
.filter(x -> objectTwos.stream()
.noneMatch(y -> y.text.equals(x.name) && y.xId == x.id))
.collect(Collectors.toList());
You can create a list of ObjectOne from the list of ObjectTwo as this:
List<ObjectOne> objectOne = listTwo.stream()
.map(x -> new ObjectOne(x.getxId(), x.getText()))
.collect(Collectors.toList());
And then you can use retainAll to find the common elements:
listOne.retainAll(objectOne);
if you wont modify the list of ObjectOne, then you can create a second list from listOne
List<ObjectOne> listOne2 = new ArrayList<>(listOne);
listOne2.retainAll(objectOne);
Note, this solution need to use hashcode and equals in ObjectOne.
I don't know how to do this with just one stream, but at least I got a solution for two.
List<ObjectOne> list1 = new ArrayList<>();
List<ObjectTwo> list2 = new ArrayList<>();
list1.stream()
.filter(o1 -> isInObjectTwoList(list2, o1))
.collect(Collectors.toList());
private boolean isInObjectTwoList(List<ObjectTwo> objectTwoList, ObjectOne o1) {
return objectTwoList.stream()
.filter(o2 -> o2.getText().equals(o1.getValue()))
.findAny()
.isPresent();
}
I have two list of objects accounts and salaries and I need to iterate the list of objects. If the id matches I need to update the account object.
I have list1 and list2 these two objects are different object type. we need to update the object(param) in list1 with list2 object(param).
Example
if(accounts !=null && salaries!=null) { // checking for nulls
for (Account obj1 : accounts) {// iterating objects
for (Salary obj2 : salaries) {
String id = ibj2.getId();
if (id.equals(obj1.getId())) {// id checks
obj1.setxxxx(obj2.getxxxx());// set the value
}
}
}
}
I tried:
list1.stream().flatMap(x -> list2 .stream() .filter(y -> x.getId().equals(y.getId())));
Your flatMap (suggested in the comment), will produce a Stream<Salary>, which won't allow you do modify the corresponding Account instances.
You can create a Stream of Accounts and their corresponding Salary and run forEach on that Stream:
accounts.stream()
.flatMap(a->salaries.stream()
.filter(s -> s.getID().equals(a.getID())
.map(s -> new SimpleEntry<Account,Salary)(a,s)))
.forEach(e -> e.getKey().setxxxx(e.getValue().getxxxx()));
The final operation, obj1.setxxxx(obj2.getxxxx()); requires to have both obj1 and obj2. that dictates the item that is streamed from both lists
list1.stream()
.forEach(obj1 ->
list2.stream()
.filter(obj2 -> obj1.getId().equals(obj2.getId()))
.findFirst()
.ifPresent(obj2 -> obj1.setxxxx(obj2.getxxxx()))
);
I would always suggest to create a Map since the lookup cost will decrease and it will become more readable.
Map<String, List<Salary>> salaryById = salaries.stream().collect(Collectors.groupingBy(Salary::getId));
accounts.forEach(a -> CollectionUtils.emptyIfNull(salaryById.get(a.getId())).forEach(s -> s.setxxxx(..)));
In case Account Salary <-> Account is One to One you change grouping to Collectors.toMap(..)
I have a Person object which has a name attribute and some other attributes. I have two HashSet with Person objects. Note that name is not an unique attribute meaning that two Persons with same name can have different height so using HashSet does not guarantee that two Persons with same name are not in the same set.
I need to add one set to another so there are no Persons in the result with the same name. So something like this:
public void combine(HashSet<Person> set1, HashSet<Person> set2){
for (String item2 : set2) {
boolean exists = false;
for (String item1 : set1) {
if(item2.name.equals(item1.name)){
exists = true;
}
}
if(!exists){
set1.add(item2);
}
}
}
Is there a cleaner way of doing this in java8?
set1.addAll(set2.stream().filter(e -> set1.stream()
.noneMatch(p -> p.getName().equals(e.getName())))
.collect(Collectors.toSet()));
If it makes sense for you to override equals and hashCode you can use something like this:
Set<Parent> result = Stream.concat(set1.stream(), set2.stream())
.collect(Collectors.toSet());
Without the Java 8 streams you can easily just do this:
Set<Parent> result = new HashSet<>();
result.addAll(set1);
result.addAll(set2);
But remember this solution is only feasible when it makes sense to have equals and hashCode overridden.
`
You can use a HashMap with name as key, then you avoid the O(n²) runtime complexity of your method. If you need HashSet, then there is no faster way. Even if you use Java 8 Streams. They add just more overhead.
public Map<String, Person> combine(Set<Person> set1, Set<Person> set2) {
Map<String, Person> persons = new HashMap<>();
set1.forEach(pers -> persons.computeIfAbsent(pers.getName(), key -> pers));
set2.forEach(pers -> persons.computeIfAbsent(pers.getName(), key -> pers));
return persons;
}
Alternatively, you could create your own collector. Assuming that you're certain that two persons with the same name are in fact the same person:
First you can define a collector:
static Collector<Person, ?, Map<String, Person>> groupByName() {
return Collector.of(
HashMap::new,
(a,b) -> a.putIfAbsent(b.name, b),
(a,b) -> { a.putAll(b); return a;}
);
}
Then you can use it to group persons by name:
Stream.concat(s1.stream(), s2.stream())
.collect(groupByName());
However, this would give you a Map<String, Person> and you just want the whole set of Persons found, right?
So, you could just do:
Set<Person> p = Stream.concat(s1.stream(), s2.stream())
.collect(collectingAndThen(groupByName(), p -> new HashSet<>(p.values())));
I am using the following expression to filter a list of people whose birthday matches criteria.
List<Person> matchingPeople = people.stream()
.filter(p -> dateFilters.stream()
.anyMatch(df ->
numOfDaysBetween(p.getBirthDate(), df.getDate()) < df.getDiffRange()
)
)
.collect(Collectors.toList());
Collectors.toList() returns the list of people matching the criteria. I am wondering how to capture the list of people that got removed for debugging/logging purpose. One possible way is to run the list through another filter, but that will be inefficient. Can we do it in the same filter ?
Yes, you can do it in the same filter call :
List<Person> matchingPeople =
people.stream()
.filter(p -> {
if (dateFilters.stream()
.anyMatch(df -> numOfDaysBetween(p.getBirthDate(), df.getDate()) < df.getDiffRange()
))
{
return true;
} else {
//you can add here code to log elements that don't pass the filter
//or you can add these elements to an external List
return false;
}
})
.collect(Collectors.toList());
Another alternative is to partition the input List into two Lists based on the filter predicate :
Map<Boolean, List<Person>> partition =
people.stream()
.collect(Collectors.partitioningBy(p -> <same code as in your filter method call>));
You can simply remove matching people from people collection:
people.removeAll(matchingPeople);
I have 2 lists:
List1: Object1 (name1, id1)
List2: Object2(name2, id2)
Given the size of list1 is the same as list2
I want to iterate ove the list2 and if the name2 of list2 is not null than update the name1 of list1.
here is the code using old java:
for(Object1 obj1:list1) {
for(Object2 obj2:list2) {
if(obj1.getId1.equals(obj2.getId2)) {
obj1.setName1(obj2.getName2);
}
}
}
Which is the best way to implement this with java.util.stream?
Just to be clear, I think your code is intended to do the following: update the name of each item in list1 to be the name of any item in list2 that has the same ID. There doesn't seem to be anything checking if the names of items in list1 are null.
If that's correct, then:
list2.forEach(obj2 -> list1.stream()
.filter(obj1 -> obj1.getId().equals(obj2.getId()))
.forEach(obj1 -> obj1.setName(obj2.getName()));
If you want to check if name is null, then add a new filter before setting the name:
.filter(Objects::isNull)
As I mentioned in the comments. If the id is a uniqe identifier for your objects, then a Map is more appropriate than a List.
So you better work on such a map (assuming id is an integer):
Map<Integer, Object1> obj1map;
You could create that map from your first list with
obj1map = list1.stream().collect(toMap(Object1::getId, Function.identity()));
Now you can stream over your second list and update the map accordingly:
list2
.stream()
.filter(o -> o.getName() != null) // remove null names
.filter(o -> obj1map.containsKey(o.getId())) // just to make sure
.forEach(o -> obj1map.get(o.getId()).setName(o.getName()));
The idea of a stream is that it does not have context. Knowing where you are in the first stream is context which you would need to find the corresponding item in the second stream.
Use a normal for loop with an index i instead.
for (int i=0; i < list2.size(); i++) {
Object2 item2 = list2.get(i);
if (list2.get(i).name != null) {
list1.get(i).name = item.name;
}
}