THE SYLLABUS TASK
What you need to build
Develop a Student Marks Database using Collections.
Full syllabus requirements: classes, fields and methods
Commands, one field per line: 1 roll percentage add/update; 2 sorted display; 3 roll search; 4 toppers and fail list; 0 exit. Positive rolls, percentages 0–100, up to 100 distinct records. All tied highest percentages are toppers; fail means strictly below 40.
UNDERSTAND THE IDEA
Explanation
HashMap maps each roll number to one percentage, making an existing-roll put an update rather than a duplicate record. TreeMap provides a sorted view by roll for presentation.
The topper list includes every record equal to the highest percentage. The fail list uses <40, so exactly 40 is not failed. An empty database is handled before asking Collections.max for a maximum.
TURN THE IDEA INTO STEPS
Step-by-step algorithm
- Validate and add or update a roll’s percentage.
- For search, use HashMap.get and distinguish null from a stored zero.
- Create a TreeMap view for sorted display.
- Find the maximum percentage and list all ties.
- List percentages below 40; handle the empty map first.
SEE THE CORE OPERATION
Flowchart
This chart focuses on the named operation or core OOP decision. Diamonds show conditions; labeled branches show outcomes. Read it with the complete algorithm and source for input handling and other operations. Scroll within the diagram on a phone.
HashMap record lookup
Main.java
import java.util.*;
public class Main {
private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);
static String text() {
if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
String value = IN.nextLine().trim();
if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
return value;
}
static int integer(int low, int high) {
int value = Integer.parseInt(text());
if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
return value;
}
static double number(double low, double high) {
double value = Double.parseDouble(text());
if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
return value;
}
static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }
static class MarksDatabase {
final HashMap<Integer,Double> records=new HashMap<>();
void addRecord(int roll,double percentage){if(roll<=0||!Double.isFinite(percentage)||percentage<0||percentage>100)throw new IllegalArgumentException("Invalid record.");records.put(roll,percentage);}
void displayAllSorted(){System.out.println("Sorted records:");for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());}
Double searchByRoll(int roll){return records.get(roll);}
void displayLists(){
if(records.isEmpty()){System.out.println("No records.");return;}
double top=Collections.max(records.values());System.out.println("Toppers:");
for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())if(Double.compare(entry.getValue(),top)==0)System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());
System.out.println("Fail list (<40):");for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())if(entry.getValue()<40)System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());
}
}
// 1 roll percentage=add/update, 2=sorted display, 3 roll=search, 4=topper/fail lists, 0=exit.
public static void main(String[] args){
try{
MarksDatabase database=new MarksDatabase();
while(true){int command=integer(0,4);if(command==0)break;
if(command==1){int roll=integer(1,1000000);double percentage=number(0,100);if(database.records.size()==100&&!database.records.containsKey(roll))throw new IllegalArgumentException("Database limit reached.");database.addRecord(roll,percentage);System.out.println("Saved.");}
else if(command==2)database.displayAllSorted();
else if(command==3){Double value=database.searchByRoll(integer(1,1000000));if(value==null)System.out.println("No such roll.");else money("Percentage",value);}
else database.displayLists();
}
}catch(IllegalArgumentException e){System.out.println("Invalid input: "+e.getMessage());}
}
}
Copy a source file or download the project to preserve its file names.
USE THE RIGHT ENVIRONMENT
How to compile and run
Install a JDK (Java 11 or newer; JDK 17 is suitable), extract this project’s ZIP into its own folder, then open a terminal in that folder.
javac -encoding UTF-8 Main.java
java MainThe standalone source is named Main.java and its entry class is Main. You may also paste it into an online Java compiler and supply the sample input there. Choose Java explicitly; these pages do not change the site compiler or import code automatically.
Each experiment has its own folder because many use the public class Main. Compile and run one experiment at a time.
UNDERSTAND THE JAVA CODE
Line-by-line explanation
These steps follow the exact Java source above. Related statements are grouped; line numbers count blank lines and restart for each file. Start with main to follow execution, then open the constructors, methods or event handlers it uses.
Main.java
Lines 1–4Import utilities and create the shared input reader
import java.util.*;
public class Main {
private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);java.util.* imports utility type names such as Scanner, Locale and collections. Main is the public class matching Main.java. The private static final IN field holds one shared Scanner for System.in; final prevents replacing that reference. The scanner uses Locale.ROOT.
Lines 5–10Read one nonempty trimmed input line
static String text() {
if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
String value = IN.nextLine().trim();
if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
return value;
}text checks whether a line exists, reads it and removes leading/trailing whitespace. Reject empty text and lengths above 200 by throwing IllegalArgumentException. Otherwise return the string. One field is read per line, so these helpers do not mix nextInt with leftover newlines.
Lines 11–15Parse and range-check an integer field
static int integer(int low, int high) {
int value = Integer.parseInt(text());
if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
return value;
}Integer.parseInt converts the returned string to int or throws NumberFormatException, a subtype of IllegalArgumentException. Reject values outside the caller’s inclusive low/high limits, then return the accepted integer.
Lines 16–20Parse and range-check a finite decimal field
static double number(double low, double high) {
double value = Double.parseDouble(text());
if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
return value;
}Double.parseDouble reads the string as a double. Double.isFinite rejects NaN and infinities as well as the explicit low/high range check. Return only a finite accepted value; exceptions transfer control to a matching catch block.
Line 21Format a labelled numeric result consistently
static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }money is a formatting helper: %s prints the label, %.2f prints two decimal places, and %n ends the line. Locale.ROOT fixes locale-sensitive formatting. The helper can also display non-currency quantities; it does not change the stored value.
Lines 22–24Use a map to insert or update a roll-number record
static class MarksDatabase {
final HashMap<Integer,Double> records=new HashMap<>();
void addRecord(int roll,double percentage){if(roll<=0||!Double.isFinite(percentage)||percentage<0||percentage>100)throw new IllegalArgumentException("Invalid record.");records.put(roll,percentage);}HashMap associates Integer roll IDs with Double percentages. After validation, put creates a new mapping or replaces the old percentage for that roll. It does not create duplicate map keys.
Lines 25–26Sort a display copy and represent a missing lookup
void displayAllSorted(){System.out.println("Sorted records:");for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());}
Double searchByRoll(int roll){return records.get(roll);}A new TreeMap orders the mappings by roll number for display; HashMap itself has no sorted iteration guarantee. searchByRoll returns a Double reference, which can be null for an absent key.
Lines 27–30Find the highest percentage and include all tied toppers
void displayLists(){
if(records.isEmpty()){System.out.println("No records.");return;}
double top=Collections.max(records.values());System.out.println("Toppers:");
for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())if(Double.compare(entry.getValue(),top)==0)System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());Handle an empty database before Collections.max. Find the maximum value, then scan a roll-sorted copy and print every equal top percentage, not only the first student.
Lines 31–33Print students strictly below the pass threshold
System.out.println("Fail list (<40):");for(Map.Entry<Integer,Double> entry:new TreeMap<>(records).entrySet())if(entry.getValue()<40)System.out.printf(Locale.ROOT,"%d: %.2f%%%n",entry.getKey(),entry.getValue());
}
}The fail list uses percentage < 40, so exactly 40 is excluded. The braces end the list-report method and MarksDatabase.
Lines 34–38Start a command-driven map session
// 1 roll percentage=add/update, 2=sorted display, 3 roll=search, 4=topper/fail lists, 0=exit.
public static void main(String[] args){
try{
MarksDatabase database=new MarksDatabase();
while(true){int command=integer(0,4);if(command==0)break;The comment lists add/update, sorted display, lookup and lists. Create one database object and loop until command 0.
Line 39Allow an existing key to be updated even at capacity
if(command==1){int roll=integer(1,1000000);double percentage=number(0,100);if(database.records.size()==100&&!database.records.containsKey(roll))throw new IllegalArgumentException("Database limit reached.");database.addRecord(roll,percentage);System.out.println("Saved.");}Read and validate roll and percentage. At 100 records reject only a new roll; containsKey permits updates to existing entries. Then addRecord performs the map put and prints Saved.
Lines 40–43Display, search safely or report topper/fail lists
else if(command==2)database.displayAllSorted();
else if(command==3){Double value=database.searchByRoll(integer(1,1000000));if(value==null)System.out.println("No such roll.");else money("Percentage",value);}
else database.displayLists();
}For a lookup, test the nullable Double before numeric formatting to avoid unboxing null. Other branches reuse the database’s report methods. The final brace ends the command loop.
Lines 44–46Report invalid input and close the enclosing scopes
}catch(IllegalArgumentException e){System.out.println("Invalid input: "+e.getMessage());}
}
}The catch handles IllegalArgumentException, including invalid numeric strings from the helpers, and prints its message. The final braces close main and Main. Java objects become eligible for garbage collection when no live reference needs them; this program does not manually free them.
FOLLOW THE STATE
Dry run
- Rolls 3 and 1 both have 90; roll 2 has 39.
- Sorted display prints rolls 1,2,3.
- Both rolls 1 and 3 are toppers.
- Updating roll 2 to 40 removes it from the fail list without adding another record.
CHECK THE BEHAVIOUR
Sample input & output
These outputs were captured by running the exact delivered Java project.
Sample 1
1
3
90
1
1
90
1
2
39
2
4
1
2
40
4
3
9
0
Saved.
Saved.
Saved.
Sorted records:
1: 90.00%
2: 39.00%
3: 90.00%
Toppers:
1: 90.00%
3: 90.00%
Fail list (<40):
2: 39.00%
Saved.
Toppers:
1: 90.00%
3: 90.00%
Fail list (<40):
No such roll.
Sample 2
4
1
1
0
3
1
0
No records.
Saved.
Percentage: 0.00
Common mistakes
- HashMap order is not sorted order.
- Do not treat percentage zero as not found; test null.
- Use <40 exactly and include every tied topper.
WHY THIS GROWTH RATE?
Time and space complexity
HashMap add/search expected O(1); sorted display/lists O(n log n); O(n) stored records.
HashMap operations have expected constant-time lookup/update with suitable hashing; that is not an unconditional worst-case guarantee.
Constructing TreeMap from this HashMap inserts n entries into an ordered tree and costs O(n log n). Scanning its entries and finding a maximum add O(n), leaving the same dominant bound.
Temporary ordered views require O(n) additional references. A fixed number of repeated view constructions still has O(n log n) total growth.
These bounds describe the implemented data processing. Input/output text length, scheduling, framework rendering and runtime/library internals are identified separately where they matter. Educational limits bound the actual examples.
PREPARE FOR YOUR LAB VIVA
Related viva questions & answers
Try answering aloud, then expand to check your reasoning. Questions focus on this program’s classes, state and behavior.
What does HashMap.put do when a roll already exists?
It replaces that roll’s prior percentage. The number of distinct records stays unchanged, so addRecord also serves as an update operation.
Why is TreeMap used only for sorted presentation?
The HashMap remains the main lookup store. A TreeMap view orders keys for display without requiring the primary storage to maintain sorted iteration.
How is a stored zero percentage distinguished from a missing roll?
get returns Double zero for the stored value and null for absence. Testing the returned reference for null avoids confusing these cases.
Why are all maximum ties included in the topper list?
The task asks for a topper list rather than one arbitrarily chosen roll. Comparing each percentage with the maximum retains every tied highest record.
Does exactly 40 percent belong to the fail list?
No. The given condition is percentage below 40, so the code uses a strict less-than comparison.
Why check for an empty map before Collections.max?
There is no maximum value in an empty collection. Handling emptiness first prevents an exception and gives a meaningful No records message.