comparing objects - java

I have two student objects.
class Student{
int physics;
int english;
int chemistry;
}
I need to compare Student A marks in each subject with Student B's marks in all the subjects.
A marks in physics needs to be compared with B's marks in physics, english, chemistry.
Similarly A's english with all the three of B.
if there is atleast one match say A's chemistry marks are equal to B's english marks,
then stop execution and return false.
My logic is
if(a.getPhysics==b.getPhysics || a.getPhysics==b.getEnglish || a.phy==b.chem || ...){
return false;
}
Is this better or any other good logic ??????

Well, you will have to make O(n^2) comparisons in any case, the question is how clean the code will be.
What you are suggesting now is good for 6 boolean comparisons, what if you have 30 subjects? Will you maintain the hundredss of comparisons you need to make?
Keeping it simple, I would prefer keeping the grades in a List or a Map and then do a nested iteration:
for (int gradeA : thisStudent.getGrades()) {
for (int gradeB : otherStudent.getGrades()) {
if (gradeA == gradeB) return false;
}
}
return true;
Of course this code needs to be adapted to your scenario (different iteration on List vs. Map, optimization by not checking each grade every time, extracting a method out of this, etc...)

A little improvment would be to create a method in the Student class to do it.

Those attributes (the courses physics, english, ...) shouldn't be in the Student class. A better option would be to create a CourseModel where your store all your Courses and keep track of all the Students who are enrolled in a course. From your CourseModel, you can query for a particular Student and get all their courses back (as a array/collection). When you have two collections/arrays, simply create a nested for-statement to compare all of them.

Use HashSets:
Set<Integer> aMarks = new HashSet<Integer>();
Set<Integer> bMarks = new HashSet<Integer>();
Collections.addAll(aMarks, 2, 3, 9);
Collections.addAll(bMarks, 4, 2, 2);
boolean check = Collections.disjoint(aMarks, bMarks);
return check;
The values are just for test. You can change Collections.addAll(...) with a new method Student.getMarksAsSet()

You could add the ability for Student to return its marks as a set:
public class Student {
private int physics;
private int english;
private int chemistry;
public Student(int physics, int english, int chemistry) {
this.physics = physics;
this.english = english;
this.chemistry = chemistry;
}
public Set<Integer> marks() {
return new HashSet<Integer>(Arrays.asList(physics, english, chemistry));
}
}
Then, when are trying to determine if two students match, all you need to see is whether their two respective sets of marks are disjoint, as StudentMatcher does:
public class StudentMatcher {
public boolean matches(Student student1, Student student2) {
Set<Integer> studentMarks1 = student1.marks();
Set<Integer> studentMarks2 = student2.marks();
return haveIntersection(studentMarks1, studentMarks2);
}
private boolean haveIntersection(Set<Integer> studentMarks1, Set<Integer> studentMarks2) {
return studentMarks1.removeAll(studentMarks2);
}
}
And here is a unit test to verify it works:
public class StudentMatcherTest {
#Test
public void matches() {
StudentMatcher matcher = new StudentMatcher();
Student student1 = new Student(34, 45, 66);
Student student2 = new Student(99, 55, 34);
Student student3 = new Student(11, 22, 33);
assertTrue("Should match", matcher.matches(student1, student2));
assertFalse("Should not match", matcher.matches(student1, student3));
}
}
There is more that can be done to make this better, but I'm assuming your code is more complicated than what you've posted, so hopefully this is enough to get you on a better path.

If the marks are in a small range (A-F rather than percent), and you need to compare marks in many subjects rather than the three you give, populate an array of boolean values to hold whether the first student has the given mark, then for the second check whether the array has a value set. That would be O(N+M) where N is the number of subjects and M the number of possible grades.
If you only have the three subjects, having the tests hard coded isn't that bad - you'll need six lines to get the marks from each anyway.

Related

HashMap with multiple values get.key() error

