I have a list of hierarchical objects and I have to remove a hierarchy if a hierarchy has a value as zero.
I have to remove all the managers(employees)
if and only if emp salary is 0 and there are no employees under him.
OR all the employees under him having zero salary.
But not if manager salary is having zero and employees under him does not have zero salary.
Eg.
emp(10)
|---- empA(9)
|---- empAa(8)
|---- empAb(7)
|---- empAc(0)
|---- empB(7)
|---- empBa(0)
|---- empBaa(0)
|---- empBaaa(0)
|---- empBb(0)
|---- empBba(4)
above structure has to be modified as below.
emp(10)
|---- empA(9)
|---- empAa(8)
|---- empAb(7)
|---- empB(7)
|---- empBb(0)
|---- empBba(4)
I am getting stackoverflow exception when the input list is big. how should I enhance this logic to avoid SOFE.
removeHierarchy(List<Employee> managers){
int count = 0;
if(null != managers && !managers.isEmpty()){
for(Employee manager: managers){
List<Employee> toBeRemoved = new ArrayList<>();
List<Employee> employees = manager.getEmployees();
if(0 == manager.getSalary()){
if(null == employees || employees.isEmpty()){
toBeRemoved.add(manager);
} else {
++count;
removeHierarchy(employees);
}
} else {
removeHierarchy(employees);
}
managers.removeAll(toBeRemoved);
}
}
for(int i=0; i < count; i++){
removeHierarchy(managers);
}
}
I think your problem is that you don't actually remove any employees from the lists. If the list has such configuration - you will go in infinite cycles on last lines.
emp(0)
|---- empA(0)
It seems the last lines should be something like managers.removeAll(toBeRemoved)
Here is working function:
void removeHierarchy( List<Employee> managers )
{
List<Employee> toBeRemoved = new ArrayList<>();
for ( Employee manager : managers )
{
removeHierarchy( manager.getEmployees() );
if (0 == manager.getSalary() && manager.getEmployees().isEmpty()) {
toBeRemoved.add( manager );
}
}
managers.removeAll( toBeRemoved );
}
See full test code
To decide whether you have to keep or remove an Employee, you must first process her employees' list:
void removeHierarchy(List<Employee> managers){
if(null != managers && !managers.isEmpty()){
List<Employee> toBeRemoved = new ArrayList<>();
for(Employee manager: managers) {
List<Employee> employees = manager.getEmployees();
removeHierarchy(employees);
if (0 == manager.getSalary()
&& (null == employees || employees.isEmpty())) {
toBeRemoved.add(manager);
}
}
managers.removeAll(toBeRemoved);
}
}
I think this program has multiple problems.
You cannot iterate over a list and try to manipulate it at same time
There's is no logic to remove the identified managers/employees
As suggested by John, I'm adding method that can perform the required task. It is no different that the solution provided in other answer except that I have used Java 8 syntactic sugar.
public static boolean removeEmployee(Employee employee)
{
employee.setEmployees(employee.getEmployees().stream().filter(Employee::removeEmployee).collect(Collectors.toList()));
return !(employee.getSalary()==0 && employee.getEmployees().size()==0);
}
Note: Make sure your array list (list of employees) is not null i.e., empty array list when you create an employee object.
Related
In a spring boot application with jpa.
I have a Entity with a one to many relation.
I create a dto with a list who represent this entity.
When i receive the dto from the client and convert to the entity, we do some validation to add new object, update existing object and remove object user have decided
protected void convertToBeans(CarDto dto, Car bean) {
List < CarComponentOccurrencesDto > dtoAircraftComponentOccurrences = dto.getCarComponentOccurrences();
dtoCarComponentOccurrences = (dtoCarComponentOccurrences == null) ?
new ArrayList < > () : dtoCarComponentOccurrences;
// when a param is removed in the middle of the list, the list replace the removed element by a null othewise, that keep the same size, this cause null pointer exceptions.
dtoCarComponentOccurrences.removeIf(Objects::isNull);
List < CarComponentOccurrences > beanCarComponentOccurrences = bean.getCarComponentOccurrences();
// remove items who dont exist anymore
List < CarComponentOccurrences > carComponentOccurrencesToRemove = new ArrayList < > ();
for (CarComponentOccurrences occurence: beanCarComponentOccurrences) {
if (dtoCarComponentOccurrences.stream().noneMatch(e - > Objects.equals(e.getId(), occurence.getId()))) {
carComponentOccurrencesToRemove.add(occurence);
}
}
for (CarComponentOccurrences toRemove: carComponentOccurrencesToRemove) {
bean.removeCarComponentOccurrences(toRemove);
}
// add items that do not exist
for (CarComponentOccurrencesDto carComponentOccurrenceDto: dtoCarComponentOccurrences) {
if (carComponentOccurrenceDto.getId() == null || beanCarComponentOccurrences.stream().noneMatch(e - > Objects.equals(e.getId(), carComponentOccurrenceDto.getId()))) {
CarComponentOccurrences carComponentOccurrence = new CarComponentOccurrences();
if (carComponentOccurrenceDto.getComponentNameId() != null) {
Optional < CarComponents > optCarComponents = carComponentsRepository
.findById(carComponentOccurrenceDto.getComponentNameId());
if (optCarComponents.isPresent()) {
carComponentOccurrence.setCarComponent(optCarComponents.get());
}
carComponentOccurrence.setCarService(bean);
carComponentOccurrence.setCode(carComponentOccurrenceDto.getCode());
bean.addCarComponentOccurrences(aircraftComponentOccurrence);
}
}
}
}
is there a way to manage create, update and delete item from a list when we attach to entity with mapstruct or we need to that way ?
i found that a lot of boilerplate code
I have a task to read 2 files and match the contents of the files and provide a list of unmatched entries of both files. That means I have to present how many matched entries in the two files and how many unmatched entries in file 1 which is not in file 2 , how many unmatched entries in file 2 which is not in file 1.
My apporach is reading the files , creating java objects out of it, putting the contents of 2 files to 2 separate arraylists and compare them. My current code is listed below. For clarification, I want to check the content of the object ( eg : check EmployeeID and match from both files).
In below code, I have matched file1 content with file2, and removed the matched contents from file2.Works fine to match entries and get the unmatched count of file1 compared to file2.
I am plannning to match the remaining items in file2 and go another round in the same compareByEmpIdandDOBmethod using fileTwoEmpList as first parameter and fileOneEmpList as second parameter get the unmatched count of file2 compared to file1. But I feel this is an overkill and not very efficient. Can someone point out a different approach if any pelase ?
Both of the arraylists are sorted. Thanks in advance !
public class EmpMatching {
public void compareLists(List<EmployeeDetails> fileOneEmpList, List<EmployeeDetails> fileTwoEmpList){
Collections.sort(fileOneEmpList);
Collections.sort(fileTwoEmpList);
List<EmployeeDetails> unmatchedFromListTwo = compareByEmpIdandDOB(fileOneEmpList,fileTwoEmpList);
}
public List<EmployeeDetails> compareByEmpIdandDOB(List<EmployeeDetails> fileOneEmpList,List<EmployeeDetails> fileTwoEmpList){
int matchEmpCountFromTwoFiles = 0;
System.out.println("File One List Size Before Recon " + fileTwoEmpList.size());
for(EmployeeDetails fileOneEmp : fileOneEmpList){
for(int index = 0;index < fileTwoEmpList.size();index++ ){
EmployeeDetails fileTwoEmp= fileTwoEmpList.get(index);
if(fileOneEmp.getEmpID().equals(fileTwoEmp.getEmpID()) && fileOneEmp.getEmpDOB().equals(fileTwoEmp.getEmpDOB())){
matchEmpCountFromTwoFiles++;
fileTwoEmpList.remove(fileTwoEmp);
System.out.println("Match Found " + fileOneEmp.getEmpID());
}
}
System.out.println("File Two List Size " + fileTwoEmpList.size());
}
System.out.println("Match Count >>>>> " + matchEmpCountFromTwoFiles);
System.out.println("File Two List Size >>>>> " + fileTwoEmpList.size());
return fileTwoEmpList;
}
}
//Model class
public class EmployeeDetails implements Comparable<EmployeeDetails>{
private String EmpID;
private String EmpName;
private String EmpDOB;
#Override
public int compareTo(EmployeeDetails o) {
return 0;
}
}
You don't need to sort these lists for this task.
In terms of the Set theory, you need to find the set difference. I.e. to find all unique objects that appear only in the first or in the second list.
This task can be solved in a few lines of code with liner time complexity. But it is important to implement the equals/hashCode contract in the EmployeeDetails.
public List<EmployeeDetails> compareLists(List<EmployeeDetails> fileOneEmpList,
List<EmployeeDetails> fileTwoEmpList) {
Set<EmployeeDetails> emp1 = new HashSet<>(fileOneEmpList);
Set<EmployeeDetails> emp2 = new HashSet<>(fileTwoEmpList);
emp1.removeAll(emp2);
emp2.removeAll(emp1);
emp1.addAll(emp2);
return new ArrayList<>(emp1);
}
The approach above is both the most efficient and the simplest.
If you are comfortable with Streams API, you can try another approach and implement this method in the following way:
public List<EmployeeDetails> compareLists(List<EmployeeDetails> fileOneEmpList,
List<EmployeeDetails> fileTwoEmpList) {
return Stream.of(new HashSet<>(fileOneEmpList), new HashSet<>(fileTwoEmpList)) // wrapping with sets to ensure uniqueness (if objects in the list are guaranteed to be unique - use lists instead)
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.filter(entry -> entry.getValue() == 1) // i.e. object appear only once either in the first or in the second list
.map(Map.Entry::getKey)
.collect(Collectors.toList()); // .toList(); for Java 16+
}
Time complexity of the stream based solution would be linear as well. But as I've said, the first solution based on the Collections API is simpler and slightly more performant.
If for some reason, there's no proper implementation of equals() and hashCode() in the EmployeeDetails. And you have no control over this class and can't change it. Then you can declare a wrapper class and perform the same actions.
Below is an example of how to create the wrapper using Java 16 records.
Methods equals() and hashCode() will be generated by the compiler based on empId and empDob.
public record EmployeeWrapper(String empId, String empDob) {
public EmployeeWrapper(EmployeeDetails details) {
this(details.getEmpID(), details.empDOB);
}
}
The implementation of the equals/hashCode for the EmployeeDetails class based on the empID and empDOB might look like this (also, you can use the facilities of your IDE to generate these methods):
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeDetails that = (EmployeeDetails) o;
return empID.equals(that.empID) && empDOB.equals(that.empDOB);
}
#Override
public int hashCode() {
return Objects.hash(empID, empDOB);
}
Person elpidio = new Person.Builder().id(3L).firstName("elpidio").secondName("gomez").ext("121").build();
Person romual1 = new Person.Builder().id(4L).firstName("romualdo").secondName("perez").ext("141").build();
Person romual2 = new Person.Builder().id(4L).firstName("romualdo").secondName("perez").ext("144").build();
Now I need a out put some thing like this.
[Person [firstName=elpidio, secondName=gomez,ext=[121]],
Person [firstName=romualdo, secondName=perez,ext=[121,144]]]
I refereed to below ans. But the problem I found is that I have many property in Person, and out of that I just need to merge one fields. So the entry set is giving issue. Any suggestion in welcome.
Extract duplicate objects from a List in Java 8
How to merge child objects in list based on duplicate parent object
This is just one way to do it (and probably an overly convoluted, not-so-great way that I haven't completely checked for everything). What I like about this is that it shows how to make a collector of your own, and they are fairly useful to understand those underpinnings. Hopefully the comments are explanation enough. If not, there are lots of guides on custom collectors (like this)
Person elpidio = Person.Builder().id(3L).firstName("elpidio").secondName("gomez").ext("121").build();
Person romual1 = Person.Builder().id(4L).firstName("romualdo").secondName("perez").ext("141").build();
Person romual2 = Person.Builder().id(4L).firstName("romualdo").secondName("perez").ext("144").build();
Map<Long,Person> filteredPersons = Stream.of( elpidio, romual1, romual2 ).collect(Collector.of(
HashMap::new, // Starts with an empty list
(result,entry) -> {
if ( result.containsKey(entry.id)) {
// If we already have this id in our map, we add the ext to the result list. This is assuming
// "ext" in Person is a collection of some sort (because I don't feel like going through the
// process of creating a new arbitrary class - in other words, that exercise is left for the
// reader =) If you don't want dupes of ext, make sure it's a set or something else that will
// remove dupes
result.get(entry.id).ext.addAll(entry.ext);
} else {
result.put(entry.id, entry);
}
},
(result1, result2) -> {
// This handles the case where the collect is handled in parallel. How do we merge two resulting
// maps?
// In this case we are arbitrarily calling "result1" the version to check against, but it really
// doesn't matter.
for ( Map.Entry<Long,Person> person : result2.entrySet() ) {
if ( result1.containsKey(person.getKey()) ) {
result1.get(person.getKey()).ext.addAll(person.getValue().ext);
} else {
result1.put(person.getKey(), person.getValue());
}
}
return result1;
}));
filteredPersons.forEach( (k,v) -> System.out.println( k + " - first: " + v.first + " second: " + v.second + " " +
"ext: " + v.ext));
outputs:
3 - first: elpidio second: gomez ext: [121]
4 - first: romualdo second: perez ext: [141, 144]
There are a lot of ways to solve this question. However you if you want the answer as per your reference.
persons.stream()
.collect(Collectors.groupingBy(Function.identity(), toList()))
.entrySet()
.stream()
.map(x -> {
List<String> exFields = x.getValue().stream().map(Person::getExField).flatMap(Collection::stream).collect(toList());
Person duplicatePerson = x.getKey();
duplicatePerson.setExField(exFields);
return duplicatePerson;
}).collect(toList());
Explanation.
Here we are trying to group objects if certain fields are same in object. This can be done by overridinng the equals and HashCode method. like
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(id, person.id) &&
Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
#Override
public int hashCode() {
return Objects.hash(id, firstName, lastName);
}
With this much in place you will be able to get your entrySet. Keys of the entrySet as the duplicate object (any object with those three fields) and value will be a list containing all those duplicate objects. Now from this duplicate list you only need that exFields from each object so this is the code
List<String> exFields = x.getValue().stream().map(Person::getExField).flatMap(Collection::stream).collect(toList());
Why I had to use flatMap ?
Once you get the list of all the exFields you assign that list to your duplicate object.
duplicatePerson.setExField(exFields);
Finally Collect.
You can use Java 8 Api Stream in order to filter them by id using a predicate or use distinct()
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
I have a JPA query to delete selected entities which is stored in a list. I am currently deleting the entities in a loop via the in built JPA delete() method 1 by 1. Is there a way to just pass in the list instead of looping it?
Current implementation that works but looping to delete 1 by 1. I need the initial query to get list of entities for other reasons thus not looking to change that. Just a way to pass in the list of entiies to be deleted. Please advice. Thanks.
Note: This is with Java 8 and Spring 4.3 if it matters.
#GetMapping("/delete/{name}/{count}")
public String delete(#PathVariable String name, #PathVariable int count){
boolean isDelete = true;
while (isDelete){
//1st query
List<PersonEntity> results = personService.get(name, count);
if(results != null && !results.isEmpty()){
System.out.println("Deleting following: ");
//2nd query
results.forEach(p -> {
System.out.println(p.getName());
personService.delete(p);
});
} else {
isDelete = false;
}
}
return "Done!";
}
You can try something like this:
List<PersonEntity> results = personService.get(name, count);
if(results != null && !results.isEmpty()) {
List<Integer> personIds = results.stream()
.map(personIds)
.collect(Collectors.toList());
personService.deleteManyById(personIds);
In your service:
public void deleteManyById(List<Integer> ids) {
personRepository.deleteByIdIn(ids);
}
In your repo (assuming it's a spring JpaRepository):
void deleteByIdIn(List<Integer> ids);
Just be aware of the fact that dbs have a limit in the number of parameters you can pass in a IN condition
I have two columns in a table called Folder and Parent. And in my service I have a method called FolderService.getAllParents(s) - where (s) is a string. However a folder can also be in the parent column and I m trying to do a recursive formula to get all the folders and their respective parents.
For example Documents (parent) and Film (folder) also Film (parent) and Reviews(folder) ,Reviews(parent) and name(folder) and so on. I want to get all the parents and folder according to the string searched. Here's the recursive formula that i m stuck on:
public ArrayList<String> getParents(String s){
ArrayList<String> mn = new ArrayList<String>();
for(FolderManager y:FolderService.getAllParents(s)){
if(y.getParent().isEmpty()){
mn.add(y.getFolder());
return mn;
}else{
return getParents(y.getFolder());
}
}
return mn;
}
I m not getting any errors but I m only getting till
Documents (parent) - Film (folder) Film (parent) - Reviews(folder)
First of all, if I understood you correctly, your FolderService.getAllParents() will always return one object (because one child can have only one parent, isn't it?).
Then I don't think recursion is needed here. It can be done with a simple loop:
List<String> getParents(FolderManager fm) {
List<String> parents = new ArrayList<>();
while (fm.getParent() != null) {
parents.add(fm.getParent().getFolder());
fm = fm.getParent();
}
return parents;
}