Java 8 comparison of 2 different lists - java

I need a help on iterating and comparing 2 different list using java 8. If one of the attribute matches then i need to set the values of one list into another. An employee can have multiple projects.
EmployeeDto{ --List
Name
Id
Number
ProjectList }
I have a List projectList
ProjectDto
{
Id,
ProjectName....}
I want to compare employeeList and projectList. Check if Id matches from both list then set
employeeDto.setProjectList(projectList)
List<ProjectDto> newList = new Arraylist<>();
for(EmployeeDto emp: employeeList) {
for(ProjectDto project: ProjectList){
if(project.getId().equals(emp.getId())){
newList.add(project);
}
emp.setProjectList(newList));
}
Thanks in advance

employeeList.forEach(employee -> {
employee.setProjectList(projectList.stream()
.filter(project -> Objects.equals(employee.getId(), project.getId()))
.collect(Collectors.toList()));
});
That is one possible way.

There is no need to do O(n^2) work. You can group the projects by emp ID first.
Map<String, List<ProjectDto>> grouped = projects.stream().collect(groupingBy(ProjectDto::getId, toList()));
Then simply iterate through your employees and set the respective projectList for each one.
employees.forEach(emp -> emp.setProjectList(grouped.getOrDefault(emp.getId(), new ArrayList<>())));

Related

Merging two stream operation into one in Java for performance improvement

I have this object
Class A {
int count;
String name;
}
I have a list of my above custom object as below :
List<A> aList = new ArrayList<>();
A a = new A(1,"abc");
A b = new A(0,"def");
A c = new A(0,"xyz");
aList.add(a);
aList.add(b);
aList.add(c);
I will get this list as input in my service. Now based upon some scenario, first I need to set "count" to ZERO for all elements in the list and based on a check with "name" I need to set the count as ONE for a particular name.
This is how I am doing now :
String tempName = "Some Name like abc/def/xyz";
alist.stream().forEach(x -> x.setCount(0));
aList.stream().filter(x -> x.getName().equalsIgnoreCase(tempName))
.findFirst()
.ifPresent(y -> y.setCount(1));
This is doing my job, but I want to know if I can simplify the above logic and use one single stream instead of two and improve the performance by avoiding looping through the list twice.
Just check if the name matches in the first loop:
alist.forEach(x -> x.setCount(x.getName().equalsIgnoreCase(tempName) ? 1 : 0));

How can I use java streams to retrieve the value of x names?

I can use the below snippet to retrieve the name if there is 1 entry in the list by retrieving element 0 in the list, however, each NameResponse can have several names (e.g. a first name, a middle name and a surname). How can I retrieve x names associated with one customer? There could be 20 names for argument's sake. I would like to implement using a stream since I am using Java 8, but I am unsure how to implement this. Any suggestions?
private List<String> getNames(Customer customer) {
List<NameResponse> nameResponses = new ArrayList<>();
NameResponse nameResponse = new NameResponse();
nameResponse.setName("Test Name");
nameResponses.add(nameResponse);
customer.setNames(nameResponses);
return List.of(customer.getNames().get(0).getName());
}
Customer class:
private List<NameResponse> names;
NameResponse class:
private String name;
Something like below assuming you have the appropriate getters:
return customer.getNames()
.stream()
.map(NameResponse::getName)
.collect(Collectors.toList());
You could do that using the map operator on the stream and then collect to output a list:
return customer.getNames().stream()
.map(nameResponse -> nameResponse.getName())
.collect(Collectors.toList()));

Updating a subsection of a list with an "id" field

