my goal is to associate all team members to teamleaders but hierarchically. eg : Bill is a top team leader, John and Alice are one level down, Mary is one level down to John and Alice,etc...
Team leader Team members
========= ==========
Bob ""
Marry Bob
John Marry
Alice Marry
Bill John,Alice
So I use
final static SetMultimap<String, String> teamMap = HashMultimap
.create();
and I want to get for each Team leader the full team members list he leads
eg : For Alice as a Team Leader, her full team members list is Marry, Bob.
For Bill as a Team Leader, his full team members list is John, Alice, Marry, Bob
Since recursivity seems to answer to my problem, I use this
public static List<String> getTeamMembers(String teamLeader) {
List<String> teamMembers = teamMembersMap.get(teamLeader);
fullTeamMembers.addAll(teamMembers);
for (String teamMember : teamMembers) {
if (teamMember.equalsIgnoreCase("")) {
**// if teamMember is John go to Marry then Bob and return recursively
// without treating Alice loop!**
return fullTeamMembers;
} else {
return getTeamMembers(teamMember);
}
}
**// compiler error otherwise force to return something here
// maybe in case teamMembers is empty****
return null;
}
And my call
for (String teamLeader : teamMembersMap.keySet()) {
System.out.println("********************************");
System.out.println("**************teamLeader :" + teamLeader);
fullTeamMembers.clear();
List<String> fullTeamMembers = getTeamMembers(teamLeader);
teamMembersMap.putAll(teamLeader, fullTeamMembers);
}
But I am stucked on the two point in comment in the code : the loop issue and the return compiler error. Any Help is welcomed to solve this problem in a better way.
Of course it tells you to put a return statement there. Your teamMembers could be empty or even NULL. Other than a few missing null/empty checks, there is nothing wrong with your code.
If you really want an improvement and null safety, here's my suggestion:
public static List<String> getTeamMembers(final String teamLeader)
{
final List<String> result = new ArrayList<>(); // fullTeamMembers
final List<String> teamMembers = teamMembersMap.get(teamLeader); // Can be NULL or empty
if (teamMembers == null || teamMembers.isEmpty())
return result; // Return empty array
// Else, carry on
result.addAll(teamMembers);
for (String teamMember : result)
if ( !teamMember.equalsIgnoreCase("") )
return getTeamMembers(teamMember); // Go recursively if not ""
// Return result from map
return result;
}
Related
I want to start from a collection of diploma projects and by using stream I want to get an arrayList of diploma project titles, from the students that have taken a course identified by courseId. They will also need to have passed the course with grade of 2 or higher.
I have this DiplomaProject class:
public class DiplomaProject{
String title;
ArrayList<Student> authors
}
Each diplomaProject can have multiple authors.
I have this Course class:
public class Course{
String courseName;
String courseId;
}
This Student class:
public class Student{
String name;
HashMap<Course, Integer> courseList;
DiplomaProject diplomaProject;
}
The grade of the course is the Integer value of courseList.
This is my current solution, but I know it does not do what I want. I can't find a way to filter based on the value of the courseList, and I do not know how I can get the the diplomaProject titles at the end of the streams (only at the top level).
public static List<String> diplomaProjectTitle(List<DiplomaProject> diplomaProjects) {
return diplomaProjects.stream()
.map(diplomaProject -> diplomaProject.authors)
.flatMap(students -> students.stream())
.filter(student -> student.courseList.keySet().equals("math1001"))
.flatMap(student -> student.courseList.keySet().stream())
.map(student -> student.courseName)
.collect(Collectors.toList());
You are losing the info on the diploma projects with the the .map functions. What you want to do is operate within the .filter() functions of the first diplomaproj stream.
Therefore
public List<String> diplomaProjectTitles(List<DiplomaProject> diplomaProjects) {
return diplomaProjects.stream()
.filter(projects -> projects.getAuthors().stream().map(Student::getCourseList)
//get the course matching this one (both name and id)
.map(c -> c.get(new Course("math101", "1")))
//check if that course has grade greater than the minimum
.anyMatch(grade -> grade>=2))
.map(DiplomaProject::getTitle)
.collect(Collectors.toList());
}
For this to work though you would need to modify your Course class. Since you are using it within a hash map as a key, and want to get it through a custom query you will need to add the hashCode() function.
public class Course {
private String courseName;
private String courseId;
#Override
public int hashCode() {
return courseName.hashCode() + courseId.hashCode();
}
#Override
public boolean equals(Object o) {
if(o instanceof Course oc) {
return oc.getCourseName().equals(this.getCourseName()) && oc.getCourseId().equals(this.getCourseId());
}
return false;
}
//getters and setters
}
In order to test it I created a simple method that prepares a test case
public void filterStudents() {
List<DiplomaProject> diplomaProjects = new ArrayList<>();
List<Course> courses = new ArrayList<>();
courses.add(new Course("math101", "1"));
courses.add(new Course("calc101", "2"));
courses.add(new Course("calc102", "3"));
List<Student> students = new ArrayList<>();
Map<Course, Integer> courseMap = Map.of(courses.get(0), 3, courses.get(1), 1);
students.add(new Student("TestSubj", courseMap));
Map<Course, Integer> courseMap2 = Map.of(courses.get(0), 1, courses.get(1), 3);
students.add(new Student("TestSubj2", courseMap2));
diplomaProjects.add(new DiplomaProject("Project1", students));
diplomaProjects.add(new DiplomaProject("Project2", List.of(students.get(1))));
log.info("Dimploma projects are " + diplomaProjectTitles(diplomaProjects));
}
this way Project 1 will have a student with math101 with grade 3 and one with grade 1, and Project2 will have a student with math101 with grade 1. As expected, the result of the filtering method is only project1
I want to get a List of diploma project titles, from the students that have taken a Course identified by the given courseId. They will also need to have passed the course with grade of 2 or higher.
In your method diplomaProjectTitle you're actually losing the access to the titles of the diploma projects at the very beginning of the stream pipe-line because the very first operation extracts authors from the stream element.
You need to need the stream to of type Stream<DiplomaProject> in order to get a list of diploma project titles as a result. Therefore, all the logic needed to filter the desired diploma project should reside in the filter() operation.
That's how it might be implemented:
public static List<String> diplomaProjectTitle(List<DiplomaProject> diplomaProjects,
String courseId,
Integer grade) {
return diplomaProjects.stream()
.filter(diplomaProject -> diplomaProject.getAuthors().stream()
.anyMatch(student ->
student.getCourseList().getOrDefault(courseId, 0) >= grade
)
)
.map(DiplomaProject::getTitle)
.toList(); // or .collect(Collectors.toList()) for JDK version earlier than 16
}
A couple of important notes:
Avoid using public fields and accessing the fields from outside the class directly rather than via getters.
Pay attention to the names of your method, variables, etc. The name courseList is confusing because it's actually not a List. This map should rather be named gradeByCourse to describe its purpose in a clear way.
Leverage abstractions - write your code against interfaces. See What does it mean to "program to an interface"?
Pay attention to the types you're working with keySet().equals("math1001") even IDE is capable to tell you that something is wrong here because Set can never be equal to a String.
A step-by-step way of thinking:
We need to filter projects based on the criteria that these have at least one author (student) who has passed a specific course (courseId) with a grade >= 2 (another filter).
dipProjects.stream().filter(p->p.getAuthors().stream().anyMatch(s->s.getCourseList().getOrDefault(courseId,0) >= grade)).map(p->p.getTitle()).collect(Collectors.toList());
Apologies, this has been done to death but I am really struggling to implement a solution to this problem and I am quite new to Java.
I need to be able to call a method which basically allows a Person object to be added to a list.
The main problem I have encountered whilst trying to implement a solution is a 'ConcurrentModificationException' which is understandable given I have been trying to update the list whilst in the middle of a for each loop.
So, I have come up with the following solution which prevents the 'ConcurrentModificationException' but my solution doesn't work properly and seems overcomplicated - see following code snippet of method:
public void addPerson(Person aPerson) {
// tempPersons list for temporarily storing Person objects
tempPersons = new ArrayList<>();
// if persons list is initially empty, add aPerson object to it
if (persons.isEmpty()) {
persons.add(aPerson);
}
// if persons list is not initially empty
if (!persons.isEmpty()) {
// loop through persons list
for (Person anotherPerson : persons) {
// if persons list anotherPerson first name is the same as the aPerson first name, print message
if (anotherPerson.getFirstName().equals(aPerson.getFirstName())) {
System.out.println("The Person " + aPerson.getFirstName() +
" is already entered in the list");
}
// otherwise add the aPerson object to the tempPersons list
else {
tempPersons.add(aPerson);
}
}
// once out of loop, add the tempPersons list to the persons list
persons.addAll(tempPersons);
// create new tempPersons2 list based on a hashset of the persons list so there are no duplicates
List<Person> tempPersons2 = new ArrayList<>(
new HashSet<>(persons));
// assign tempPersons2 list without duplicates to persons list
persons = tempPersons2;
}
}
So, if for example I call the above addPerson method 4 separate times with a mixture of unique and duplicate objects (aPerson param), the method correctly identifies that there is already an object with the same first name in there but the persons list always seems to end up with a duplicate object (first name) in there e.g. if I have the following objects:
Person1
FirstName = Bob
Person2
FirstName = Jan
Person3
FirsName = Bob
Person4
FirstName = Ann
Then I make the following method call 4 separate times:
addPerson(Person1);
addPerson(Person2);
addPerson(Person3);
addPerson(Person4);
When I call a print method, I get the following output:
The Person Bob is already entered in the list
Jan
Ann
Bob
Bob
I would expect the following:
The Person Bob is already present
Jan
Ann
Bob
Apologies for all the waffle and what is probably a really simple solution to most of you but I have been stuck on this for a couple of days.
Similar article can be found here but I am still struggling.
Adding elements to a collection during iteration
Without changing your code too much:
public void addPerson(Person person) {
// The guard is more or less a premature optimization and should be removed.
if (persons.isEmpty()) {
persons.add(person);
return;
}
for (Person anotherPerson : persons) {
if (anotherPerson.getFirstName().equals(person.getFirstName())) {
System.out.println("The Person " + person.getFirstName() +
" is already entered in the list");
return;
}
}
persons.add(person);
}
This would exit the method when a match is found, if there are no matches the person is simply added after the loop. Note the return in the first check.
Optimizations of this code could be using a Map or Set to speed up the contains check; also just using anyMatch on persons would lead to a more elegant solution.
The duplicates are caused by your for loop and it's else condition. If Bob and Jan is in your collection and you add a second Bob then Jan won't be equal to Bob and your else path is executed adding a duplicate to your final persons List.
You can use Set instead of list which will satisfy your need and implement the comparator or comparable interface for person comparison
Enviroment: Java 7
I have a hashmap 'pdf' of Aircrew details that has the following key structure:
public static Map<String, String> pdf;
PilotName JONES
PilotFirstname Jim
PilotID 12345
PilotLicense AAD987
PilotDOB 12/12/2001
PilotGender Male
PilotEthnicity
PilotAddress 123 Any Street
CopilotName SMITH
CopilotFirstname Thomas
CopilotID 987654
CopilotLicense AAB9475
CopilotAddress 456 Any Street
CopilotDOB 12/03/1987
CopilotGender Male
CopilotEthnicity European
CabinManagerSurname
CabinManagerFirstName BROWN
CabinManagerID 48573
CabinManagerDOB
CabinManagerGender
CabinManagerEthnicity
CabinManagerAddress
Hostess1Surname
Hostess1FirstName
HostessID
Hostess1DOB
Hostess1Gender
Hostess1Ethnicity
Hostess1Address 789 Any Street
Hostess2Surname EDWARDS
Hostess2FirstName Mary
HostessID 475804
Hostess2DOB 11/10/1990
Hostess2Gender Female
Hostess2Ethnicity European
Hostess2Address
Hostess3Surname
Hostess3FirstName
Hostess3ID 489282
Hostess3DOB
Hostess3Gender
Hostess3Ethnicity
Hostess3Address
NB: The field names for crew and pilots are different (Surname/Name Firstname/FirstName).
I want to test if any of certain fields are not empty then call createPerson() method.
The fields to be tested differ for Cabin Crew from Pilots.
I made this attempt but the code is ugly:
List<String> pilotRoles = ["Pilot", "Copilot"];
List<String> cabinRoles = ["CabinManager", "Hostess1", "Hostess2", "Hostess3"];
for (String role : pilotRoles) {
if ( String.isNotNullOrEmpty(pdf.get(pilotRole +"Name")) || String.isNotNullOrEmpty(pdf.get(pilotRole +"Firstname")) || String.isNotNullOrEmpty(pdf.get(pilotRole +"ID")) || String.isNotNullOrEmpty(pdf.get(pilotRole +"License"))) {
listPeople.add(createPerson(pdf, pilotRole));
for (String role : cabinRoles) {
if ( String.isNotNullOrEmpty(pdf.get(cabinRole +"Surname")) || String.isNotNullOrEmpty(pdf.get(cabinRole +"FirstName")) || String.isNotNullOrEmpty(pdf.get(cabinRole +"ID")) ) {
listPeople.add(createPerson(pdf, cabinRole));
For the above data the createPerson routine would be entered for both pilots as at least 1 of the tested fields is not null or empty.
The createPerson routine would NOT be entered for hostess1 and all of the tested fields are null or empty.
Is there a more elegant way? If so how.
I'd try something like this: In addition to your lists of roles, create an additional list that holds the names of all attributes, like "Name" etc.
Then you can then create a function to filter your roles for fields that are missing in your pdf map, like this:
private List<String> getRolesOfMissingUsers(Map<String, String> pdf, List<String> roles, List<String> attributes) {
return roles.stream().filter(role -> attributes.stream().map(attribute -> role + attribute)
.anyMatch(roleAttribute -> StringUtils.isNotBlank(pdf.get(roleAttribute))))
.collect(toList());
}
You can then use the result of this method to create your missing users. Here is an example just for your pilot rules:
for (String role : getRolesOfMissingUsers(pdf, pilotRoles, Arrays.asList("Name", "Firstname", "ID", "License"))) {
listPeople.add(createPerson(pdf, role));
}
EDIT: I noticed you're on Java 7, so you could try this instead:
for (String role : pilotRoles) {
for (String attribute : Arrays.asList("Name", "Firstname", "ID", "License")) {
if (StringUtils.isNotBlank(pdf.get(role + attribute))) {
listPeople.add(createPerson(pdf, role));
break;
}
}
}
If you extract this to a method and pass the list of attributes as a parameter, you should also be able to use this for your crew and pilot list.
I have a result set having List<Employees> sent by another application.
class Employee{
Long id;
String name;
String gender;
List<String> projects;
// Getters
// Setters
}
I need to write a method or lambda expression to filter the List using a bunch of query words (String[]) passed from the UI.
Any word in the String[] can match any variable (id, name, gender, projects). All List which have a match should be returned. part of name should also match e.g.: "john" should match List 1 and 3 in the example.
List<Employee> filter (empList, queryWords) {
// code
}
Can you point me in the right direction to achive this?
example:
List:
1. 121, john doe , male , (proj1)
2. 125, sam , female, (proj4 proj5 proj9)
3. 129, john lam , male , (proj1 proj2 proj5)
4. 143, peter pan , male , (proj4 proj8)
5. 151, linda , female, (proj8 proj7 proj3 proj11)
Search Query Words:
1. "female" "proj3"- should return only No.5
2. "proj5" - should return only No.2 and 3
3. "john" - should return No.1 and 3
4. "pan" - should return No.4
public List<Employee> filter(empList, queryWords){
List<Employee> result = new ArrayList<Employee>();
// look at each employee in the list
for(Employee employee : empList){
// look at each query string
for(String queryWord : queryWords){
// if any of the employee fields matches the query word,
// add it to our list and move to next employee
if(employee.name.equals(queryWord) ||
employee.gender.equals(queryWord) ||
employee.id.toString().equals(queryWord) ||
isQueryInList(queryWord, employee.projects)) {
// add it to your results
result.add(employee);
// quit looking at the rest of the queryWords,
// we found one, thats enough, move on to the next employee
break;
}
}
}
return result;
}
private boolean IsQueryInList(String queryWord, List<String> items){
//check each item in the list to see if it matches the queryWord
for(String item : items){
if(queryWord.equals(item)) {
return true;
}
}
//if we didn't find any item that matches, return false
return false;
}
Write a method
private boolean employeeMatchesWord(Employee employee, String word)
that returns true if at least one field of the employee matches the given word.
Then use
return empList.stream()
.filter(employee -> Arrays.stream(queryWords)
.anyMatch(word -> employeeMatchesWord(employee, word))
.collect(Collectors.toList());
You could convert the query words array to a Set, create a Set of properties from all the employee's members, and use retainAll to determine which employees have at least one of the query words:
public static List<Employee> filter (List<Employee> empList, String[] queryWords) {
Set<String> queryWordsSet = new HashSet<>(Arrays.asList(queryWords));
return empList.stream().filter(e -> {
Set<String> properties = new HashSet<>(e.getProjects());
properties.addAll
(Arrays.asList(e.getId().toString(), e.getName(), e.getGender()));
properties.retainAll(queryWordsSet);
return !properties.isEmpty();
}).collect(Collectors.toList());
}
EDIT:
As JB Nizet commented, the retainAll can be elegantly replaced with an anyMatch expression:
public static List<Employee> filter (List<Employee> empList, String[] queryWords) {
Set<String> queryWordsSet = new HashSet<>(Arrays.asList(queryWords));
return empList.stream().filter(e -> {
Set<String> properties = new HashSet<>(e.getProjects());
properties.addAll
(Arrays.asList(e.getId().toString(), e.getName(), e.getGender()));
return properties.stream().anyMatch(queryWordsSet::contains);
}).collect(Collectors.toList());
}
Imagine we are pulling data about people and their favourite foods.
The data would come to us in the format: "Name, FavFood1, FavFood2..FavFoodn".
e.g. "James, Beans, Chicken".Notice how we do not know how many foods a person will favour.
From this data we create an instance of a Person object which captures the person's name and favourite foods. After we have pulled data on every person, we want to create a spreadsheet whose columns would be: Name|Potato|Chicken|Beans|Curry etc.
All of the values to the right of the person's name will be simple boolean values representing whether or not that food was one of the person's favourites.
The problem is: we do not know in advance; all the foods that someone could possibly favour, and as such cannot just set up boolean instance variables in the Person class.
I've given this some thought, implementing sets,hash-sets and hash-maps, however every solution I think of ends up being horribly inelegant and so I've turned to the genius of stackoverflow for help on this one.
My question is: What design pattern / approach can I use to cleanly achieve the outcome I desire? Whilst this is a language-agnostic question I am programming this in Java, so if there's anything in the Java API or elsewhere built for this, do let me know.
Thanks in advance!
Try this. It generates data in csv form.
class Person {
final String name;
final Set<String> foods;
Person(String name, Set<String> foods) {
this.name = name;
this.foods = foods;
}
Stream<Boolean> getBooleans(List<String> foods) {
return foods.stream().map(food -> this.foods.contains(food));
}
#Override
public String toString() {
return "Person(" + name + ", " + foods +")";
}
}
class Test {
public static void main(String[] args) throws Exception {
List<String> data = Arrays.asList(
"James, Beans, Chicken",
"Emily, Potato, Curry",
"Clara, Beans, Curry"
);
List<String> foodNames = Arrays.asList(
"Potato", "Chicken", "Beans", "Curry"
);
Stream<Person> persons = data.stream().map(d -> {
String[] split = d.split(",");
for(int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
}
String name = split[0];
Set<String> foods = Stream.of(split).skip(1).collect(Collectors.toSet());
return new Person(name, foods);
});
Stream<String> csvData = persons.map(p ->
p.name + ", " + p.getBooleans(foodNames)
.map(b -> b.toString())
.collect(Collectors.joining(", "))
);
csvData.forEach(System.out::println);
}
}
First of all, I highly recommend that whatever you do it in a separate class with methods like addFavoriteFood(String food) and boolean isFavoriteFood(String food) getFavorites(String food).
Personally I think the implementation of this class should contain both an instance HashSet (to hold the foods this person likes) and a SortedSet that is common to all the foods that can contain a list of ALL foods. (See notes at end)
Add would add it to both sets, getFavorites would return those in the first Hash set.
Hmm, it may also need a static getAllFavorites() method to return the SortedSet
Since your FavoiteFoods class knows the master list AND the person's favorites, you could even have it do most of the work by having a getFormattedRow() and static getFormattedHeaderRow() method. then your implementaiton is just:
System.out.println(FavoriteFoods.getFormattedHeaderRow());
for(Person person:people)
System.out.println(person.favoriteFood.getFormattedRow());
Again, the best thing here is that you can just use the Simplest Thing That Could Possibly Work for your implementation and re-do it later if need be since, being isolated in another class, it doesn't infect all your code with nasty implementation-specific sets, classes, booleans, etc.
Notes about the master list: This master list could naively be implemented as a Static but that's a bad idea--optimally the same masterList SortedSet would be passed into each instance on construction. Also since it is shared among all instances and is mutable it brings in issues if your solution is threaded!
What is so inelegant about this pseudocode?
Set<String> allFoods = new TreeSet<String>();
List<Person> allPersons = new ArrayList<Person>();
while (hasMorePersons()) {
Person person = getNextPerson();
allPersons.add(person);
allFoods.addAll(person.getFoods());
}
spreadSheet.writeHeader("Name", allFoods);
for (Person person : allPersons) {
spreadSheet.writeName(person.getName());
for (String food : allFoods) {
// assume getFoods() return a Set<String>,
// not necessarily ordered (could be a HashSet)
boolean yourBooleanHere = person.getFoods().contains(food);
spreadSheet.writeBoolean(yourBooleanHere);
}
spreadSheet.nextLine();
}
If you need a table of booleans or whatever else, you can easily store them anywhere you want during the second loop.
Note: TreeSet orders foods according to the natural order (that is, alphabetically). To output them in the order they are encountered, use a LinkedHashSet instead.