import java.util.*;
class Student{
final String name;
final String gender;
public String number;
static Map<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
static ArrayList<String> nameandNumber = new ArrayList<>();
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
nameandNumber.add(this.name);
nameandNumber.add(this.number);
hm.put(this.gender,nameandNumber);
}
void getPersonByGender() {
String[] Liste = hm.get("Man").toArray(new String[0]);
for (int i = 0; i < Liste.length - 1; i += 2) {
System.out.println(Liste[i] + "\t<------>\t" + Liste[i + 1]);
}
}
}
hello guys i am creating a class and this class will return me 10 student information which I will give (according to the difference between men and women). When i try to use getPersonByGender's function this function gives me all students.
static ArrayList<String> nameandNumber = new ArrayList<>();
new is useful: Count the amount of times your code ever invokes new ArrayList. It's a fairly easy answer: Once. For your entire program.
If you only call new once, that means there is only one list. In the whole system.
No wonder then: This:
nameandNumber.add(this.number);
is called 10 times (because 10 students) for a single 'run' of your app. Thus, that one list you have must therefore have all these numbers added together - that's why you see all the data.
Your code is, unfortunately, layers of bad design decisions.
You can 'fix' the problem (but it'll still be fragile code that is hard to read), or you can 'fix' the design (which is more work).
Fix the problem
Instead of 1 list shared by all students which obviously can't work, you want to call new ArrayList for each student. Get rid of that static single arraylist, and instead make one every time:
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
var nameandNumber = new ArrayList<String>();
nameandNumber.add(this.name);
nameandNumber.add(this.number);
hm.put(this.gender, nameandNumber);
}
Now you call new ArrayList the right number of times throughout one run of your program, for example.
But you're still in trouble here - because you decided to use a List to represent a single idea (a student), you have confused yourself: A given gender maps to multiple students. Given that a single student is represented by a List<String>, multiple students would be a List<List<String>> and, oof, this is getting real complex, real fast.
We could plug away at fixing this further but let's take a step back and fix your design instead!
Fix the design
More generally, java's typing system is highly nominal: Types have names, and the more descriptive the name, the better.
Student is a far better, clearer name than List<String>. How is a reader of your code supposed to know that those List<String> objects specifically are intended to contain precisely 2 strings, the first of which is the student's name, the second of which is their student ID number? It doesn't say that anywhere. If you mess it up you get no compiler errors of any kind.
You have a type, right there, that properly describes that concept: Student!
So why not replace this bad code:
static Map<String, ArrayList<List<String>>> hm = new HashMap<String, ArrayList<List<String>>>();
With this much improved code:
static Map<String, List<Student>> genderMap = new HashMap<>();
It has all sorts of improvements:
It has a proper name. hm doesn't mean anything.
It uses <> to be shorter - you don't need to repeat that stuff.
It codes to the principle (List) instead of the concrete class.
It uses nominal types - this maps a gender string to a list of students. And the code reads the same way, that's good.
Putting it together:
class Student {
final String name;
final String gender;
final String number;
static Map<String, List<Student>> genderMap = new HashMap<>();
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
List<Student> studentsForThisGender = genderMap.get(gender);
if (studentsForThisGender == null) {
// There is no list yet; we need to make one.
genderMap.put(gender, studentsForThisGender = new ArrayList<>());
}
studentsForThisGender.add(this);
}
static void getPersonByGender() {
Student[] liste = genderMap.get("Man").toArray(new Student[0]);
for (Student student : liste) {
System.out.println(student.name + "\t<------>\t" + student.number);
}
}
}
Note:
getPersonByGender is now static - that's a thing you do to the concept of 'students' not any particula student.
Instead of this nebulous 'print list[0] - you just sorta have to know that's the name', we print student.name which documents itself.
We fixed the problem where you were list-confused.
If you get a little further along your java studies, that 'get the list of students for a given gender, and make a new list if neccessary' can be put more succintly. The last 4 lines can be reduced to:
genderMap.computeIfAbsent(gender, () -> new ArrayList<>()).add(this);
But I bet that syntax, and what is happening there, hasn't been covered yet in your java course.

Access elements of arraylist of arraylist of arraylist?