I am trying to learn how to use the lambda functions for sleeker code but struggling to make this work.
I have two lists. The "old" list is always shorter or the same length as the "updated list".
I want to take the objects from the "updated list" and overwrite the "stale objects" in the shorter "old list".
The lists have a unique field for each object.
For example, it is a bit like updating books in a library with new editions. The UUID (title+author) remains the same but the new object replaces the old on the shelf with a new book/object.
I know I could do it the "long way" and make a HashMap<MyUniqueFieldInMyObject, MyObject> and then take the new List<MyUpdatedObjects> and do the same.
I.e. Have HashMap<UniqueField, MyOldObject> and HashMap<UniqueField, MyUpdatedObject>, then iterate over the old objects with a pseudo "if updated objects have an entry with the same key, overwrite the value with the updated value"...
But...
Is there a "nicer" shorted way to do this with functional lambda statements?
I was thinking along the lines of:
List<MyObject> updatedList;
List<MyObject> oldList;
updatedList.forEach(MyObject -> {
String id = MyObject.getId();
if (oldList.stream().anyMatcher(MyObject ->
MyObject.getId().matches(id)) {
//Do the replacement here? If so...how?
}
}
Which is where I am lost!
Thanks for any guidance.
If you want to update the list in place rather than making a new list, you can use List.replaceAll:
oldList.replaceAll(old ->
updateListe.stream()
.filter(updated -> updated.getId().equals(old.getId())
.findFirst()
.orElse(old)
);
The main problem with this solution is that its complexity is O(size-of-old*size-of-updated). The approach you described as "long way" can protect you from having to iterate over the entire updated list for every entry in the old list:
// note that this will throw if there are multiple entries with the same id
Map<String, MyObject> updatedMap = updatedList.stream()
.collect(toMap(MyObject::getId, x->x));
oldList.replaceAll(old -> updatedMap.getOrDefault(old.getId(), old));
I recommend you to iterate over the oldList - the one you want to update. For each of the object iterated match the equivalent one by its id and replace it using Stream::map. If an object is not found, replace it with self (doesn't change the object) using Optional::orElse.
List<MyObject> newList = oldList
.stream() // Change values with map()
.map(old -> updatedList.stream() // Iterate each to find...
.filter(updated -> old.getId() == updated.getId()) // ...by the same id
.findFirst() // Get new one to replace
.orElse(old)) // Else keep the old one
.collect(Collectors.toList()); // Back to List
List<Foo> updatedList = List.of(new Foo(1L, "new name", "new desc."));
List<Foo> oldList = List.of(new Foo(1L, "old name", "old desc."));
List<Foo> collect = Stream.concat(updatedList.stream(), oldList.stream())
.collect(collectingAndThen(toMap(Foo::getId, identity(), Foo::merge),
map -> new ArrayList(map.values())));
System.out.println(collect);
This will print out:
[Foo{id=1, name='new name', details='old desc.'}]
In Foo::merge you can define which fields need update:
class Foo {
private Long id;
private String name;
private String details;
/*All args constructor*/
/*getters*/
public static Foo merge(Foo newFoo, Foo oldFoo) {
return new Foo(oldFoo.id, newFoo.name, oldFoo.details);
}
}
I think it's best to add the objects to be updated into a new list to avoid changing a list you are streaming on and then you can simply replace the old with the new list
private List<MyObject> update(List<MyObject> updatedList, List<MyObject> oldList) {
List<MyObject> newList = new ArrayList<>();
updatedList.forEach(object -> {
if (oldList.stream().anyMatch(old -> old.getUniqueId().equals(object.getUniqueId()))) {
newList.add(object);
}
}
return newList;
}

Filtering a list using Java 8 lambda expressions

I have a Project class:
class Project {
List<Name> names;
int year;
public List<Name> getNames(){
return names;
}
}
Then I have another main function where I have a List<Project> and have to filter that list of projects on the basis of year and get names list as the result.
Can you please tell me how to do it using java 8 lambda expressions?
Thanks
Well, you didn't state the exact filtering condition, but assuming you wish to filter elements by a given year:
List<Name> names = projects.stream()
.filter(p -> p.getYear() == someYear) // keep only projects of a
// given year
.flatMap(p -> p.getNames().stream()) // get a Stream of all the
// Names of all Projects
// that passed the filter
.collect(Collectors.toList()); // collect to a List

Find specific object in a List by attribute

I have a list:
List<UserItem> userList = new ArrayList<>();
Where I add the following:
User father = new User();
father.setName("Peter");
UserItem parent = new UserItem(father, null);
userList.add(parent);
I then create another user:
User daughter = new User();
daughter.setName("Emma");
UserItem child = new UserItem(daughter, <OBJECT IN LIST WHERE NAME IS "PETER">);
userList.add(child);
However, I need to change the text wrapped in <> above to the parent object I added before (the father), specified by the name ("Peter" in this case).
How can I find an object in a List by a specific attribute? In my case, how can I find the object in the List that has the name "Peter"?
Please note that I add hundreds, sometimes thousands, of different users like this to the list. Each "parent" has a unique name.
The obvious solution would be iterating on the list and when the condition is met, return the object:
for (User user : userList) {
if ("peter".equals(user.getName()) {
return user;
}
}
And you can use filter (Java 8):
List<User> l = list.stream()
.filter(s -> "peter".equals(s.getUser()))
.collect(Collectors.toList());
to get a list with all "peter" users.
As suggested in comments, I think using Map is a better option here.
Answer to your question is here: https://stackoverflow.com/a/1385698/2068880
Stream peters = userList.stream().filter(p -> p.user.name.equals("Peter"))
However, as ruakh suggested, it's more reasonable to use Map<String, UserItem> to make it faster. Otherwise, it will iterate all the objects in the list to find users with name "Peter".
Other way with parallelStream with findAny
Optional<UserItem> optional = userList.parallelStream().findAny(p -> p.user.getName().equalsIgnoreCase("Peter"));
UserItem user = optional.isPresent() ? optional.get() : null;

Categories

Resources