I'm currently learning Java and during my current project I need to print stats of students depending on the different courses: "Java", "DSA", "Databases", "Spring". I managed to get right data but as you can see below in code it is to many code repetition. Do you know maybe how to inject e.g. getJavaPoints() function when "Java" String is passed to printInfoAboutTopLearners(String courseName) method?
I don't use DB as repository currently as it is just learning project the repository is ArrayList<<Student>Student>.
The goal is to simplify code below:
public static void printInfoAboutTopLearners(String courseName) {
System.out.println(courseName);
System.out.println("id points completed");
ArrayList<Student> students = StudentRepository.getStudentRepository();
if (courseName.equals("Java")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getJavaPoints).reversed())
.filter(student -> student.getJavaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getJavaPoints(),
(double) student.getJavaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("DSA")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDsaPoints).reversed())
.filter(student -> student.getDsaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDsaPoints(),
(double) student.getDsaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Databases")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDbPoints).reversed())
.filter(student -> student.getDbPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDbPoints(),
(double) student.getDbPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Spring")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getSpringPoints).reversed())
.filter(student -> student.getSpringPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getSpringPoints(),
(double) student.getSpringPoints() * 100 / JAVA_POINTS));
}
}
Below Student class:
public class Student {
private final int ID_BASE = 10000;
private int id;
private String firstName;
private String lastName;
private String email;
private int javaPoints;
private int dsaPoints;
private int dbPoints;
private int springPoints;
public Student(String firstName, String lastName, String email) {
this.id = idGenerator();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.javaPoints = 0;
this.dsaPoints = 0;
this.dbPoints = 0;
this.springPoints = 0;
}
public int idGenerator(){
return ID_BASE + StudentRepository.getSize();
} //Getters and Setters below
This is how I would model this:
public record Student(String firstName, String lastName, String studentID) {}
public record Course(String name, String id, Gradebook gradebook) {}
Student and Course objects should not (or need not) be directly related to each other. A student doesn't have a course. A student is ENROLLED in a course. Likewise, a course doesn't have a student. Attributes of a course should be limited to at least its name and its ID. A course could have other attributes like section ID, a boolean flag for availability, capacity (max number of seats), and description. I used Java records to eliminate boilerplate code. Also, these objects are immutable (and inherently thread-safe) because there is no reason to mutate them once they are created. I can still mutate the gradebook as you will see later on.
You then need to create classes that will complete the association between a student and a course. Something that comes to mind is Enrollment. In this class, you will have a list of courses containing a list of students enrolled in the particular courses. For this, I will use a class.
public class Enrollment {
private Map<Course, List<Student>>
// rest of class omitted
}
This is an entity a registrar's office would need. For this example, I indicated that Gradebook is an attribute of Course record. This means that Course object has-a Gradebook. A gradebook is a record of students and their particular grades. This is the class that, for your example, links or associates students and course grades.
public class Gradebook {
private Map<Student, List<Integer>> grades = new HashMap<>(); // mapping of student IDs and course grades
public List<Integer> getGrades(Student student) {
return grades.get(student);
}
public double getGPA(Student student) {
return getGrades(student).stream().mapToInt((x) -> x).summaryStatistics().getAverage();
}
public void addStudent(Student student) {
grades.put(student, new ArrayList<Integer>());
}
public void enterStudentGrade(Student student, int points) {
List<Integer> gradePoints = grades.get(student);
gradePoints.add(points);
grades.put(student, gradePoints);
}
public TopPerformer getTopPerformer() {
Map.Entry<Student, Double> topPerformer = grades.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
e-> e.getValue()
.stream()
.mapToInt(Integer::intValue)
.average()
.getAsDouble())).entrySet().stream().max((Entry<?, Double> e1, Entry<?, Double> e2) -> e1.getValue()
.compareTo(e2.getValue())).get();
String name = topPerformer.getKey().firstName() + " " + topPerformer.getKey().lastName();
return new TopPerformer(name, topPerformer.getValue());
}
public record TopPerformer(String name, double points) {}
}
It is worth noting that I didn't use a record for the gradebook because I want to be able to update (mutate) gradebooks after they are initially created.
Now that you have this framework in place, you can create a function to figure out (calculate) the top performers for each course. The question is: where do you put this method? IMO, since a Gradebook instance contains a record of students and their particular grades, you can have this class calculate the averages and return its top student. For simplicity, I added it to this class. I also believe it belongs here since the purpose of the Gradebook class is to manage student grades and calculating the top performer seems to be a derived functionality of this class. I also added the following main method to this class to test the code:
public static void main(String[] args) {
Student steve = new Student("Steve", "Jobs", "12345");
Student bill = new Student("Bill", "Gates", "67890");
Student john = new Student ("John", "Doe", "98765");
Student jane = new Student ("Jane", "Doe", "31245");
Course java = new Course("Java", "COMP1010", new Gradebook());
Course dsa = new Course("DSA", "COMP1020", new Gradebook());
dsa.gradebook().addStudent(john);
dsa.gradebook().addStudent(bill);
java.gradebook().addStudent(steve);
java.gradebook().addStudent(jane);
java.gradebook().enterStudentGrade(jane, 100);
java.gradebook().enterStudentGrade(jane, 95);
java.gradebook().enterStudentGrade(jane, 97);
java.gradebook().enterStudentGrade(steve, 96);
java.gradebook().enterStudentGrade(steve, 95);
java.gradebook().enterStudentGrade(steve, 97);
dsa.gradebook().enterStudentGrade(bill, 100);
dsa.gradebook().enterStudentGrade(bill, 95);
dsa.gradebook().enterStudentGrade(bill, 97);
dsa.gradebook().enterStudentGrade(john, 96);
dsa.gradebook().enterStudentGrade(john, 95);
dsa.gradebook().enterStudentGrade(john, 97);
System.out.println(java.gradebook().getTopPerformer());
System.out.println(dsa.gradebook().getTopPerformer());
}
There is no branching to figure out the course name and do the calculation for top performer. No matter what course it is (current or one that doesn't exist today), it calculates the top performer using the same function; which goes to the heart of your question which was eliminate duplicate code.
Executing the code above outputs the following:
TopPerformer[name=Jane Doe, points=97.33333333333333]
TopPerformer[name=Bill Gates, points=97.33333333333333]
The TopPerformer object doesn't need to be a record. It also doesn't need to be a class. You could simply modify the code slightly and return a map of the top student and his or her GPA.
You have a lot going on here, so without re-writing everything myself, I would suggest you embrace OOD and re-work your classes a bit. You already have a Student class. You should also had a Course class that contains a Map of students with their scores. You can then remove the various point fields from your student class. I'd also keep a CourseRepository class.
Course's have Students, Students don't have Courses.
Then your logic would go something like:
Get Course from CourseRepository using the courseName
Get Get the list of Students and their scores from the Course
Sort the list
Output the list
Though there are other improvement areas like class design etc. as suggested in other answers. Below code demonstrates injecting a function to a method to take out specifics out of the method while keeping only common logic inside. This method is then typically be called "higher order function" in functional programming paradigm terminology.
public static void printInfo(String courseName) {
switch (courseName) {
case "Java":
printInfoAboutTopLearners(Student::getJavaPoints);
break;
case "DSA":
printInfoAboutTopLearners(Student::getDsaPoints);
break;
case "Databases":
printInfoAboutTopLearners(Student::getDbPoints);
break;
case "Spring":
printInfoAboutTopLearners(Student::getSpringPoints);
}
}
public static void printInfoAboutTopLearners(Function<Student, Integer> valueGetter) {
System.out.println("id points completed");
List<Student> students = StudentRepository.getStudentRepository();
students
.stream()
.sorted(Comparator.comparing(valueGetter).reversed())
.filter(student -> valueGetter.apply(student) != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
valueGetter.apply(student),
(double) valueGetter.apply(student) * 100 / JAVA_POINTS));
}
Related
I have a class Student:
public class Student {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
// getters, all-args constructor, etc.
}
And I have a list of Student instances called students.
I want to create a new list which will contain the Students grouped by their name, age and city. The salary and incentive of the students having these attributes identical should be summed up.
Example:
Input:
Student("Raj",10,"Pune",10000,100)
Student("Raj",10,"Pune",20000,200)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",10000,100)
Student("Ram",30,"Pune",30000,300)
Student("Seema",10,"Pune",10000,100)
Output:
Student("Raj",10,"Pune",30000,300)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",40000,400)
Student("Seema",10,"Pune",10000,100)
My attempt:
List<Student> students = // initializing the list
List<Student> res = new ArrayList<>(students.stream()
.collect(Collectors.toMap(
ec -> new AbstractMap.SimpleEntry<>(ec.getName(),ec.getAge(),ec.getCity()),
Function.identity(),
(a, b) -> new Student(
a.getName(), a.getAge(), a.getCity(), a.getSalary().add(b.getSalary()),a.getIncentive().add(b.getIncentive())
)
))
.values())));
Which produces a compilation error:
Compile error- Cannot resolve constructor 'SimpleEntry(String, int, String)' and Cannot resolve method 'add(double)
I've also tried some other options, but without success. How can I achieve that?
To obtain the total salary and incentive for students having the same name, age and city you can group the data using a Map. So you were thinking in the right direction.
In order to achieve that you would need some object, that would hold references name, age and city.
You can't place this data into a Map.Entry because it can only hold two references. A quick and dirty option would be to pack these properties into a List using List.of() (Arrays.asList() for Java 8), or nest a map entry into another map entry (which would look very ugly). Although it's doable, I would not recommend doing so if you care about maintainability of code. Therefore, I'll not use this approach (but if you wish - just change one expression in the Collector).
A cleaner way would be to introduce a class, or a Java 16 record to represent these properties.
Option with a record would be very concise because all the boilerplate code would be auto-generated by the compiler:
public record NameAgeCity(String name, int age, String city) {
public static NameAgeCity from(Student s) {
return new NameAgeCity(s.getName(), s.getAge(), s.getCity());
}
}
For JDK versions earlier than 16 you can use the following class:
public static class NameAgeCity {
private String name;
private int age;
private String city;
// getters, all-args constructor
public static NameAgeCity from(Student s) {
return new NameAgeCity(s.getName(), s.getAge(), s.getCity());
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NameAgeCity that = (NameAgeCity) o;
return age == that.age && Objects.equals(name, that.name) && Objects.equals(city, that.city);
}
#Override
public int hashCode() {
return Objects.hash(name, age, city);
}
}
To make the solution compliant with Java 8, I would use this class.
In order to group the data from the stream into a Map, where instances of the class above would be used as a Key, we can use a flavor of Collector groupingBy(), which expects a keyMapper function and a downstream Collector responsible for generating the Values of the Map.
Since we need to perform plain arithmetic using primitive values, it would be performancewise to make use of the Collector which performs a mutable reduction, i.e. mutates the properties of it's underlying mutable container while consuming elements from the stream. And to create such Collector a type that would serve as a mutable container.
I'll introduce a class AggregatedValues which would serve as accumulation type. It would make sense to do so if objects in the source list represent different people (in such case the result would not represent a particular person, and you would use Student, or whatever it's named in the real code, for that purpose it would be confusing), or if Student is immutable, and you want to keep it like that.
public class AggregatedValues implements Consumer<Student> {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
// getters, all-args constructor
#Override
public void accept(Student s) {
if (name == null) name = s.getName();
if (age == 0) age = s.getAge();
if (city == null) city = s.getCity();
salary += s.getSalary();
incentive += s.getIncentive();
}
public AggregatedValues merge(AggregatedValues other) {
salary += other.salary;
incentive += other.incentive;
return this;
}
}
And that's how we can make use of it:
List<Student> students = new ArrayList<>();
Collections.addAll(students, // List.of() for Java 9+
new Student("Raj", 10, "Pune", 10000, 100),
new Student("Raj", 10, "Pune", 20000, 200),
new Student("Raj", 20, "Pune", 10000, 100),
new Student("Ram", 30, "Pune", 10000, 100),
new Student("Ram", 30, "Pune", 30000, 300),
new Student("Seema", 10, "Pune", 10000, 100)
);
List<AggregatedValues> res = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper
Collector.of( // custom collector
AggregatedValues::new, // supplier
AggregatedValues::accept, // accumulator
AggregatedValues::merge // combiner
)
))
.values().stream()
.collect(Collectors.toList()); // or toList() for Java 16+
If in your real code, it does make sense in your real code to have the same type of the resulting list we can do one small change by introducing method toStudent() in the AggregatedValues.
public class AggregatedValues implements Consumer<Student> {
// the rest code
public Student toStudent() {
return new Student(name, age, city, salary, incentive);
}
}
And this method can be used as a finisher Function of the Collector:
List<AggregatedValues> res = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper
Collector.of( // custom collector
AggregatedValues::new, // supplier
AggregatedValues::accept, // accumulator
AggregatedValues::merge // combiner
AggregatedValues::toStudent // finisherFunction
)
))
.values().stream()
.collect(Collectors.toList()); // or toList() for Java 16+
res.forEach(System.out::println);
Output:
Student{name='Raj', age=20, city='Pune', salary=10000.0, incentive=100.0}
Student{name='Raj', age=10, city='Pune', salary=30000.0, incentive=300.0}
Student{name='Ram', age=30, city='Pune', salary=40000.0, incentive=400.0}
Student{name='Seema', age=10, city='Pune', salary=10000.0, incentive=100.0}
I need to implement a method which should take an array of persons, basically public String oldest (Person [] persons), and return the oldest one. The persons which will be inputed are the following:
new Person("Augusta Ada King, grevinna av Lovelace", 1815),
new Person("Muhammad ibn Musa al-Khwarizmi", 780),
new Person("Alan Turing", 1912),
new Person("Grace Hopper", 1906)
Below you can find my class Called Person. I've tried all different solutions with basic for-loop but I feel really lost and would appreciate any input or recommendation how I should write the method to find the oldest person.
class Person {
String name;
int yearOfBirth;
public Person(String name, int yearOfBirth) {
this.name = name;
this.yearOfBirth = yearOfBirth;
}
public int getAge() {
return getAge(java.time.LocalDate.now().getYear());
}
public int getAge(int year) {
return year - yearOfBirth;
}
#Override
public String toString() {
return String.format("%s %d", name, yearOfBirth);
}
public String oldest(Person [] persons){
}
You can try this:
Person oldest = Arrays.stream(persons).max(Comparator.comparing(Person::getAge)).get();
You need to iterate over your persons array and check which yearOfBirth is greater. You can implement your method like below:
public String oldest(Person[] persons) {
Person oldestPerson = persons[0];
for (Person person : persons) {
if (person.getYearOfBirth() < oldestPerson.getYearOfBirth()) {
oldestPerson = person;
}
}
return oldestPerson.getName();
}
This method should be static since it has nothing to do with an instance, and otherwise you have to call the method from an instance, which you probably don't want to do
Want should happen if you have more then one (oldest) person with the same age?
Why should the return be only the name and not a Person?
public static String oldest(Person[] persons) {
if (persons == null){
throw new NullPointerException("persons == null");
}
if (persons.length == 0) {
return null; //or throw same Exception depending on your handling
}
Person oldestPerson = persons[0];
for (Person person : persons) {
if (person.yearOfBirth < oldestPerson.yearOfBirth) {
oldestPerson = person;
}
}
return oldestPerson.name;
}
Since there could be the possibility of more than one oldest person I would do it as follows:
Here is some data with two oldest people (sort of)
Person[] people = {
new Person("Augusta Ada King, grevinna av Lovelace",
1815),
new Person("Muhammad ibn Musa al-Khwarizmi", 780),
new Person("Another oldest person", 780),
new Person("Alan Turing", 1912),
new Person("Grace Hopper", 1906) };
String s = Person.oldest(people);
System.out.println(s);
prints
Muhammad ibn Musa al-Khwarizmi
Another oldest person
first I would make the method static since it doesn't rely on instance fields but an array of instances.
I would use a map to facilitate holding the the names of the people using their age as the key. Use the Map.merge method to populate the map and handle duplicate ages
Now iterate thru the names and as you do so:
find the oldest age.
store the name as the value for that age.
if another is found of the same age, concatenate the name with a newline(\n) and update the current map value.
when finished, return the value for the computed oldest individual(s).
public static String oldest(Person[] persons) {
int oldest = 0;
Map<Integer, String> ages = new HashMap<>();
for (Person p : persons) {
int age = p.getAge();
oldest = Math.max(oldest, age);
ages.merge(age, p.name, (last, current)- > last + "\n" + current);
}
return ages.get(oldest);
}
In case you would rather return a List of names your method can look like this. The main differences are:
return a List<String> which contains the names
using a Map<Integer, List<String>> to contain the people based on age.
computeIfAbsent to initialize the maps value for that age one time and then add the name to the list.
public static List<String> oldest(Person[] persons) {
int oldest = 0;
Map<Integer, List<String>> ages = new HashMap<>();
for (Person p : persons) {
int age = p.getAge();
oldest = Math.max(oldest, age);
ages.computeIfAbsent(age, v->new ArrayList<>()).add(p.name);
}
return ages.get(oldest);
}
It would then be called like so
List<String> list = Person.oldest(people);
list.foreach(System.out::println); // to print - same as before.
My final recommendation is that you use Lists over Arrays as they have advantages, the main on (imho) being that they grow dynamically as you add more items.
I'm trying to write a java class called "Student" which have an attribute called "courseCode" in which the user can input any course code he wants.
*I don't know the course codes, I'll let the user enter any course code he wants
Example:
Student st1 = new Student("William", .., .., "CS 124");
Student st2 = new Student("James", .., .., "AU 487");
Student st3 = new Student("David", .., .., "CS 124");
Student st4 = new Student("Richard", .., .., "CS 124");
Student st5 = new Student("John", .., .., "AU 487");
In the above example, 3 students registered in course "CS 124" and 2 students registered in "AU 487"
I want to create a static method in Student class itself that can be given a course code as an input and returns the number of students registered in the same course.
Example:
Student.numberOfStudentsInCourse("AU 487");
Output: 2
Student.numberOfStudentsInCourse("CS 124");
Output: 3
I hope I could make it clear to you.
Thanks for your help
Here is one suggestion, at a high level:
In Student class, create a static HashMap<String, Integer>, key is course code, value is counter of students registered for the course.
In the constructor of Student, increment count for the respected course.
In numberOfStudentsInCourse(), return the count for matching course.
In order to do this, your method will need to have a parameter of List<String> or something similar to tell the method which student objects it needs to be looking through.
public static int numberOfStudentsInCourse(List<Student> students, String courseCode) {
int count = 0;
for(Student student : students)
if(student.getCourseCode().equals(courseCode))
count++;
return count;
}
I just wanted to show you a java >=8 approach
You can use the following code to create a Map from courseCode to list of Students with the courseCode
Map<String, Long> courseToCountMap = students.stream()
.collect(Collectors.groupingBy(n -> n.getCourseCode(), Collectors.counting()));
courseToCountMap.entrySet().forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));
My Student class
class Student {
String courseCode;
public Student(String courseCode) {
super();
this.courseCode = courseCode;
}
public String getCourseCode() {
return courseCode;
}
public void setCourseCode(String courseCode) {
this.courseCode = courseCode;
}
}
I'm trying to set HashMap values in java whilst trying to reuse an arrayList. I hadn't worked with HashMaps before and am still get my head around this, so please bear with my limited knowledge.
Class Student:
public class Student {
private static int id = 0;
private String name;
private String surname;
private Map<Subject, List<Grade>> grades;
private List<Absence> absences;
}
grades setter:
public void setGrades(Map<Subject, List<Nota>> grades) {
this.grades = grades;
}
Class Subject:
public class Subject {
private String name;
}
Class Grade:
public class Grade {
private Term term;
private String letter;
}
Main program:
Subject subject = new Subject("History");
Map<Subject, List<Grade>> gradeMap = new HashMap<Subject, List<Grade>>();
List <Grade> grades = new ArrayList<Grade>();
grades.add(new Grade(Term.FIRST_TERM, 'A'));
grades.add(new Grade(Term.SECOND_TERM, 'F'));
grades.add(new Grade(Term.THIRD_TERM, 'B'));
gradeMap.put(subject, grades);
student1.setGrades(gradeMap);
Test printing the keys and values :
for(Map.Entry<Subject, List<Grade>> t :student1.getGrades().entrySet()){
Subject key = t.getKey();
for (Grade grade : t.getValue()) {
System.out.println(key.getName() + " - " + grade.getNumber());
}
}
But whenever i try to reutilize the ArrayList, since there are many students whose grades need setting, I lose all data in my student1 instance
grades.clear();
If I try printing to console now the output is empty:
for(Map.Entry<Subject, List<Grade>> t :student1.getGrades().entrySet()){
Subject key = t.getKey();
for (Grade grade : t.getValue()) {
System.out.println(key.getName() + " - " + grade.getNumber());
}
}
I would really appreciate any comments and suggestions on this, clearly I must be doing something wrong but I don't know what :D
Thanks in advance!
What you put in the map is a reference to that same list you still have. It didn't create a copy of it (as Java doesn't do such things), what you hold is still that same list that's in the map. If you want a new one you need to create it manually:
grades = new ArrayList<>();
And than the only reference to the one with Student1 record will indeed be in that map.
See Is Java "pass-by-reference" or "pass-by-value"? for more details.
how to implement Java Based Auto suggestion. suppose I have different types of data like firstName, rollNumber, address.
My first requirement is like if user enter first character on text box, then result should be sorted on natural order based on firstName and 10 results should be display.
after space if use enter second character and if it is numbere then RollNumber else lastName should be sorted on natural order as ascending.
or if user type third character then Address should be display on ascending order. there should be no database, you don't have to implement Solr or other api. how to implement on pure Java.
here I did not implement the text-box,but I Just took an example to demonstrate
import java.util.*;
import java.lang.*;
import java.io.*;
// A class to represent a student.
class Student {
int rollno;
String name;
String address;
// Constructor
public Student(int rollno, String name, String address) {
this.rollno = rollno;
this.name = name;
this.address = address;
}
// Used to print student details in main()
public String toString(){
return this.rollno + " " + this.name +
" " + this.address;
}
}
class Sortbyroll implements Comparator<Student> {
// Used for sorting in ascending order of rollno
public int compare(Student a, Student b) {
return a.rollno - b.rollno;
}
}
class Sortbyname implements Comparator<Student> {
// Used for sorting in ascending order of name
public int compare(Student a, Student b) {
return a.name.compareTo(b.name);
}
}
// Driver class
class Main {
public static void main (String[] args) {
ArrayList<Student> ar = new ArrayList<Student>();
//here I have thousand student are inserted into
//simple collection.
ar.add(new Student(111, "bbbb", "london"));
ar.add(new Student(131, "aaaa", "nyc"));
ar.add(new Student(121, "cccc", "jaipur"));
System.out.println("Unsorted");
for (int i=0; i<ar.size(); i++) {
System.out.println(ar.get(i));
}
//collection sorted by rollno
Collections.sort(ar, new Sortbyroll());
System.out.println("\nSorted by rollno");
for (int i=0; i<ar.size(); i++) {
System.out.println(ar.get(i));
}
//sort by Name
Collections.sort(ar, new Sortbyname());
System.out.println("\nSorted by name");
for (int i=0; i<ar.size(); i++) {
System.out.println(ar.get(i));
}
}
}
First of all your question is incomplete and misleading. It does not describes the requirement properly. But overall what I assume
You want Google like (?) suggester in your text box
It does not tell any specific things. What about your front end ? How about your data ?
Any way I think you just wanted to have a console like application where you will give partial String as input and your method will guess the Rest of String as an assumption from your dummy data. Am I right ?
If that is the thing you were looking for then I just sketched a demo code below
static List<String> query(String queryStr, List<Student> list) {
List<String> suggestion = new ArrayList<>();
list.forEach(std -> {
if (isMatched(queryStr, String.valueOf(std.getRoll()))) {
suggestion.add(String.valueOf(std.getRoll()));
}
if (isMatched(queryStr, std.getName())) {
suggestion.add(std.getName());
}
if (isMatched(queryStr, std.getAddress())) {
suggestion.add(std.getAddress());
}
});
return suggestion;
}
private static boolean isMatched(String query, String text) {
return text.toLowerCase().contains(query.toLowerCase());
}
And what does this code do ? It actually takes the Partial String that the user input so far and your List<Student> as parameters. Then it iterates over the list and matches for all field for partial match. If any field matches the query it add that value in the suggestion list. In the main you can do like this :
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student(101, "Abc ghi", "USA"));
list.add(new Student(102, "DEF", "UST"));
list.add(new Student(103, "Ghi ab", "DSjkD"));
list.add(new Student(104, "jKL ut", "USN"));
list.add(new Student(105, "MNP", "TSA101"));
list.add(new Student(106, "UTC ABC", "ESA"));
List<String> sugg = query("01", list);
sugg.forEach(System.out::println);
}
and you will find the console printed like :
101
TSA101
Does it make sense ? it might not be your whole confusing requirements. But I think you got the idea. You can exploit this to address your own requirements. You could further imply your sorting logic or any kind of filters to it. It should not be that tough thing.
But you should be concerned that with large number of collection or complex associated objects this would not suffice. Real world application does not work this straight forward. You might need lot of other things to consider like memory, i/o and execution time.
Good Luck!
Do refer https://github.com/nikcomestotalk/autosuggest/
This implementation is in java based on Patricia trie and Edit distance algorithm.
Some salient features of this application is
Auto correction of keywords
Bucket support for sorting and personalization support.
Filtering support.
Limit support.
Build in http server.
Blazing fast search.
And you all are welcome for feedback
Solr/Lucene/Elastic will not give freedom to choose algorithm and personalization support.
You can use a Trie data structure for autosuggestion implementation and the time complexity would be O(word_length) for insert and search.
Apache commons provides implementation "org.apache.commons.collections4.Trie"
example:
Trie<String, String> trie = new PatriciaTrie<>();
trie.put("abcd", "abcd");
trie.put("abc", "abc");
trie.put("abef", "abef");
SortedMap<String, String> map = trie.prefixMap("ab");
map.forEach((k, v) -> {
System.out.println(k + " " + v);
});