I am new to Java, and I am currently using BlueJ for a project. I am having troubles accessing the objects inside an ArrayList of an ArrayList of such objects. Say I have a Student object:
public class Student
{
private String homeAddress;
private String monthBorn;
private String yearBorn;
private int;
public Student(String homeAddress, String monthBorn, String yearBorn,
int finalGrade)
{
this.homeAddress = homeAddress;
this.monthBorn = monthBorn;
this.yearBorn = yearBorn;
this.finalGrade = finalGrade;
}
}
And then methods to get address, month, year and grade. Then I have a class Class, which is an ArralyList of Student objects:
public class Classroom
{
private String classroom;
private ArrayList<Student> listOfStudents;
public Classroom (String classroom)
{
this.classroom = classroom;
listOfStudents = new ArrayList<Student>();
}
}
And this class includes methods to add Student objects, to list all the students in the class (listAllStudentsInClassroom) which returns an ArrayList of Student, to find the Student with the highest grade in the class (getHighestGradeStudent), and to a list of students with grades higher than a certain amount.
Finally, the class School, which is an ArrayList of Classroom:
public class School
{
private ArrayList<Classroom> school;
public School()
{
school = new ArrayList<Classroom>();
}
}
This includes methods to add a class object, and it should include methods to return the Student with the highest grade ever and a list of students from all classes with grades higher than a certain one. However, I can only get the methods to iterate through only the first class added! Here is the code for getHighestGradeStudentEver so far:
public Student getHighestGradeStudentEver ()
{
Student s = school.get(0).getHighestGradeStudent();
int highestGrade = school.get(0).listAllStudentsInClassroom().get(0).getFinalGrade();
for(int i =1; i< school.size(); i++){
int highestGrade = school.get(i).listAllStudentsInClassroom().get(i).getFinalGrade();
if(value > (highestValue)){
highestValue = value;
s = school.get(i).getHighestGradeStudent();
}
}
return s;
}
This only returns the student with the highest grade from the first classroom object added to School. What am I doing wrong? Sorry for the long question, I tried to be as clear as possible!
If you can already get the highest graded student in a class, you can get that for all the classes, and find the highest grade out of all of those.
// find the highest grade in each class
ArrayList<Student> highestInEachClass = new ArrayList<>();
for (Classroom classroom : school) {
highestInEachClass.add(classroom.getHighestGradeStudent());
}
// find the highest grade overall
Student highestGradeStudent = highestInEachClass.get(0);
for (Student student : highestInEachClass) {
if (highestGradeStudent.getFinalGrade() < student.getFinalGrade()) {
highestGradeStudent = student;
}
}
return highestGradeStudent;
Alternatively, use Stream:
return school.stream().flatMap(x -> x.getListOfStudents().stream())
.sorted(Comparator.comparing(Student::getFinalGrade).reversed())
.findFirst().orElse(null);
As I understand your question, you already have a function Classroom.getHighestGradeStudent() which gives you the best student of that class. You also have a way to get the grade of a given student, since the Student object contains .finalGrade.
You want to loop through all classrooms in the school, and find the student with the highest grade.
So you have your for loop, which iterates over the classrooms. And for every classroom, you get some arbitrary student's final grade:
int highestGrade = school.get(i).listAllStudentsInClassroom().get(i).getFinalGrade();
^
This is likely not what you want. Instead, you want the best student's grade from that classroom. For that, you should instead use
int highestGrade = school.get(i).getHighestGradeStudent().getFinalGrade();
(If my assumption is wrong and you do not have a function getHighestGradeStudent() of a given classroom, you would need to loop over the result of listAllStudentsInClassroom() (or store that list sorted))
Then, you can continue with your code as you're doing, by updating the stored best student s if the best student of the current classroom is better than what you previously had in s.
But make sure you use either highestGrade or highestValue, not both of them. As your code stands, I don't see highestValue defined anywhere.
Note, that it's possible to make this code more efficient, if you only search for the best student in a given class once. I would do
Student bestOfClassroom = school.get(i).getHighestGradeStudent();
int highestGrade = bestOfClassroom.getFinalGrade();
so you already have your student to store in s by simply doing s = bestOfClassroom instead of searching through the whole list again.
But this is an optimization that should not be relevant for the Correctness of your program.

Sorting Student names

