I have a class say Student that have a field marks of type Double. I am creating a list of Student objects. Each student object may have marks field set to null or set with same value in different student objects. I have a problem where I want to return a single student object from this list based on below conditions:
when all students have same marks then return null.
else return student that have highest marks.
I wonder if there is a better approach using java stream api to get it done. Thank you in advance.
Student class:
public class Student {
private Double marks;
public Double getMarks() {
return marks;
}
public void setMarks(Double marks) {
this.marks = marks;
}
#Override
public String toString() {
return "Student [marks=" + marks + "]";
}
public Student(Double marks) {
super();
this.marks = marks;
}
}
Using Streams, you can do it while collecting to TreeMap and verifying the lastEntry as in:
private Student highestMarkUniqueStudent(List<Student> studentList) {
if(studentList.size() == 0) return null;
if(studentList.size() == 1) return studentList.get(0);
TreeMap<Integer, List<Student>> map = new TreeMap<>(studentList.stream()
.collect(Collectors.groupingBy(Student::getMarks)));
List<Student> highestMarkStudents = map.lastEntry().getValue();
// only one highest or all same marks or more than one with highest mark
return highestMarkStudents.size() == 1 ? highestMarkStudents.get(0) : null;
}
You can do like this:
Indeed by using TreeMap you have the highest marks in the first entry. so by checking its value, you can return desire result. if all marks have the same value so the first entry has to value more than one and if you have two or more highest marks then the first entry has still more than one student object in the list.
TreeMap<Integer, List<Student>> map = list.stream()
.collect(Collectors.groupingBy(
Student::getMarks,
() -> new TreeMap<Integer, List<Student>>(Comparator.reverseOrder()),
Collectors.mapping(Function.identity(), Collectors.toList())));
Map.Entry<Integer, List<Student>> firstEntry = map.firstEntry();
if (firstEntry.getValue().size() <= 1) {
result = firstEntry.getValue().get(0);
}
You can work with streams, but you'd need a helper object which would be able to collect your data.
The work now either can be done with redution or with collection.
With reduction, you'd do
students.stream().reduce(
new Helper(),
(helper, student) -> new Helper(helper, student));
class Helper {
private Student bestStudent = null;
private boolean different = false;
public Helper() {
}
public Helper(Helper oldHelper, Student newStudent) {
if (oldHelper.bestStudent == null) {
bestStudent = newStudent;
} else if (student.getMark() > oldHelper.bestStudent.getMark()) {
different = true;
bestStudent = student;
} else if (student.getMark() < oldHelper.bestStudent.getMark()) {
different = true;
}
}
public Student getResult() {
return different ? bestStudent : null;
}
}
but that creates a new Helper object for every Student.
With collection, we'd do
students.stream().collect(Helper::new, Helper::accept, Helper::combine);
class Helper {
private Student bestStudent = null;
private boolean different = false;
public Helper() {
}
public void accept(Student newStudent) {
if (bestStudent == null) {
bestStudent = newStudent;
} else if (newStudent.getMark() > bestStudent.getMark()) {
different = true;
bestStudent = newStudent;
} else if (newStudent.getMark() < bestStudent.getMark()) {
different = true;
}
}
public void combine() (Helper other) {
if (bestStudent == null) {
bestStudent = other.bestStudent;
different = other.different;
} else if (other.bestStudent != null && other.bestStudent.getMark() > bestStudent.getMark()) {
different = true;
bestStudent = other.bestStudent;
} else if (other.bestStudent != null && other.bestStudent.getMark() < bestStudent.getMark()) {
different = true;
}
}
public Student getResult() {
return different ? bestStudent : null;
}
}
(Note: 1. Code not tested, 2. parts of the basic logic taken from another answer.)
You might not need streams. Sort the list highest mark firsts, if the first 2 are same, return null, otherwise return the first student.
students.sort(Comparator.comparing(Student::getMarks).reversed());
boolean firstTwoHaveSameMarks = students.get(0).getMarks().equals(students.get(1).getMarks());
return firstTwoHaveSameMarks ? null : students.get(0);
If 2 or more students have the same highest mark, it returns null, otherwise it returns the student.
Related
I have a class Item, which implements Comparable, and has a compareTo method. I want to compare the object o to other Items. I casted o to Item.
In a separate class, Inventory, I have a method for inserting items into the inventory. But I only want to insert if their product numbers are different. So I try to call the compareTo() method to compare item numbers but get a stackoverflow error.
I've tried p.compareTo(iter.next), because I want it to cycle through all of the items in the list. Sorry the formatting isn't perfect. first post here.
public class item{
public int compareTo(Object o){
result = compareTo((Product)o);
if (result < 0){
return -1;
}
else if (result == 0){
return 0;
}
else{
return 1;
}
}
}
public class ProductInventory extends Product {
private void insert(Product p){
Iterator<Product> iter = list.iterator();
if (list.size() == 0) {
list.addFirst(p);
}
while(iter.hasNext()) {
p.compareTo(iter.next());
//if (p.getNumber() != iter.next().getNumber()) {
System.out.print("RESULT:" + result);
if (result != 0) {
list.addFirst(p);
}
else {
System.out.print("DUPLICATE");
}
iter.next();
}
}
I want it to print duplicate if result = 0 (the numbers are the same), otherwise add it to list.
The compareTo method in your item class is infinitely recursing on this line:
result = compareTo((Product)o); //this is a self-call!
You want to replace that line with the implementation for your compareTo, for example:
result = this.value - that.value; //insert actual logic here
Maybe you need this:
public class ProductInventory extends Product {
private void insert(Product p) {
if (!list.contains(p)) {
list.add(0, p);
}
}
}
I have a HashMap of employees:
Employee{
String name;
String id;
String Salary;
}
Map<String,Employee> emps = new HashMap<>();
emps.put("1",employee1);
emps.put("2",employee2);
emps.put("3",employee3);
I want have following scenarios:
All employees have name ==> Pass
All employess dont have name(name=null) ==> Pass
3. Other cases must throw an exception.
Example: employee2 does not have a name, but employee1 and employee3 do.
How can I write such scenario?
You can use Streams to filter employees that have or don't have a name, count them and compare the result to the size of the list.
long count = emps.values()
.stream()
.filter(employee -> employee.getName() != null)
.count();
/**
* count == 0 => All employess dont have name
* count == size => All employees have name
*/
return count == 0 || count == employees.size();
You can iterate over the values of the map by calling (map.values()) ,which give you Collection .Then apply your logic.
Collection<Employee> values = emps.values();
int count = 0;
for (Employee employee : values) {
if(null != employee.name){
count ++;
}
}
return count == 0 || count == emps.size();
1) Get list of employees
Collection<Employee> employees = employeesMap.values();
testEmployees(employees);
The program will stop with an exception if employees do not pass the test. This is how to test them
public void testEmployees(List<Employee> employees) {
int nullNameEmployeeCount = 0;
int size = employes.size();
for (Employee employee : employees) {
if (employee.getName() == null) {
nullNameEmployeeCount++;
}
}
if (nullNameEmployeeCount == 0 || nullNameEmployeeCount == size) {
System.out.println("All employees name are not nulls or nulls");
} else {
throw new EmployeesNotPassException();
}
}
And
public class EmployeesNotPassException extends RuntimeException {}
public class Pass {
public static void main(String[] args) {
List<Employee> list = new ArrayList<>();
boolean res = true;
for (Employee e : list) {
res = res && isPass(e);
}
System.out.println(res);
}
public static boolean isPass(Employee employee) {
if (employee == null)
return false;
if (employee.getName() == null)
return true;
if (employee.getName() != null && !StringUtils.isEmpty(employee.getName())) {
return true;
}
return false;
}
}
I created an object Student using Comparable with getters/setters as well as a method that overrides compareTo. In a separate file an arraylist of objects is populated from a text file. Now I need to compare the values in the arraylist to another Student object.
The file was used to create an arraylist as below:
try {
private static ArrayList<Student> array = new ArrayList<Student>();
File file = new File("students.txt");
Scanner scanner = new Scanner(file);
while (scanner.hasNextLine()) {
String inline = scanner.nextLine();
String[] split = inline.split(":");
Student myStudent = new Student();
myStudent.setUsername(split[0]);
myStudent.setPassword(split[1]);
array.add(myStudent);
}
scanner.close();
}
catch (FileNotFoundException e)
{
System.out.println("ERROR.");
}
The text file looks like this:
John:password1
Jane:password2
Jack:password3
(One on each line, no blank lines in between.)
And in a separate method a created Student object is compared to the elements in the arraylist:
Student aStudent = new Student();
aStudent.setUsername("student");
aStudent.setPassword("password");
boolean found = false;
for (int i = 0; i < array.size(); i++)
{
if (array.get(i).compareTo(aStudent) == 0)
{
System.out.println(aStudent.equals(array.get(i)));
found = true;
break;
}
else
{
System.out.println("No such records found!");
found = false;
break;
}
System.out.println(found);
}
The problem is that the object aStudent is not being compared with the objects in the arraylist. It does not print out anything (a -1, 0, or 1) for the call to compareTo, but it always shows that found is true, even though it should be false when there are no matches for aStudent in the file (which there aren't any matches to the username "student" or the password "password").
All together my code complies and works - it just works incorrectly.
Sorry if this sounds confusing. In short, my question is how can I compare the objects of an arraylist to another object using the Comparable interface and compareTo? A plus is if you can tell me what I'm doing wrong.
Thank you in advance.
EDIT
Here is the overriding of the compareTo method:
public int compareTo(Student obj){
int result = 1;
if ((this.Username.compareToIgnoreCase(object.Username) < 0) || (this.Password.compareTo(object.Password) < 0))
{
result = -1;
}
else if ((this.Username.compareToIgnoreCase(object.Username) == 0) && (this.Password.compareTo(object.Password) == 0))
{
result = 0;
}
return result;
}
More context would be useful, but your for-loop looks wrong...
for (int i = 0; i < array.size(); i++)
{
if (array.get(i).compareTo(aStudent) == 0)
{
System.out.println(aStudent.equals(array.get(i)));
found = true;
break; // out of loop
}
else
{
System.out.println("No such records found!");
found = false;
break; // break out loop
}
System.out.println(found);
}
The break statement is used to break out of the loop, meaning that you will only ever compare the first element in the list.
The entire else branch isn't required (or at least I don't think it is ;)), for example...
for (int i = 0; i < array.size(); i++)
{
if (array.get(i).compareTo(aStudent) == 0)
{
System.out.println(aStudent.equals(array.get(i)));
found = true;
break; // out of loop
}
}
System.out.println(found);
Updated
Based on you new compareTo code snippet, this...
if ((this.Username.compareToIgnoreCase(object.Username) < 0) || (this.Password.compareTo(object.Password) < 0))
{
result = -1;
}
else if ((this.Username.compareToIgnoreCase(object.Username) < 0) && (this.Password.compareTo(object.Password) < 0))
{
result = 0;
}
seems wrong to me...the else if should be more like
else if ((this.Username.compareToIgnoreCase(object.Username) == 0) && (this.Password.compareTo(object.Password) == 0))
if the contract for the Comparable interface is to be met, where 0 is equal...
For example...
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;
public class Test {
private static ArrayList<Student> array = new ArrayList<Student>();
public static void main(String[] args) {
array.add(new Student("John", "password1"));
array.add(new Student("Jane", "password2"));
array.add(new Student("Jack", "password3"));
Student aStudent = new Student("Jack", "password3");
boolean found = false;
for (int i = 0; i < array.size(); i++) {
if (array.get(i).compareTo(aStudent) == 0) {
System.out.println(aStudent.equals(array.get(i)));
found = true;
break;
}
}
System.out.println(found);
}
public static class Student implements Comparable<Student> {
private String name;
private String password;
public Student(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public int compareTo(Student object) {
int result = 1;
if ((this.getName().compareToIgnoreCase(object.getName()) < 0) || (this.getPassword().compareTo(object.getPassword()) < 0)) {
result = -1;
} else if ((this.getName().compareToIgnoreCase(object.getName()) == 0) && (this.getPassword().compareTo(object.getPassword()) == 0)) {
result = 0;
}
return result;
}
}
}
Which will print out...
false
true
Where the objects are not equal but where they are comparable...which is kind of weird...to me ;)
Your problem may lie in the compareTo function that you overrode, you need to include that code otherwise no one can determine why certain values are being returned
EDIT:
Note that when objects are created, they are not necessarily equal solely because their contained values are equal. They are separate instances of the object and treated as such.
You will need to override the equals function as well, not just the compareTo function, in order to get the result that you seek.
I have developed a pojo named Employee.java. Now I was planning to make it as user defined collection. I want to make a map and store all the employee type objects in it.
Below is my pojo
public class Employee {
String name,job;
int salary;
public Employee(String n , String j, int t ) //constructor
{
this.name= n;
this.job=j;
this.salary= t;
}
#Override
public int hashCode()
{
return name.hashCode()+job.hashCode()+salary;
}
#Override
public boolean equals(Object obj) {
Employee e = (Employee) obj;
return this.name.equals(e.name)&&this.job.equals(e.job)&&this.salary==e.salary;
}
}
Now I have developed another class that contains map and will store employee type objects..
public static void main(String[] args)
{
Map employeeMap = new HashMap();
Employee e = new Employee("Saral", "Trainer", 34000);
Employee e1 = new Employee("Sarall", "saral", 34090);
employeeMap.put("S", e);
employeeMap.put("S1", e);
System.out.println(employeeMap.size());
Set s = employeeMap.entrySet();
Iterator it = s.iterator();
while(it.hasNext())
{
Map.Entry m =(Map.Entry)it.next();
System.out.println(m.getKey()+"\t"+m.getValue());
}
but when I try to run it , I want to fetch the employee details but I GET DISPLAYED THE OBJECT ON SCREEN ...I want to see the employees value, Please advise me how to get values from employee object.
2
S CollectionsPrac.Employee#285c2854
S1 CollectionsPrac.Employee#285c2854
You need to override the toString method in your Employee class, for example:
public String toString() {
return name + " [" + job + "] - salary: " + salary;
}
By the way, you can replace:
Iterator it = s.iterator();
while(it.hasNext())
{
Map.Entry m =(Map.Entry)it.next();
System.out.println(m.getKey()+"\t"+m.getValue());
}
with
System.out.println(s.toString());
Unless you really want the output to be tab separated.
You need to override the toString() method of Employee
#Override pulic String toString() {
return name + " " + job;
}
First of all. Your hashcode is broken.
Try running this:
System.out.println("Should be false: " + (new Employee("Sara", "Trainer", 1).hashCode() == new Employee("Trainer", "Sara", 1).hashCode()));
If you are using and IDE (like eclipse) there is a function to generate equals and hashcode methods automatically and you would get something like this:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((job == null) ? 0 : job.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + salary;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (job == null) {
if (other.job != null)
return false;
} else if (!job.equals(other.job))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (salary != other.salary)
return false;
return true;
}
As for your main method.. You should try to learn some basics about generics (the stuff inside the <>). You don't need the nity grity details at first. Just learn how to use it with lists and maps.. It will make your life a lot easier. Especially since your using and IDE...
Here is a refactored version of your main method:
public static void main(String[] args)
{
Map<String, Employee> employeeMap = new HashMap<String, Employee>();
Employee e = new Employee("Saral", "Trainer", 34000);
Employee e1 = new Employee("Sarall", "saral", 34090);
employeeMap.put("S", e);
employeeMap.put("S1", e1);
System.out.println(employeeMap.size());
Set<Entry<String, Employee>> entrySet = employeeMap.entrySet();
for (Entry<String, Employee> entry: entrySet) {
System.out.println(entry.getKey()+"\t"+entry.getValue().name);
}
System.out.println("Should be false: " + (new Employee("Sara", "Trainer", 1).hashCode() == new Employee("Trainer", "Sara", 1).hashCode()));
}
Change this in
Iterator it = s.iterator();
while(it.hasNext())
{
Map.Entry m =(Map.Entry)it.next();
Employee empl = (Employee) m.getValue();
System.out.println(m.getKey()+"\t"+empl.name);
}
As you can see with the line
Employee empl = (Employee) m.getValue();
the value is "casted" to an Employee object, and you can start to work with empl variable and use all the Employee class methods and members.
I have code that looks like the following:
private void MethodToDo(SpecialObject o) {
Map<InfoObj, Integer> totalNeeds = new HashMap<InfoObj, Integer>();
for (ListObject obj : o.getListOfObjects()) {
InfoObj infoObj = new InfoObj(obj.getTitle(), obj.getId());
Integer need = totalNeeds.get(infoObj);
if (need == null) {
need = new Integer(obj.getNeeded());
} else {
need = need + obj.getNeeded();
}
totalNeeds.put(infoObj, need);
}
}
The object is a private inner class (in the same class as that method) that looks like this:
private class InfoObj {
private String title;
private Integer id;
public InfoObj(String title, Integer id) {
this.title = title;
this.id = id;
}
public String getTitle() {
return title;
}
public Integer getId() {
return id;
}
#Override
public boolean equals(Object io2) {
if (this == io2) { return true; }
if ( !(io2 instanceof InfoObj) ) { return false; }
InfoObj temp = (InfoObj) io2;
return this.id.equals(temp.id) && this.title.equals(temp.title);
}
#Override
public int hashCode() {
final int prime = 7;
int result = 1;
result = prime * result
+ ((this.title == null) ? 0 : this.title.hashCode());
result = prime * result
+ ((this.id == null) ? 0 : this.id.hashCode());
return result;
}
However, despite overriding the equals and hashCode methods, the hashMap will still contain repeat keys (as in title and id are equivalent...but still show up in multiple places). I think I'm doing everything correctly, but realize I could be missing something...
Also, I know there are repeat keys because I loop through the keySet and output the results, which results in objects with the same title and id showing up multiple times.
Per your comment, a HashMap cannot contain the same keys per the implementation the same key would be:
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
And since you are following the contract for equals and hashcode, any object you create here:
InfoObj infoObj = new InfoObj(obj.getTitle(), obj.getId());
With the same id and title, will be considered the same key, and if the map previously contained a mapping for the key, the old value is replaced.
It looks like everything is OK here.