Introduction

Most of you did a diagnostic quiz at the beginning of the semester. This assignment is aboute writing an analytics tool for such quizzes/tests. I have removed any personal information like names and email addresses and replaced Student IDs with randomized IDs.

The data is already read from the csv file into an array of Strings (records) and integers (weights), such that,

  • each String in array records holds the line representing a single test attempt (comma-separated). So,
  • records[0] holds the first attempt (row 2 in the csv, since header is skipped).
  • records[1] holds the second attempt,
  • and so on.
  • each item in array weights holds the weight of the questions. So,
  • weights[0] holds the number of marks for question 1,
  • weights[1] holds the number of marks for question 2,
  • and so on.

Your friends

You have the following friends for this assignment,

1. split(delimiter): Operates on a String object. Splits the String on the delimiter passed. Some examples,

String s = "this is cool";
String[] t = s.split(" "); //t = ["this", "is", "", "", "", "", "", "cool"]
String[] v = s.split(" +"); //t = ["this", "is", "cool"] since split on one or more spaces

String a = "ready, steady, go";
String[] b = a.split(","); //b = ["ready ", "steady ", "go "]
String[] c = a.split(", "); //split on comma AND space
//c = ["ready", "steady", "go"]

2. Integer.parseInt(String): This is a class method operating on Integer class, and converts the passed String to an integer. If the passed value cannot be converted (for example, "hello!", it throws a NumberFormatException). Some examples,

String s = "90210";
int t = Integer.parseInt(s); //t = 90210

String x = "POTUS in a bicycle accident";
String y = Integer.parseInt(x); //throws NumberFormatException

String a = "1.25";
int b = Integer.parseInt(a); //throws NumberFormatException
double c = Double.parseDouble(a); //c = 1.25

3. String.equals(String): You can compare if two Strings are identical using the equals method (and NOT == operator). Some examples,

String s = new String("super");
String t = new String("super");
boolean a = (s==t); //a is false
boolean b = (s.equals(t)); //b is true

With the help of these, you can split an item of records[i] (i being the index) on comma AND space to get each individual token, and then, if required, convert it to integer using Integer.parseInt.

We have also provided an implementation of countZeroes as a sample ("How good is that!" - ScoMo).

Starting point

Starting point is provided in the template attached.

records and weights are available in each method

Since you are writing the instance methods in class Analytics, please note that the arrays records and weights are available in each of the methods, and are pre-populated through the setup method of AnalyticsTest that runs before running each test. You don't need to pass them as parameters.

You should NOT modify the contents of these arrays.

File reading data

The file DataReader.java is responsible to read the data and populate arrays records and weights, through the constructor Analytics(String). This file (DataReader.java) should not be modified. Failure to adhere to this requirement will result in a zero mark.

Test file

The JUnit tests are in AnalyticsTest.java and this file should NOT be modified. Failure to adhere to this requirement will result in a zero mark.

Creating helper methods

You can add as many helper methods as you want.

Adding import statements

You cannot import any library in Analytics.java. The only import statement should be import java.io.FileNotFoundException;. Failure to adhere to this requirement will result in a zero mark.

Modifying method headers

Modifying method headers is a strict no. As an example, changing the header public int countQuestions() to any of the following will result in a zero mark:

public int countQuestions(int n)
public static int countQuestions()
public static double countQuestions()

Compilation or syntax errors