im having a trouble finishing my work because i dont know how to do the sorting of names together with the grade in ascending and descending. i hope you guys can help.
import java.util.Scanner;
public class SortingStudents {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.print("Enter the number of students: ");
int capacity = s.nextInt();
String [] name = new String[capacity];
Double [] grade = new Double[capacity];
for(int i = 0; i < capacity; i++) {
System.out.print("student's name: ");
name [i] = s.next();
System.out.print("grade: ");
grade [i] = s.nextDouble();
}
Scanner input = new Scanner(System.in);
System.out.println("Type A for Ascending D for Descending:");
char a=input.nextLine().charAt(0);
if(a == 'A' || a == 'a'){
for(int i = 0; i<grade.length;i++){
System.out.println(grade[i]+"\t" +grade[i]);
}
}
}
You are using Java, which is an object-oriented programming language. This means you can think about your problem in terms of classes which represent state in your problem domain and have behavior (methods) which manipulate this state.
In this case your code shows several responsabilities, for which you could create useful classes and/or methods:
entering data via the command line (number of students, name and grade and desired sorting direction)
a registry of students with a fixed size
sorting the student registry in the desired direction
Class names that come to mind are: DataEntry, Student, StudentRegistry. For sorting the students in different ways the standard approach is creating a Comparator class, see below.
The classes could look roughly like this:
public class Student {
private String name;
private Double grade;
// getters and setters ommitted for brevity
}
The registry:
public class StudentRegistry {
// it's easier to use a List because you are adding students one by one
private List<Student> students;
public StudentRegistry(int capacity) {
// ...constructor code initializes an instance of StudentRegistry
}
public void addStudent(Student student) {
// add a student to the list
}
public Student[] getStudents(Comparator<Student> comparator) {
// sort the list using the comparator and Collections.sort()
// use List.toArray() to convert the List to an array
// alternatively (java 8) return a Stream of Students
// or return an unmodifiable List (using Collections.unmodifiableList())
// you don't want to expose your modifiable internal List via getters
}
}
A comparator which can sort Ascending or Descending. You could consider adding the capability to sort either by grades or by names, that would be quite easy.
public class StudentComparator implements Comparator<Student> {
public enum Direction {
ASCENDING, DESCENDING
}
// optional addition:
//public enum Field{
// NAME, GRADE
//}
// if used, add Field parameter to the constructor
public StudentComparator(Direction direction) {
// initialize instance
}
#Override
public int compare(Student a, Student b) {
// implement Comprator method, see JavaDoc
}
#Override
public boolean equals(Object o) {
// implement equals, see JavaDoc
}
}
Class for letting the user enter data:
public class DataEntry {
public int getNumberOfStudents() {
// ...
}
public Student getStudent() {
// ...
}
public StudentComparator.Direction getSortingDirection() {
// ...
}
}
And a main class:
public class Main {
public static void main(String[] args) {
DataEntry dataEntry = new DataEntry();
int capacity = dataEntry.getCapacity();
StudentRegistry studentRegistry = new StudentRegistry(capacity);
for(int i=0; i<= capacity; i++) {
studentRegistry.addStudent(dataEntry.getStudent());
}
StudentComparator comparator = new StudentComparator(dataEntry.getSortingDirection());
Student[] students = studentRegsitry.getStudents(comparator);
}
}
This approach separates concerns of your problem in separate classes and methods, making the code much easier to understand, debug and test.
For example, to test the Main class you could set up a mock DataEntry class which provides predetermined values to your test. See the topic of unit testing.
If you want to do it your way without changing how the arrays are stored separately. You will not be able to use the Arrays.sort() method to sort your grades because that will not take the name array into account and you will lose the link between them so that the grades will no longer match with the names.
You could very quickly code up your own bubble sorter to quickly sort the array, and then you could use your loop to affect the name array at the same time. But if you don't want to code your own sorter, you will need to organise your grades and names array so that can be treated as one unit i.e in a separate class.
If you choose to code your own sorter then here is a great link to learn that: http://www.java-examples.com/java-bubble-sort-example
If you choose to change the way that you store the grades and names, here is how you can use the Arrays.sort() method:
http://www.homeandlearn.co.uk/java/sorting_arrays.html
You could just concatenate the name and grade and sort them, that way it could be a lot easier.

Trouble passing parameters to an object java

I'm having trouble passing parameters to specify a specific attributes to an object to change that attribute. I'm a little new to objects and how they work. I need to use objects for this, as it's an assignment.
The purpose of this program is to basically store grades in an electronic gradebook. However, I have an object filled with names and quiz grades of hypothetical students. I need to create the setQuiz() method to grab a specified quiz value of the specified student, and then change it to specified grade. Here's what I have so far.
public class Student extends TestStudent
{
public String name;
public int q1, q2, q3, q4, q5;
public Student(String cName, int cq1, int cq2, int cq3, int cq4, int cq5 )
{
name = cName;
q1 = cq1;
q2 = cq2;
q3 = cq3;
q4 = cq4;
q5 = cq5;
}
public void setQuiz(int grade, int quiz, int student)
{
super.studentAr[student].{quiz?} = grade;
//Basically, I am not sure how to reference the exact grade I need
//to access, thus the question mark.
}
}
So what do I do, StackOverflow? Do I need to totally rethink my structure here? Is it bad practice? Or am I on the right track and just missing something vital?
Thanks a bunch in advance.
With this structure, the only way you can access each quiz is by referring to the quiz specifically. You can't just pass in a quiz number. You must do: student.q1, student.q2 etc.
Another thing that doesn't make sense is that you are passing the index of student into your setQuiz method. This method already belongs to a student object and there is only one object it can affect, which is itself. You need to choose which student to call the setQuiz method on in whatever location that you're calling it from.
EDIT: Changed the constructor to initialize all grades.
A better way to create your class would be as follows:
public class Student extends TestStudent
{
public String m_name;
public int[] m_grades;
public Student(String cName, int q1, int q2, int q3, int q4, int q5)
{
m_grades = new int[5];
m_grades[0] = q1;
m_grades[1] = q2;
m_grades[2] = q3;
m_grades[3] = q4;
m_grades[4] = q5;
}
public void setQuiz(int grade, int quiz, int student)
{
if (quiz > 0 && quiz <= num_quizzes && grade >= 0 && grade < MAX_GRADE) //Always check yout inputs!!
{
super.studentAr[student].m_grades[quiz-1] = grade;
}
}
}
Then you can use it form outside:
Student josh = new Student("Josh", 10, 20, 30, 40, 50);
josh.setQuiz(51, 1);
josh.setQuiz(60, 2);
josh.setQuiz(20, 3);
Or however you'd like to use it.. Up to you!
Another note. When you call super, in this case you are accessing an array on TestStudent class. I have not seen this class and I do not know if it has a studentAr[] member, but it shouldn't!
This array should be in another class that isn't inherited. It oculd be called StudentList or something like that.
EDIT: In response to your comment. If you need the grades to be accessed exactly as you stated, the only solution that comes to mind is:
public void setQuiz(int grade, int quiz, int student)
{
if (quiz == 1)
super.studentAr[student].q1 = grade;
if (quiz == 2)
super.studentAr[student].q2 = grade;
if (quiz == 3)
super.studentAr[student].q3 = grade;
if (quiz == 4)
super.studentAr[student].q4 = grade;
if (quiz == 5)
super.studentAr[student].q5 = grade;
}
Do note, however, that this is a fairly bad way of doing things. When I was handed assignments like this, I usually tried to do them right, impressed the teacher and walked away with an A+. Of course not all teachers appreciate this, so to stay safe you may want to prepare multiple answers - the specific one and the good one.
If you want to access on of a set of things by an index, you want to use an array, so you can use something like q[quiz].

