Java streams - getting a map from list of maps - java

I have a class University that contains (among other things) a list of semesters and a method:
public Map<String,Integer> gradesMap(Student s,Semester s)
that should return a map of grades for a given student in a given semester.
Classes Semester and Course look something like this:
public class Semester{
...
private List<Course> courses;
public List<Courses> getCourses(){return courses;}
...
}
public class Course{
...
String courseName;
private Map<Student,Integer> courseGrades;
public Map<Student,Integer> getCourseGrades(){return courseGrades;}
public String getCourseName(){return courseName;}
...
}
I tried writing something like:
Map<String,Integer> grades=semester.getCourses().stream().
forEach(c->c.getCourseGrades()).collect(Collectors.toMap(key,value));
but I'm not sure how to fetch the key and value for my map. Any suggestions would be appreciated.
Edit: Output map should contain a Course name and a grade.

You can get the course grade from the Course Grade map by the student and collect grade as the map's value and course name as the key.
Map<String,Integer> gradesMap =
semester.getCourses()
.stream()
.collect(Collectors.toMap(c -> c.getName(),
c -> c.getCourseGrades().get(studentObj)));
Note: Make sure you defined equals() and hashCode() for Student

Related

Stream - filter based on hashMap value

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

Get element from list that contains another list

I have this configuration with java:
Student {
private List<Note> notes;
}
Note {
private int value;
// Constructor - getters - setters
}
School {
Private List<Student> students;
// Constructor - getters - setters
}
I want the following behavior:
Students :
S1 : note1(value=10), note2(value=16)
S2 : note1(value=7), note2(value=18), note3(value=2)
S3 : note1(value=19)
I want to manage an object with a list of schools as:
Manage {
private List<School> schools;
}
And I want to get the school who has the student with the higher note.
In this example: the result would be S3 because we have one student with the higher note 19.
How can I achieve this behavior using Java Stream?
You can create a Stream<Map.Entry<School,Student>> of all the pairs of Schools and Students, and then find the entry having the Student with the max value.
For that purpose I suggest adding to Student class a getMaxValue() method that would return the max value of all the Students Notes.
Optional<School> school =
schools.stream()
.flatMap(sc -> sc.getStudents()
.stream()
.map(st -> new SimpleEntry<>(sc,st)))
.collect(Collectors.maxBy(Comparator.comparing(e -> e.getValue().getMaxValue())))
.map(Map.Entry::getKey);

Updating a hashmap within a hashmap with a class

Sorry about the title really struggling to name this. Lets see how the question goes.....
In java I have a class called book which contains the following code. Where name is the book name, the hashMap string is the critics name and the hashMap Integer is the critic score.
public class book {
private String name;
private HashMap<String, Integer> results = new HashMap<String, Integer>();
}
I then have another class called bookRecord which contains the following code.
private HashMap<Integer, Book> bookRecord = new HashMap<Integer, Book>();
I have written a UI that allows me to enter a new book and save that book into the bookRecord.
The Integer in bookRecord is an ID for the book.
I have written a query to search by the ID and return the name of the book. Now I want to be able to update the book if a new critic and score is available.
I have been playing with a for each loop to find the right key in the book record and then if the key is found another for each loop but I have confused myself.
Any advice?
You have two options:
First option you make the results HashMap public and can achieve something like this:
Book uBook = records.get(BOOK_ID_YOU_WANT_TO_UPDATE);
if (uBook != null) {
uBook.results.put("New critic name", Critic_SCORE);
}
Seconds option is to leave the results private but add a new method addCriticScore(String criticName, Integer score):
// In your class that updates the book
Book uBook = records.get(BOOK_ID_YOU_WANT_TO_UPDATE);
// In the Book class
public void addCriticScore(String criticName, Integer score) {
this.results.put(criticName, score);
}
P.S: Don't forget to rename your class book to Book. In java the class name is with capitals

How to write my own comparator class in java?

I didn't find proper solution for the below scenario. I have employee names and location. In each location many employees can work.
Example: assume that employee names are unique so I consider it as a key and value as location.
TreeMap<String,String> t=new TreeMap<String,String>();
t.put(mike, Houston);
t.put(arian, Houston);
t.put(John, Atlanta);
Well my scenario is i have to write my own comparator where location is sorted first and when there are multiple locations of same name then they need to be sorted by employees. Any kind of help is appreciated.
you need a structure, and compareTo:
public class EmpLoc implements Comparable<EmpLoc> {
String employee;
String location;
public EmpLoc (String _employee, String _location)
{
employee=_employee;
location=_location;
}
#Override
public int compareTo(EmpLoc other)
{
int last = this.location.compareTo(other.location);
return last == 0 ? this.employee.compareTo(other.employee) : last;
}
}
The problem is in your data structure. TreeMap ensure your keys are always sorted in an order, but your key doesn't have full information you need to sort. Instead what you need is probably
TreeSet<Employee> employees = new TreeSet<>(employeeComparator);
where Employee is:
public class Employee {
private String name;
private String location;
/* getters & setters omitted */
}
Now you can create a comparator for Employee
You can use similar structure:
Map<String, List<String>> map = new TreeMap<>(<your_own_comparator_for_locations_or_default_one>);
This is Multimap, and this is implementation by conventional means, but also there are third-party implementation, e.g. Guava. Guava has some sorted, synchronized and immutable implementations of multimaps, you can use them by default or to see how to do some things.
You can put values like below:
public void putEmployees(String location, String employee) {
List<String> employees = map.get(location);
if (employee == null) {
employees = new ArrayList<>();
}
employees.add(employee);
Collections.sort(employees, <your_own_comparator_for_employees_or_default_one>);
map.put(location, employees);
}

How to work with maps in JPQL?

Here is the overview of a problem..
public class Student
{
private Map<Test,MarkWrapper> marks;
...
}
public class MarkWrapper
{
private List<Mark> marks;
...
}
public class Test
{
private String name;
private Date date;
..
}
public class Mark
{
private int mark;
private int total;
private float average;
...
}
Now, I would like to retrieve students who got 90 marks. Rather than changing the architecture by removing the map, how to solve this problem?
In the Map class there is no getter method to get the values in it. If this can't be achieved then how would i do it with Criteria API?
Thank you.
I would like to retrieve students who got 90 marks
If you want to retrieve student whose marks is exact 90 then make your map key as 90 and add value as student.
Example:
Pun on map to those students who got exact 90 marks by iterating studentList
Map<Integer,Student> studentMap=new HashMap<Integer, Student>();
List<Student> studentList= <yourStudentList>;
for(Student student : studentList){
if(student.getMarks() == 90){
studentMap.put(90,student);
}
}
Edit:
I assume you have entity class name as Student and have property marks and so on.
Using JPQL:
You can do to get students list from database either
String jpql="Select s from Student s where s.marks =:marks ";
Query query=em.createQuery(jpql);
query.setParameter("marks", 90);
List<Student> studentList=query.getResultList();//returns all students who got marks 90
or
String jpql="Select s from Student s ";
Query query=em.createQuery(jpql);
List<Student> studentList1=query.getResultList();//returns all students
and you can put in map to all students who got 90 marks by iterating studentList1 like I said earlier
An example JPQL query string:
SELECT s FROM Student s JOIN s.marks sm
WHERE VALUE(sm) IN (
SELECT mw FROM MarkWrapper mw JOIN mw.marks mwm
WHERE mwm.mark = :value
)
where :value is query parameter (i.e. 90)

Categories