Any compilation or syntax error in Analytics.java OR AnalyticsTest.java (the latter can happen if you modify any method header in Analytics.java) will result in an automatic zero mark. Just make sure there are no red crosses in any of your files (and don't modify method headers), and you will be fine.

Starter Codes

AnalyticsTest.java

//DO NOT MODIFY THIS FILE

package assignment1;

import java.io.FileNotFoundException;
import java.util.Arrays;

import static org.junit.Assert.*;
import org.junit.*;
import java.io.*;
import java.text.*;
import java.util.*;
import org.junit.rules.*;
import java.lang.reflect.*;

public class AnalyticsTest {
private Analytics diagnostic2018, diagnostic2019, diagnostic2020;
public static String currentMethodName = null;
public static int score = 0;
public static String result = "";

@Before
public void beforeEachTest() throws FileNotFoundException {
currentMethodName = null;
diagnostic2018 = new Analytics("diagnostic_2018.csv");
diagnostic2019 = new Analytics("diagnostic_2019.csv");
diagnostic2020 = new Analytics("diagnostic_2020.csv");
}

@Test @Graded(description="countQuestions", marks=5)
public void testCountQuestions() {
assertEquals(2, diagnostic2018.countQuestions());
assertEquals(32, diagnostic2019.countQuestions());
assertEquals(31, diagnostic2020.countQuestions());
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="quizTotal", marks=5)
public void testQuizTotal() {
assertEquals(41, diagnostic2019.quizTotal());
assertEquals(40, diagnostic2020.quizTotal());
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="getQuestionWeight", marks=10)
public void testGetQuestionWeight() {
assertEquals(3, diagnostic2018.getQuestionWeight(1));
assertEquals(3, diagnostic2018.getQuestionWeight(1));
assertEquals(5, diagnostic2019.getQuestionWeight(1));
assertEquals(2, diagnostic2019.getQuestionWeight(2));
assertEquals(1, diagnostic2019.getQuestionWeight(20));
assertEquals(1, diagnostic2019.getQuestionWeight(32));
assertEquals(0, diagnostic2019.getQuestionWeight(0));
assertEquals(0, diagnostic2019.getQuestionWeight(33));
assertEquals(5, diagnostic2020.getQuestionWeight(1));
assertEquals(2, diagnostic2020.getQuestionWeight(2));
assertEquals(1, diagnostic2020.getQuestionWeight(18));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="countPasses", marks=10)
public void testCountPasses() {
assertEquals(586, diagnostic2019.countPasses(0));
assertEquals(27, diagnostic2019.countPasses(41));
assertEquals(314, diagnostic2020.countPasses(0));
assertEquals(14, diagnostic2020.countPasses(40));
assertEquals(517, diagnostic2019.countPasses(20));
assertEquals(264, diagnostic2020.countPasses(20));
assertEquals(470, diagnostic2019.countPasses(25));
assertEquals(228, diagnostic2020.countPasses(25));
assertEquals(389, diagnostic2019.countPasses(30));
assertEquals(177, diagnostic2020.countPasses(30));
assertEquals(220, diagnostic2019.countPasses(35));
assertEquals(98, diagnostic2020.countPasses(35));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="avgQuizPercent", marks=10)
public void testAvgQuizPercent() {
assertEquals(74.5858, diagnostic2019.avgQuizPercent(), 0.001);
assertEquals(71.8073, diagnostic2020.avgQuizPercent(), 0.001);
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="highestMark", marks=10)
public void testHighestMark() {
assertEquals(41, diagnostic2019.highestMark());
assertEquals(40, diagnostic2020.highestMark());
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="bestMarkByStudent", marks=10)
public void testBestMarkByStudent() {
assertEquals(41, diagnostic2019.bestMarkByStudent(2281102));
assertEquals(0, diagnostic2019.bestMarkByStudent(1234567));
assertEquals(27, diagnostic2019.bestMarkByStudent(2270809));
assertEquals(30, diagnostic2019.bestMarkByStudent(2124406));
assertEquals(33, diagnostic2020.bestMarkByStudent(602961));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="avgMarkByStudentId", marks=10)
public void testAvgMarkByStudentId() {
assertEquals(35.6666, diagnostic2019.avgMarkByStudentId(2281102), 0.001);
assertEquals(0, diagnostic2019.avgMarkByStudentId(1234567), 0.001);
assertEquals(27, diagnostic2019.avgMarkByStudentId(2270809), 0.001);
assertEquals(14.25, diagnostic2019.avgMarkByStudentId(2124406), 0.001);
assertEquals(24.75, diagnostic2020.avgMarkByStudentId(602961), 0.001);
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="avgPercentByQuestion", marks=5)
public void testAvgPercentByQuestion() {
assertEquals(47.133, diagnostic2018.avgPercentByQuestion(1), 0.001);
assertEquals(0, diagnostic2018.avgPercentByQuestion(2), 0.001);
assertEquals(88.082, diagnostic2019.avgPercentByQuestion(1), 0.001);
assertEquals(93.881, diagnostic2019.avgPercentByQuestion(2), 0.001);
assertEquals(90.366, diagnostic2019.avgPercentByQuestion(3), 0.001);
assertEquals(92.819, diagnostic2019.avgPercentByQuestion(4), 0.001);
assertEquals(60.869, diagnostic2019.avgPercentByQuestion(31), 0.001);
assertEquals(60.869, diagnostic2019.avgPercentByQuestion(32), 0.001);
assertEquals(89.805, diagnostic2020.avgPercentByQuestion(1), 0.001);
assertEquals(93.521, diagnostic2020.avgPercentByQuestion(2), 0.001);
assertEquals(92.0, diagnostic2020.avgPercentByQuestion(3), 0.001);
assertEquals(93.399, diagnostic2020.avgPercentByQuestion(4), 0.001);
assertEquals(0, diagnostic2019.avgPercentByQuestion(0), 0.001);
assertEquals(0, diagnostic2020.avgPercentByQuestion(0), 0.001);
assertEquals(0, diagnostic2019.avgPercentByQuestion(35), 0.001);
assertEquals(0, diagnostic2020.avgPercentByQuestion(40), 0.001);
assertEquals(0, diagnostic2019.avgPercentByQuestion(-2), 0.001);
assertEquals(0, diagnostic2020.avgPercentByQuestion(-3), 0.001);
assertEquals(60.869, diagnostic2019.avgPercentByQuestion(31), 0.001);
assertEquals(64.609, diagnostic2020.avgPercentByQuestion(30), 0.001);
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="hardestQuestion", marks=10)
public void testHardestQuestion() {
assertEquals(2, diagnostic2018.hardestQuestion());
assertEquals(28, diagnostic2019.hardestQuestion());
assertEquals(22, diagnostic2020.hardestQuestion());
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="getStudentAttempts", marks=5)
public void testGetStudentAttempts() {
assertEquals("[26, 40, 41]", Arrays.toString(diagnostic2019.getStudentAttempts(2281102)));
assertEquals("[]", Arrays.toString(diagnostic2019.getStudentAttempts(1234567)));
assertEquals("[27]", Arrays.toString(diagnostic2019.getStudentAttempts(2270809)));
assertEquals("[2, 8, 17, 30]", Arrays.toString(diagnostic2019.getStudentAttempts(2124406)));
assertEquals("[14, 24, 28, 33]", Arrays.toString(diagnostic2020.getStudentAttempts(602961)));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="getHardestToEasiest", marks=5)
public void testGetHardestToEasiest() {
assertEquals("[2, 1]", Arrays.toString(diagnostic2018.getHardestToEasiest()));
assertEquals("[28, 27, 29, 22, 30, 9, 31, 32, 24, 14, 12, 26, 21, 13, 11, 20, 19, 10, 25, 17, 23, 18, 1, 16, 3, 5, 4, 7, 2, 8, 15, 6]", Arrays.toString(diagnostic2019.getHardestToEasiest()));
assertEquals("[22, 28, 27, 29, 9, 31, 30, 12, 26, 21, 14, 11, 24, 19, 13, 17, 10, 20, 25, 23, 16, 18, 5, 1, 7, 3, 8, 6, 4, 2, 15]", Arrays.toString(diagnostic2020.getHardestToEasiest()));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@Test @Graded(description="timeToDisplayByIndex", marks=5)
public void testTimeToDisplayByIndex() {
assertEquals("59 minutes", diagnostic2019.timeToDisplayByIndex(0));
assertEquals("38 minutes", diagnostic2019.timeToDisplayByIndex(1));
assertEquals("1 hour and 5 minutes", diagnostic2019.timeToDisplayByIndex(40));
assertEquals("5 days, 18 hours and 29 minutes", diagnostic2019.timeToDisplayByIndex(10));
assertEquals("1 hour", diagnostic2019.timeToDisplayByIndex(2));
assertEquals("3 hours", diagnostic2019.timeToDisplayByIndex(3));
assertEquals("18 days, 14 hours and 54 minutes", diagnostic2020.timeToDisplayByIndex(10));
assertEquals("2 days and 3 hours", diagnostic2020.timeToDisplayByIndex(14));
assertEquals("1 day", diagnostic2020.timeToDisplayByIndex(23));
assertEquals("9 days", diagnostic2020.timeToDisplayByIndex(27));
assertEquals("0 minutes", diagnostic2020.timeToDisplayByIndex(53));
assertEquals("1 minute", diagnostic2020.timeToDisplayByIndex(4));
currentMethodName = new Throwable().getStackTrace()[0].getMethodName();
}

@After
public void logSuccess() throws NoSuchMethodException, SecurityException {
if(currentMethodName != null) {
Method method = getClass().getMethod(currentMethodName);
Graded graded = method.getAnnotation(Graded.class);
score+=graded.marks();
result+=graded.description()+" passed. Marks awarded: "+graded.marks()+"n";
}
}

@AfterClass
public static void wrapUp() throws IOException {
System.out.println("Indicative Score = "+score);
System.out.println(result);
System.out.println("NOTE: Any penalty applied due to reasons listed listed nin specifications will result in a lower final score");
}
}

DataReader.java

//DO NOT MODIFY THIS FILE

package assignment1;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Scanner;

public class DataReader {

public static String[] readData(String filename) throws FileNotFoundException {
Scanner scanner = new Scanner(new FileReader(filename));
scanner.nextLine(); // ignore the header
int count = 0;

while (scanner.hasNextLine()) {
scanner.nextLine();
count++;
}

// Pass 2: populate records
String[] records = new String[count];
scanner = new Scanner(new FileReader(filename));
scanner.nextLine(); // ignore header
for (int i = 0; i < records.length; i++) { // since we ignored header row
records[i] = scanner.nextLine();
}
scanner.close();
// Arrays.stream(records).forEach(r -> System.out.println(r));
return records;
}

public static int[] getWeights(String filename) throws FileNotFoundException {
Scanner scanner = new Scanner(new FileReader(filename));
String header = scanner.nextLine();
String[] tokens = header.split(",+");
int[] weights = new int[tokens.length - 4]; // first 4 columns are not question details

for (int i = 4; i < tokens.length; i++) {
String[] s = tokens[i].split("/");
weights[i - 4] = Integer.parseInt(s[1]);
}

scanner.close();
return weights;
}
}

Graded.java

package assignment1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
public @interface Graded {
String description();
int marks();
}

Analytics.java

package assignment1;

import java.io.FileNotFoundException;

public class Analytics {

/**
* An array of strings for the students results combined to split it into usable
* data use: for an item in the array at index i, String[] tokens =
* records[i].split(","); - tokens[0] = Student ID (numerical value stored as
* String) - tokens[1] = Start Date and Time (format DD/MM/YYYY HH:MM) -
* tokens[2] = End Date and Time (format DD/MM/YYYY HH:MM) - tokens[3] = Total
* Grade (numerical value stored as String)
*
* @note marks for each question are stored as Strings in tokens[4], tokens[5],
* .... - marks in first question (numerical value stored as String, but
* could be "-" implying non-attempt): tokens[4] - marks in second
* question (numerical value stored as String, but could be "-" implying
* non-attempt): tokens[5] ...
*/
public String[] records;
public int[] weights;

public Analytics(String filename) throws FileNotFoundException {
records = DataReader.readData(filename);
weights = DataReader.getWeights(filename);
}

/**
* 5 marks
*
* @return the number of questions on the quiz
*/
public int countQuestions() {
}

/**
* 5 marks
*
* @return the total marks for the quiz
*/
public int quizTotal() {
}

/**
* 10 marks
*
* @param questionNumber
* @return the weight of the given question number. return 0 if questionNumber
* is not valid. for example, IF THERE ARE 31 questions, any value
* OUTSIDE 1 to 31 is invalid. IF THERE ARE 50 questions, any value
* OUTSIDE 1 to 50 is invalid.
*/
public int getQuestionWeight(int questionNumber) {
}

/**
* DO NOT MODIFY PROVIDED AS A SAMPLE
*
* @return number of students who get a zero
*/
public int countZeroes() {
int count = 0;
for (int i = 0; i < records.length; i++) {
String[] tokens = records[i].split(",");
if (Integer.parseInt(tokens[3]) == 0) {
count++;
}
}
return count;
}

/**
* 10 marks
*
* @param passMark
* @return number of students who score at least the passing mark provided as
* the parameter
*/
public int countPasses(int passMark) {
}

/**
* 10 marks
*
* @return the overall average percentage marks for the quiz.
* @note the mark for a given attempt is the 4th value in the record
*/
public double avgQuizPercent() {
}

/**
* 10 marks
*
* @return the highest mark for the quiz
*/
public int highestMark() {
}

/**
* 10 marks
*
* @param id
* @return the best mark for the student with given id (return 0 if an attempt
* for that id doesn't exist)
*/
public int bestMarkByStudent(int id) {
}

/**
* 10 marks
*
* @param id
* @return the average mark for the student with given id (return 0 if an
* attempt for that id doesn't exist)
*/
public double avgMarkByStudentId(int id) {
}

/**
* 5 marks
*
* @param questionNumber
* @return the average percentage mark for the given question number. if
* questionNumber = 1, return the average percentage mark for the first
* question, and so on...
*
* NOTE: some students might not attempt a particular question,
* indicated by a dash ("-"). these values don't contribute towards
* attempts or average. For example, let the following be the first
* entries in the array records:
*
* records[0] = "551102,14/2/18 10:52,14/2/18 11:00,2,1,1" records[1] =
* "595789,14/2/18 12:27,14/2/18 14:08,1,-,1" records[2] =
* "463521,14/2/18 13:26,17/2/18 16:32,1,-,0" records[3] =
* "610197,14/2/18 17:04,14/2/18 17:31,0,0,0"
*
* If you look at Question 1 (5th comma-separate value), the marks are:
* "1", "-", "-" and "0". Thus the average should be, mathematically,
* (1+0)/2, which should be returned as (maintaining precision) 0.5
*/
public double avgPercentByQuestion(int questionNumber) {
}

/**
* 10 marks
*
* @return the number of the hardest question (based on average mark). return 1
* if the first question was the hardest, 2 if the second was and so
* on...
*/
public int hardestQuestion() {
}

/**
* 5 marks
*
* @param id
* @return an array containing marks of all attempts for student with given id
* return an empty array if an attempt for student with given id doesn't
* exist.
*/
public int[] getStudentAttempts(int id) {
}

/**
* 5 marks
*
* @return an array containing question numbers from the hardest to easiest
* (based on average marks in the questions)
*/
public int[] getHardestToEasiest() {
}

/**
* 5 marks
*
* @param idx index in array records for which the time must be returned
* @return time in the formats specified in the JUnit tests
*
* there are three components in time (for our purpose) - days, hours
* and minutes. only those components must be included that have a
* non-zero value (the only exception being when the quiz starts and
* finishes during the same minute, in which case it should return "0
* minutes"). a comma separates two components, except the last two,
* where "and" is the separator.
*
* see JUnit tests for more clarity
*
*/
public String timeToDisplayByIndex(int idx) {
}
}
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.