Find Range in which value lies in Java

Suppose, I have an unsorted array of ranges.
For e.g
class CreditRange{
long credits;
int id;
}
Now I want to find, given credit count value belongs to which one of the CreditRange.
Possible Set<CreditRange> of values can be
CreditRange :{id:1,credits:0}
CreditRange :{id:2,credits:100}
CreditRange :{id:3,credits:500}
CreditRange :{id:4,credits:250}
Case 1 : Now when user enters Credits = 50, this range comparator should give
answer as
CreditRange :{id:1,credits:0}
Case 2 : Now when user enters Credits = 300, this range comparator should give
answer as
CreditRange :{id:4,credits:250}
Case 3 : Now when user enters Credits = 600, this range comparator should give
answer as
CreditRange :{id:3,credits:500}
We can assume the ranges array takes ~1M and fits the memory. I am looking for an easy algorithm, which uses only standard JDK collections without any 3d-party libraries and special data structures, but works reasonably fast.
What would you suggest?
I guess, it's not the range you are talking about. Rather you want the largest element that is less than your passed element.
You can follow the below steps to solve the problem:
First implement a Comparator for your class, which compares on the basis of credits
Then, use a TreeSet, passing an instance of that comparator to it's constructor. It will sort the item inside it, as per the comparator.
Then use TreeSet#floor(E) method to get the greatest element which is less than E, as per the comparator. Of course, you have to create an object of CreditRange to search. You can't just search for 300.
Demo code:
NavigableSet<Integer> set = new TreeSet<>();
set.add(0); set.add(100);
set.add(250); set.add(500);
System.out.println(set.floor(50)); // 0
System.out.println(set.floor(300)); // 250
And please rename your class. It is not depicting the range in any manner. It should perhaps be better named as CreditBound as specified by Jon Skeet in comments.
As mentioned by Rohit, one easy method is to use TreeSet floor (another is to implement a a modified variant of Binary search. this is a more complete answer:
package test;
import java.util.TreeSet;
class CreditRange implements Comparable<CreditRange> {
long credits;
int id;
public CreditRange(int id, long credits) {
this.id = id;
this.credits = credits;
}
public CreditRange(long credits) {
this.id = -1;
this.credits = credits;
}
#Override
public int compareTo(CreditRange o) {
return credits < o.credits ? -1 : credits > o.credits ? 1 : 0;
}
#Override
public String toString() {
return "id:" + id + ", credits:" + credits;
}
}
public class Test {
public static void main(String[] args) {
TreeSet<CreditRange> set = new TreeSet<>();
set.add(new CreditRange(1, 0));
set.add(new CreditRange(2, 100));
set.add(new CreditRange(3, 500));
set.add(new CreditRange(4, 250));
System.out.println(set.floor(new CreditRange(50)));
System.out.println(set.floor(new CreditRange(300)));
System.out.println(set.floor(new CreditRange(600)));
}
}

Categories