Iterate and Map two lists in java 8 - java

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;
}
}

Related

How to filter a List with two object of different class and with compare field in Java

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();
}

Java nested Map/List - filter by list contents, returning parent object

I have two string arguments to my function - "Pizza", and "Chips". I'd like to use streams to return the author whose "foods" key has contents matching these two strings
List<String> collection = Arrays.asList("Pizza", "Chips");
private static List<Map<String, Object>> authors = Arrays.asList(
ImmutableMap.of("id", "author-1",
"firstName", "Adam",
"lastName", "Awldridge",
"foods", Arrays.asList("Pizza", "Chips")),
ImmutableMap.of("id", "author-2",
"firstName", "Bert",
"lastName", "Bruce",
"foods", Arrays.asList("Pizza", "Fish")),
... // other authors
);
This is my attempt with streams:
return authors
.stream()
.filter(authors.stream()
.flatMap(author -> author.get("foods"))
.findAny(queryFoods))
.findFirst().orElse(null);
I want to return the first author who's foods match that of my query. I think the main difficulty is organizing data - unfortunately I can't get the following casting to work.
.flatMap(author -> (List<String>) author.get("foods"))
Also, this might stream through the authors too many times (I should use .filter on the stream I just made with
authors.stream()
Here you cannot directly treat the value of foods key as a List. Its just an Object. So, first you need to do an instance of check and if it is an instance of List, then you can check whether it contains the values that are there in your collection.
Map<String,Object> firstAuthor = authors
.stream()
.filter(author -> {
Object foods = author.get("foods");
if(foods instanceof List) {
List foodsList = (List) foods;
return foodsList.containsAll(collection);
}
return false;
})
.findFirst().orElse(null);
OUTPUT:
{id=author-1, firstName=Adam, lastName=Awldridge, foods=[Pizza, Chips]}
The above code will give you the required author if it exists or else null.
[Here, I have assumed that you want to check whether the author has all the food items that are present in collection object created by you. If you want to check for only one of the items then you can use contains() method from java.util.List instead of containsAll() method. Also, you will have to iterate over the collection object to check for each item in collection.]
I would solve it by filtering in stream:
Map<String,Object> author = authors.stream()
.filter(a -> a.containsKey("foods"))
.filter(a -> a.get("foods") instanceof List)
.filter(a -> ((List) a.get("foods")).containsAll(collection))
.findFirst().orElse(null);
Maybe this is what you want?
authors
.stream()
.filter(a -> a.get("foods").stream().anyMatch(x -> "Pizza".equals(x)))
.findFirst().orElse(null);

How to compare two arraylist containing objects with shared properties

How to compare two arraylist containing objects with shared properties.
Ex: I have a pojo class Abc
Class Abc {
String dataString ;
int rowNum;
......
}
Abc list1 contains - 2000 records & more sometimes
Abc list2 contains - 60 records & more sometimes
I want to compare list1 dataString to list2 datastring and return rownum
if list1.dataString Notequals list2.dataString
I need rowNumber from List1 if data string DOESN'T matches. List2 Rownum can be ignored.
In high-level terms, your code should:
iterate over list1
for each element check whether its data string appears somewhere in list2
if so, include the row number of the list1 element in the result
To make the code fast, in the second step the set of list2 data strings can be precomputed. Still in high-level terms, the code is:
List<int> filtered(List<Abc> list1, List<Abc> list2) {
var dataStrings = setOf(list2.map(x -> x.dataString));
var abcs = list1.filter(x -> dataStrings.contains(x.dataString));
return abcs.map(x -> x.rowNum);
}
In standard Java, the code looks more bloated, of course:
List<int> filtered(List<Abc> list1, List<Abc> list2) {
Set<String> dataStrings = list2.stream()
.map(x -> x.dataString))
.collect(Collectors.toSet());
return list1.stream()
.filter(x -> dataStrings.contains(x.dataString))
.map(x -> x.rowNum)
.collect(Collectors.toList());
}
If in this case, you have two different lists and wants to have comparison, one of the simple way to do that can be two foreach loops with equality condition and then having the value:
for(Abc list1Obj : list1)
{
for(Abc list2Obj : list2)
{
if(list1.dataString.Equals(list2.dataString))
{
int value = Integer.parseInt(list1.rowNumber);
}
}
}
In this case, it would for each item in list1 with each item in list2.

Update param in list1<object> from list2<object> by using java 8

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(..)

Java lambda intersection two list and remove from result

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.

Categories