This Java code project was submitted for a job opportunity but was marked down for showing "bad habits that could be difficult to unlearn" but I am mystified by what this means. Any takers please. How could it be approached differently or improved.
I have removed any reference to the company involved, but the challenge was framed this way:
Problem description
An employee survey has been conducted, and you've been asked to figure out useful insights from it. You are given the data as a flat CSV file for all employees across the company, containing at least the following columns:
divisionId, teamId, managerId, employeeId, firstName, lastName, birthdate
For example, one record (row) of the CSV file looks like the following:
1,7,3,24,Jon,Snow,1986-12-26
Objective
Based on the structure above, write a piece of code which takes the CSV above as input, and creates a JSON object that looks like the following:
{ "divisions": { "#divisionId": { "teams": { "#teamId": { "managers": { "#managerId": { "employees": { "#employeeId": { "id": "#employeeId", "firstName": "Jon", "lastName": "Snow", "birthdate": "1986-12-26" } } } } } } } } }
NOTE: You can find the data set as
data.csv
in the/data
directory.Questions
- What is the big-O runtime complexity of the algorithm you’ve just written?
- Can you write the code such that all IDs are in ascending order in the JSON output?
- Can you create this such that the list of employees is sorted by their full name? (bonus points if you create a mechanism for arbitrary sort order, e.g., variable/multiple sort fields, ascending and/or descending order)
- [BONUS] Can you calculate the average age across the company, divisions, teams, and managers?
NOTE: for each of the extra questions, you can create different command-line arguments that changes the mode of the application. However, this is only a suggestion, and are you free to take any alternative approach you may wish.
Requirements
Unless explicitly requested otherwise, we expect the following to be used:
- Java 8
- Gradle as the build system
- Any necessary libraries (e.g., Jackson for JSON)
Code:
CSVData.Java
package org.challenge.csv;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
/*
* Base class for CSV data
*
*/
public class CSVData {
private transient final String[] fieldsInCSVHeader;
protected CSVData(String[] fieldsInCSVHeader) {
this.fieldsInCSVHeader = fieldsInCSVHeader;
}
@JsonIgnore
public List<String> getHeaderFields() {
return Arrays.asList(fieldsInCSVHeader);
}
public enum SortDirection {
ASCENDING,
DESCENDING
}
}
CSVParser.java
package org.challenge.csv;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Objects;
import static java.util.stream.Collectors.*;
import org.challenge.csv.survey.SurveyCSVParser;
import org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData;
/**
* Base class for CSV parsers
*
*/
public class CSVParser {
private final File csvFile;
private final short minimumFieldsPerLine;
private final String seperatorOfFields;
private List<String> linesOfCSVFile;
protected CSVParser(File csvFile, short minimumFieldsPerLine, String seperatorOfFields) {
this.csvFile = csvFile;
this.minimumFieldsPerLine = minimumFieldsPerLine;
this.seperatorOfFields = seperatorOfFields;
}
public static Parser createSurveyParser(File csvFile, SurveyCSVData.Employee.SortOrder order, CSVData.SortDirection direction) {
Objects.requireNonNull(csvFile);
return new SurveyCSVParser(csvFile, order, direction);
}
public static Parser createSurveyParser(File csvFile) {
return new SurveyCSVParser(csvFile, SurveyCSVData.Employee.SortOrder.ORIGINAL, CSVData.SortDirection.ASCENDING);
}
protected boolean fileExists() {
return csvFile.exists() && csvFile.canRead();
}
protected boolean fileIsCorrectlyFormatted() {
readFile();
return linesOfCSVFile.size() > 0 && linesOfCSVFile.get(0).split(seperatorOfFields).length >= minimumFieldsPerLine;
}
protected List<String> fileLines() {
readFile();
return linesOfCSVFile.stream().skip(1).collect(toList());
}
private synchronized void readFile()
{
try {
if (null == linesOfCSVFile) {
if (true == fileExists())
linesOfCSVFile = Files.readAllLines(csvFile.toPath()); // NOTE - BufferedReader may be preferred for very large files, can then process line by line or in chunks...
}
}
catch (IOException e) {
// NOTE - Retry in a limited loop, ...
throw new RuntimeException("FAILED to read file content");
}
}
}
Parser.java
package org.challenge.csv;
import java.util.Optional;
/*
* Interface defining CSV parser functions
*
*/
public interface Parser {
/**
* Parse CSV file into an object structure
* @return CSV data object
*/
Optional<CSVData> parse();
}
JSONWriter.java
package org.challenge.json;
import org.challenge.csv.CSVData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Class for writing JSON data from object graph via Jackson libraries
*
*/
public final class JSONWriter {
private final CSVData csvData;
public JSONWriter(CSVData csvData) {
this.csvData = csvData;
}
public String write() throws JsonProcessingException {
ObjectMapper objectToJsonMapper = new ObjectMapper();
String jsonStringRepresentation = objectToJsonMapper.writeValueAsString(csvData);
return jsonStringRepresentation;
}
}
SurveyCSVParser.java
package org.challenge.csv.survey;
import java.io.File;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.challenge.csv.CSVData;
import org.challenge.csv.CSVParser;
import org.challenge.csv.Parser;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Class for parsing CSV data related to employee survey
*
*/
public final class SurveyCSVParser extends CSVParser implements Parser {
private static final short MIN_TOKENS_PER_LINE = 7;
private static final String SEPERATOR_OF_TOKENS = ",";
private final SurveyCSVData.Employee.SortOrder sortOrderOfDataOrEmployees;
private final CSVData.SortDirection sortDirectionOfEmployees;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-M-d");
public SurveyCSVParser(File csvFile, SurveyCSVData.Employee.SortOrder sortOrderOfDataOrEmployees, CSVData.SortDirection sortDirectionOfEmployees) {
super(csvFile, MIN_TOKENS_PER_LINE, SEPERATOR_OF_TOKENS);
this.sortOrderOfDataOrEmployees = sortOrderOfDataOrEmployees;
this.sortDirectionOfEmployees = sortDirectionOfEmployees;
}
@Override
public Optional<CSVData> parse() {
SurveyCSVData csvDataParsed = null;
if (fileExists() && fileIsCorrectlyFormatted()) {
List<String> linesOfCSV = fileLines();
SurveyCSVData csvData = new SurveyCSVData(sortOrderOfDataOrEmployees);
try {
if (SurveyCSVData.Employee.SortOrder.ORIGINAL != sortOrderOfDataOrEmployees)
linesOfCSV.parallelStream().forEach(l -> processLineOfCSV(l, csvData, sortOrderOfDataOrEmployees, sortDirectionOfEmployees));
else
linesOfCSV.stream().forEach(l -> processLineOfCSV(l, csvData, sortOrderOfDataOrEmployees, sortDirectionOfEmployees));
csvDataParsed = csvData;
}
catch (Exception e) {
throw new RuntimeException("FAILED to parse CSV file"); // NOTE - Should a "bad" line prevent the remainder of the parse?
}
}
return Optional.ofNullable(csvDataParsed);
}
private static void processLineOfCSV(String line, SurveyCSVData data, SurveyCSVData.Employee.SortOrder sortOrderOfDataOrEmployees, CSVData.SortDirection sortDirectionOfEmployees)
{
StringTokenizer tokenizer = new StringTokenizer(line, SEPERATOR_OF_TOKENS);
short indexOfTokenFound = 0;
String divisionId = null, teamId = null, managerId = null, employeeId = null, lastName = null, firstName = null, birthdate = null;
while (tokenizer.hasMoreTokens() && indexOfTokenFound < MIN_TOKENS_PER_LINE) {
String token = tokenizer.nextToken();
switch (indexOfTokenFound) {
case 0:
divisionId = token;
break;
case 1:
teamId = token;
break;
case 2:
managerId = token;
break;
case 3:
employeeId = token;
break;
case 4:
firstName = token;
break;
case 5:
lastName = token;
break;
case MIN_TOKENS_PER_LINE-1:
birthdate = token;
break;
default:
assert false;
}
indexOfTokenFound++;
}
if (indexOfTokenFound >= MIN_TOKENS_PER_LINE)
buildSurveyData(divisionId, teamId, managerId, employeeId, firstName, lastName, birthdate, data, sortOrderOfDataOrEmployees, sortDirectionOfEmployees);
}
private static synchronized void buildSurveyData(String divisionId, String teamId, String managerId, String employeeId, String firstName, String lastName, String birthdate, SurveyCSVData data, SurveyCSVData.Employee.SortOrder sortOrderOfDataOrEmployees, CSVData.SortDirection direction)
{
Objects.requireNonNull(divisionId);
Objects.requireNonNull(teamId);
Objects.requireNonNull(managerId);
Objects.requireNonNull(employeeId);
Objects.requireNonNull(firstName);
Objects.requireNonNull(lastName);
Objects.requireNonNull(birthdate);
Integer divisionIdBox = Integer.parseInt(divisionId);
Integer teamIdBox = Integer.parseInt(teamId);
Integer managerIdBox = Integer.parseInt(managerId);
Integer employeeIdBox = Integer.parseInt(employeeId);
if (false == data.divisions.containsKey(divisionIdBox))
data.divisions.put(divisionIdBox, new SurveyCSVData.Division(divisionIdBox, sortOrderOfDataOrEmployees));
SurveyCSVData.Division division = data.divisions.get(divisionIdBox);
if (false == division.teams.containsKey(teamIdBox))
division.teams.put(teamIdBox, division.createTeam(teamIdBox));
SurveyCSVData.Team team = division.teams.get(teamIdBox);
if (false == team.managers.containsKey(managerIdBox))
team.managers.put(managerIdBox, team.createManager(managerIdBox, direction));
SurveyCSVData.Manager manager = team.managers.get(managerIdBox);
if (false == manager.employees.containsKey(employeeIdBox))
manager.employees.put(employeeIdBox, manager.createEmployee(employeeIdBox, firstName, lastName, birthdate)); // NOTE - Duplicates will not be added more than once
}
/**
*
* Class representing survey data
*
*/
public final static class SurveyCSVData extends CSVData {
private static final short VERSION = 1; // NOTE - Good idea to apply version to data structures
private Map<Integer, Division> divisions;
public SurveyCSVData(Employee.SortOrder sortOrderOfDataOrEmployees) {
super(new String[] {"divisionId", "teamId", "managerId", "employeeId", "lastName", "firstName", "birthdate"});
if (Employee.SortOrder.ORIGINAL == sortOrderOfDataOrEmployees)
divisions = new LinkedHashMap <>();
else
divisions = new TreeMap<>();
}
public void addDivision(Integer id, Division division) {
Objects.requireNonNull(id); Objects.requireNonNull(division);
divisions.put(id, division);
}
public Map<Integer, Division> getDivisions() {
return Collections.unmodifiableMap(divisions);
}
/**
* Class representing division in survey data
*/
public final static class Division {
private Map<Integer, Team> teams;
private transient final Integer id;
private final Employee.SortOrder sortOrderOfDataOrEmployees;
public Division(Integer id, Employee.SortOrder sortOrderOfDataOrEmployees) {
this.id = id;
this.sortOrderOfDataOrEmployees = sortOrderOfDataOrEmployees;
if (Employee.SortOrder.ORIGINAL == sortOrderOfDataOrEmployees)
teams = new LinkedHashMap <>();
else
teams = new TreeMap<>();
}
@JsonIgnore
public Integer getId() {
return id;
}
public void addTeam(Integer id, Team team) {
Objects.requireNonNull(id); Objects.requireNonNull(team);
teams.put(id, team);
}
public Team createTeam(Integer id) {
return new Team(id, sortOrderOfDataOrEmployees);
}
public Map<Integer, Team> getTeams() {
return Collections.unmodifiableMap(teams);
}
}
/**
* Class representing team in survey data
*/
public final static class Team {
private Map<Integer, Manager> managers;
private transient final Integer id;
private final Employee.SortOrder sortOrderOfDataOrEmployees;
public Team(Integer id, Employee.SortOrder sortOrderOfDataOrEmployees) {
this.id = id;
this.sortOrderOfDataOrEmployees = sortOrderOfDataOrEmployees;
if (Employee.SortOrder.ORIGINAL == sortOrderOfDataOrEmployees)
managers = new LinkedHashMap <>();
else
managers = new TreeMap<>();
}
@JsonIgnore
public Integer getId() {
return id;
}
public void addManager(Integer id, Manager manager) {
Objects.requireNonNull(id); Objects.requireNonNull(manager);
managers.put(id, manager);
}
public Manager createManager(Integer id, CSVData.SortDirection sortDirectionOfEmployees) {
return new Manager(id, sortOrderOfDataOrEmployees, sortDirectionOfEmployees);
}
public Map<Integer, Manager> getManagers() {
return Collections.unmodifiableMap(managers);
}
}
/**
* Class representing manager in survey data
*/
public final static class Manager {
private final Employee.SortOrder sortOrderOfDataOrEmployees;
private final CSVData.SortDirection sortDirectionOfEmployees;
private transient Map<Integer, Employee> employees;
private transient final Integer id;
public Manager(Integer id, Employee.SortOrder sortOrderOfDataOrEmployees, CSVData.SortDirection sortDirectionOfEmployees) {
this.id = id;
this.sortOrderOfDataOrEmployees = sortOrderOfDataOrEmployees;
this.sortDirectionOfEmployees = sortDirectionOfEmployees;
if (Employee.SortOrder.ORIGINAL == sortOrderOfDataOrEmployees)
employees = new LinkedHashMap <>();
else
employees = new TreeMap<>();
}
@JsonIgnore
public Integer getId() {
return id;
}
public void addEmployee(Integer id, Employee employee) {
Objects.requireNonNull(id); Objects.requireNonNull(employee);
employees.put(id, employee);
}
public Employee createEmployee(Integer id, String firstName, String lastName, String birthdate) {
return new Employee(id, firstName, lastName, birthdate);
}
public Map<Integer, Employee> getEmployees() {
return Collections.unmodifiableMap(employees);
}
@JsonProperty("employees")
public Map<Integer, Employee> getOrderedEmployees() {
Map<Integer, Employee> orderedMapOfEmployees;
if ((Employee.SortOrder.ID == sortOrderOfDataOrEmployees && CSVData.SortDirection.ASCENDING == sortDirectionOfEmployees) || Employee.SortOrder.ORIGINAL == sortOrderOfDataOrEmployees)
orderedMapOfEmployees = employees;
else {
Comparator<Integer> valueComparator = (k1, k2) -> {
Employee e1 = employees.get(k1);
Employee e2 = employees.get(k2);
int compare = 0;
if(null != e1 && null != e2) {
switch (sortOrderOfDataOrEmployees) {
case ID:
compare = Integer.valueOf(e1.id).compareTo(Integer.valueOf(e2.id));
break;
case LASTNAME:
compare = e1.lastName.compareTo(e2.lastName);
break;
case FIRSTNAME:
compare = e1.firstName.compareTo(e2.firstName);
break;
case BIRTHDATE:
compare = e1.birthdate.compareTo(e2.birthdate);
break;
default:
assert false;
break;
}
if (CSVData.SortDirection.DESCENDING == sortDirectionOfEmployees)
compare = -compare;
}
else
throw new NullPointerException("Comparator does not support null values");
return compare;
};
Map<Integer, Employee> sortedMapOfEmployees = new TreeMap<>(valueComparator);
sortedMapOfEmployees.putAll(employees);
orderedMapOfEmployees = sortedMapOfEmployees;
}
return orderedMapOfEmployees;
}
@Override
public String toString() {
return Objects.toString(employees);
}
}
/**
* Class representing employee in survey data
*/
public final static class Employee {
private final int id;
private final String firstName;
private final String lastName;
private final String birthdate;
private transient final LocalDate birthdateDateType;
public Employee(int id, String firstName, String lastName, String birthdate) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.birthdate = birthdate;
this.birthdateDateType = LocalDate.parse(birthdate, FORMATTER); // NOTE - Formatter is not thread safe
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getBirthdate() {
return birthdate;
}
@JsonIgnore
public LocalDate getBirthdateDateType() {
return birthdateDateType;
}
@Override
public String toString() {
return "(id='" + id + "', firstName='" + firstName + "', lastName='" + lastName + "', birthdate='" + birthdate + "')";
}
public enum SortOrder {
ORIGINAL,
ID,
LASTNAME,
FIRSTNAME,
BIRTHDATE
}
}
}
}
AgeCalculator.java
package org.challenge.analysis;
import java.time.Period;
import java.util.Optional;
/**
* Interface for obtaining average age from survey data at different scopes
*
*/
public interface AgeCalculator {
/**
* Calculate average age of employees within a specified scope
* @param scope enum value
* @param id of division, team or manager, can be not present for company scope
* @return Period of time showing the average age
* @exception AgeCalculatorException if id is not present for non-company scope
*/
Period getAverageAge(Scope scope, Optional<Integer> id) throws AgeCalculatorException;
public enum Scope {
COMPANY,
DIVISION,
TEAM,
MANAGER
}
/**
* Exception class for age calculator
*/
class AgeCalculatorException extends Exception {
private static final long serialVersionUID = 1L;
AgeCalculatorException(String message) {
super(message);
}
AgeCalculatorException(String message, Exception inner) {
super(message, inner);
}
}
}
SurveyAnalyzer.java
package org.challenge.analysis;
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.util.Objects;
import java.util.Optional;
import org.challenge.csv.survey.SurveyCSVParser;
/**
* Class implementing average age calculations on survey data
*
*/
public final class SurveyAnalyzer implements AgeCalculator {
private final SurveyCSVParser.SurveyCSVData surveyData;
public SurveyAnalyzer(SurveyCSVParser.SurveyCSVData surveyData) {
this.surveyData = surveyData;
}
@Override
public Period getAverageAge(Scope scope, Optional<Integer> id) throws AgeCalculator.AgeCalculatorException {
if (AgeCalculator.Scope.COMPANY != scope && (null == id || false == id.isPresent()))
throw new AgeCalculator.AgeCalculatorException("For non-COMPANY scope an identifier is required");
long totalDaysAgeOfEmployeesInScope, totalEmployeesInScope;
switch (scope) {
default:
//case COMPANY:
totalDaysAgeOfEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.mapToLong(e -> Duration.between(e.getBirthdateDateType().atStartOfDay(), LocalDate.now().atStartOfDay()).toDays()).sum();
totalEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.count();
break;
case DIVISION:
totalDaysAgeOfEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.filter(d -> Objects.equals(d.getId(), id.get()))
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.mapToLong(e -> Duration.between(e.getBirthdateDateType().atStartOfDay(), LocalDate.now().atStartOfDay()).toDays()).sum();
totalEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.filter(d -> Objects.equals(d.getId(), id.get()))
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.count();
break;
case TEAM:
totalDaysAgeOfEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.filter(t -> Objects.equals(t.getId(), id.get()))
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.mapToLong(e -> Duration.between(e.getBirthdateDateType().atStartOfDay(), LocalDate.now().atStartOfDay()).toDays()).sum();
totalEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.filter(t -> Objects.equals(t.getId(), id.get()))
.flatMap(t -> t.getManagers().values().parallelStream())
.flatMap(m -> m.getEmployees().values().parallelStream())
.count();
break;
case MANAGER:
totalDaysAgeOfEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.filter(m -> Objects.equals(m.getId(), id.get()))
.flatMap(m -> m.getEmployees().values().parallelStream())
.mapToLong(e -> Duration.between(e.getBirthdateDateType().atStartOfDay(), LocalDate.now().atStartOfDay()).toDays()).sum();
totalEmployeesInScope = surveyData.getDivisions().values().parallelStream()
.flatMap(d -> d.getTeams().values().parallelStream())
.flatMap(t -> t.getManagers().values().parallelStream())
.filter(m -> Objects.equals(m.getId(), id.get()))
.flatMap(m -> m.getEmployees().values().parallelStream())
.count();
break;
}
long averageAgeDays = 0;
if (totalEmployeesInScope > 0)
averageAgeDays = (long)Math.floor(totalDaysAgeOfEmployeesInScope / totalEmployeesInScope); // NOTE - Some rounding down here to nearest day over all employees in scope
Period averageAge = Period.between(LocalDate.now(), LocalDate.now().plusDays(averageAgeDays));
return averageAge;
}
}
Task1.java
package org.challenge;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Period;
import java.util.Objects;
import java.util.Optional;
import org.challenge.analysis.AgeCalculator;
import org.challenge.analysis.AgeCalculator.AgeCalculatorException;
import org.challenge.analysis.SurveyAnalyzer;
import org.challenge.csv.CSVData;
import org.challenge.csv.CSVParser;
import org.challenge.csv.Parser;
import org.challenge.csv.survey.SurveyCSVParser;
import org.challenge.json.JSONWriter;
/**
* Main class with entry point
*
*/
class Task1 {
/**
* Main entry point
* @param args
*/
public static void main(String[] args) {
try {
// Path to supplied CSV data file
Path csvFilePath = Paths.get("data", "data.csv");
// Process command-line arguments for sort order and direction
org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData.Employee.SortOrder employeeSortOrder = processSortOrder(args);
org.challenge.csv.CSVData.SortDirection employeeSortDirection = processSortAscendingDescending(args);
// Create the parser
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile(), employeeSortOrder, employeeSortDirection);
long timeBeforeWorkMs = System.nanoTime();
// Parse into object structure
Optional<CSVData> csvDataObjectsOrNull = csvParser.parse();
if (true == csvDataObjectsOrNull.isPresent())
{
CSVData csvDataObjects = csvDataObjectsOrNull.get();
Objects.requireNonNull(csvDataObjects, "FAILED to parse CSV");
// Create the writer
JSONWriter writerofJson = new JSONWriter(csvDataObjects);
// Write out objects as JSON
String jsonStringRepresentation = writerofJson.write();
Objects.requireNonNull(jsonStringRepresentation, "FAILED to output JSON");
System.out.println("Processed in " + (System.nanoTime() - timeBeforeWorkMs) + "ms");
// Dump JSON to console
System.out.println("JSON formatted survey data");
System.out.println(jsonStringRepresentation); // NOTE - Verify and pretty print JSON output at https://jsonlint.com/
// Check we have survey data
if (true == csvDataObjects instanceof SurveyCSVParser.SurveyCSVData) {
SurveyCSVParser.SurveyCSVData surveyData = (SurveyCSVParser.SurveyCSVData)csvDataObjects;
// Dump some sample object data to console
SurveyCSVParser.SurveyCSVData.Manager sampleManager = surveyData.getDivisions().get(1).getTeams().get(5).getManagers().get(1);
System.out.println("Division 1, Team 5, Manager 1 has employees: " + sampleManager);
try {
// Create survey data analyzer
AgeCalculator averageAgeCalculator = new SurveyAnalyzer(surveyData);
Period averageAge;
// Calculate some sample average ages and dump to console
averageAge = averageAgeCalculator.getAverageAge(AgeCalculator.Scope.COMPANY, Optional.empty());
System.out.println("Average age of employees in company: " + formatPeriod(averageAge));
averageAge = averageAgeCalculator.getAverageAge(AgeCalculator.Scope.DIVISION, Optional.of(1)); // NOTE - Samples only, not added to command line arguments
System.out.println("Average age of employees in division 1: " + formatPeriod(averageAge));
averageAge = averageAgeCalculator.getAverageAge(AgeCalculator.Scope.TEAM, Optional.of(12));
System.out.println("Average age of employees in team 12: " + formatPeriod(averageAge));
averageAge = averageAgeCalculator.getAverageAge(AgeCalculator.Scope.MANAGER, Optional.of(2));
System.out.println("Average age of employees under manager 2: " + formatPeriod(averageAge));
}
catch (AgeCalculatorException e) {
System.out.println("AGE EXCEPTION: " + e.toString());
}
}
else {
System.out.println("UNEXPECTED CSV data type");
}
}
else {
System.out.println("FAILED to parse CSV data");
}
System.out.flush();
}
catch (Exception e) {
System.out.println("EXCEPTION: " + e.toString());
}
}
private static org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData.Employee.SortOrder processSortOrder(String[] args) {
org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData.Employee.SortOrder sortOrder = org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL;
if (args.length > 0) {
try {
sortOrder = Enum.valueOf(org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData.Employee.SortOrder.class, args[0]);
}
catch (IllegalArgumentException e) {
System.out.println("FAILED to process sort order, defaulting to ORIGINAL");
}
}
System.out.println("Sort order is " + sortOrder.name());
return sortOrder;
}
private static org.challenge.csv.CSVData.SortDirection processSortAscendingDescending(String[] args) {
org.challenge.csv.CSVData.SortDirection sortDirection = org.challenge.csv.CSVData.SortDirection.ASCENDING;
if (args.length > 1) {
if (true == "DESC".equalsIgnoreCase(args[1]))
sortDirection = org.challenge.csv.CSVData.SortDirection.DESCENDING;
}
System.out.println("Sort direction is " + sortDirection.name());
return sortDirection;
}
private static String formatPeriod(Period period) {
String formattedPeriod = String.format("%d years, %d months, %d days", period.getYears(), period.getMonths(), period.getDays());
return formattedPeriod;
}
}
Tests:
CSVParserTests.java
package org.challenge.csv;
import static org.junit.Assert.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.challenge.csv.survey.SurveyCSVParser.SurveyCSVData;
import org.junit.Test;
public class CSVParserTests {
@Test
public void testParse_givenCSV_success() {
Path csvFilePath = Paths.get("data", "data.csv");
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile());
assertNotNull(csvParser);
Optional<CSVData> csvData = csvParser.parse();
assertNotNull(csvData.orElse(null));
assertTrue(csvData.get() instanceof SurveyCSVData);
SurveyCSVData surveyData = (SurveyCSVData)csvData.get();
assertNotNull(surveyData.getDivisions());
assertNotNull(surveyData.getDivisions().get(1));
}
@Test
public void testParse_emptyCSV_success() {
Path csvFilePath = Paths.get("data", "empty.csv");
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile());
assertNotNull(csvParser);
Optional<CSVData> csvData = csvParser.parse();
assertNotNull(csvData.orElse(null));
assertTrue(csvData.get() instanceof SurveyCSVData);
SurveyCSVData surveyData = (SurveyCSVData)csvData.get();
assertNotNull(surveyData.getDivisions());
assertEquals(0, surveyData.getDivisions().size());
}
@Test
public void testParse_extraCSV_success() {
Path csvFilePath = Paths.get("data", "extra.csv");
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile());
assertNotNull(csvParser);
Optional<CSVData> csvData = csvParser.parse();
assertNotNull(csvData.orElse(null));
assertTrue(csvData.get() instanceof SurveyCSVData);
SurveyCSVData surveyData = (SurveyCSVData)csvData.get();
assertNotNull(surveyData.getDivisions());
assertNotNull(surveyData.getDivisions().get(1));
}
@Test
public void testParse_badCSV_failure() {
Path csvFilePath = Paths.get("data", "badformat.csv");
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile());
assertNotNull(csvParser);
Optional<CSVData> csvData = csvParser.parse();
assertNull(csvData.orElse(null));
}
@Test
public void testParse_nonExistantCSV_failure() {
Path csvFilePath = Paths.get("data", "missing.csv");
Parser csvParser = CSVParser.createSurveyParser(csvFilePath.toFile());
assertNotNull(csvParser);
Optional<CSVData> csvData = csvParser.parse();
assertNull(csvData.orElse(null));
}
}
JSONWriterTests.java
package org.challenge.json;
import static org.junit.Assert.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.challenge.csv.CSVData;
import org.challenge.csv.survey.SurveyCSVParser;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
public class JSONWriterTests {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-M-d");
@Test
public void testWrite_success() {
SurveyCSVParser.SurveyCSVData surveyData = buildSampleData();
JSONWriter writerOfJson = new JSONWriter(surveyData);
try {
String jsonString = writerOfJson.write();
assertNotNull(jsonString);
assertTrue(jsonString.length() > 0);
}
catch (JsonProcessingException e) {
fail("JSON processing failed");
}
}
private SurveyCSVParser.SurveyCSVData buildSampleData() {
SurveyCSVParser.SurveyCSVData surveyData = new SurveyCSVParser.SurveyCSVData(SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Division division = new SurveyCSVParser.SurveyCSVData.Division(1, SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Team team = division.createTeam(1);
SurveyCSVParser.SurveyCSVData.Manager manager = team.createManager(1, CSVData.SortDirection.ASCENDING);
SurveyCSVParser.SurveyCSVData.Employee employee = manager.createEmployee(1, "Stuart", "Mackintosh", LocalDate.now().minusDays(1).format(FORMATTER));
manager.addEmployee(1, employee);
team.addManager(1, manager);
division.addTeam(1, team);
surveyData.addDivision(1, division);
return surveyData;
}
}
SurveyAnalyzerTests.java
package org.challenge.analysis;
import static org.junit.Assert.*;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import org.challenge.csv.CSVData;
import org.challenge.csv.survey.SurveyCSVParser;
import org.junit.Test;
public class SurveyAnalyzerTests {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-M-d");
@Test
public void test_CompanyScope_success() throws AgeCalculator.AgeCalculatorException {
SurveyCSVParser.SurveyCSVData surveyData = buildSampleData();
SurveyAnalyzer analysis = new SurveyAnalyzer(surveyData);
Period period = analysis.getAverageAge(AgeCalculator.Scope.COMPANY, Optional.empty());
assertNotNull(period);
assertEquals(1, period.getDays());
}
@Test
public void test_NoEmployees_success() throws AgeCalculator.AgeCalculatorException {
SurveyCSVParser.SurveyCSVData surveyData = buildEmptySampleData();
SurveyAnalyzer analysis = new SurveyAnalyzer(surveyData);
Period period = analysis.getAverageAge(AgeCalculator.Scope.COMPANY, Optional.empty());
assertNotNull(period);
assertTrue(period.equals(Period.ZERO));
}
@Test
public void test_DivisionScope_success() throws AgeCalculator.AgeCalculatorException {
SurveyCSVParser.SurveyCSVData surveyData = buildSampleData();
SurveyAnalyzer analysis = new SurveyAnalyzer(surveyData);
Period period = analysis.getAverageAge(AgeCalculator.Scope.DIVISION, Optional.of(1));
assertNotNull(period);
assertEquals(1, period.getDays());
}
@Test(expected=AgeCalculator.AgeCalculatorException.class)
public void test_DivisionScope_failure() throws AgeCalculator.AgeCalculatorException {
SurveyCSVParser.SurveyCSVData surveyData = buildEmptySampleData();
SurveyAnalyzer analysis = new SurveyAnalyzer(surveyData);
analysis.getAverageAge(AgeCalculator.Scope.DIVISION, null);
}
private SurveyCSVParser.SurveyCSVData buildSampleData() {
SurveyCSVParser.SurveyCSVData surveyData = new SurveyCSVParser.SurveyCSVData(SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Division division = new SurveyCSVParser.SurveyCSVData.Division(1, SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Team team = division.createTeam(1);
SurveyCSVParser.SurveyCSVData.Manager manager = team.createManager(1, CSVData.SortDirection.ASCENDING);
SurveyCSVParser.SurveyCSVData.Employee employee1 = manager.createEmployee(1, "Stuart", "Mackintosh", LocalDate.now().minusDays(1).format(FORMATTER));
SurveyCSVParser.SurveyCSVData.Employee employee2 = manager.createEmployee(2, "Stuart L", "Mackintosh", LocalDate.now().minusDays(2).format(FORMATTER));
manager.addEmployee(1, employee1);
manager.addEmployee(2, employee2);
team.addManager(1, manager);
division.addTeam(1, team);
surveyData.addDivision(1, division);
return surveyData;
}
private SurveyCSVParser.SurveyCSVData buildEmptySampleData() {
SurveyCSVParser.SurveyCSVData surveyData = new SurveyCSVParser.SurveyCSVData(SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Division division = new SurveyCSVParser.SurveyCSVData.Division(1, SurveyCSVParser.SurveyCSVData.Employee.SortOrder.ORIGINAL);
SurveyCSVParser.SurveyCSVData.Team team = division.createTeam(1);
SurveyCSVParser.SurveyCSVData.Manager manager = team.createManager(1, CSVData.SortDirection.ASCENDING);
team.addManager(1, manager);
division.addTeam(1, team);
surveyData.addDivision(1, division);
return surveyData;
}
}
Data.csv (excerpt):
divisionId,teamId,managerId,employeeId,firstName,lastName,birthdate
7,6,2,597,Terrill,Lindgren,1956-7-21
7,10,2,632,Cecile,Mante,1955-3-11
6,2,1,489,Audreanne,Labadie,1964-4-5
9,9,1,859,Vinnie,Mann,1974-11-20
7,7,1,607,Cecilia,Kunde,1997-7-18
2,9,2,134,Taryn,Bednar,1970-5-8
9,9,2,865,Helmer,Littel,1964-2-19
11,4,1,1071,Declan,Bailey,1972-8-7
5,8,1,476,Gladyce,Mills,1988-12-15
12,3,1,1157,Cyrus,Tillman,1980-2-19
7,12,2,651,Camryn,Ernser,1965-11-17
6,4,1,515,Kadin,Wehner,1989-11-5
7,4,1,570,Kirk,Rowe,1966-1-26
10,6,1,993,Ashlee,Wuckert,1956-1-9
13,2,2,1183,Anibal,Botsford,1972-7-25
7,6,2,598,Roscoe,Corkery,1954-5-16
10,7,1,1008,Branson,Hammes,1983-5-2
14,9,1,1308,Favian,Skiles,1981-9-13
14,10,1,1331,Kaelyn,Rosenbaum,1956-8-22
8,9,1,765,Shea,Osinski,1964-3-5
7,6,2,594,Helena,Lesch,1957-10-26
9,9,2,864,Orrin,Stiedemann,1951-10-19
5,8,1,475,Vivien,Kemmer,1981-7-20
5,3,1,432,Genoveva,Kassulke,1987-5-27
8,5,2,745,Verdie,Gerhold,1988-6-25
1,5,2,15,Stefan,Stokes,1978-11-24
8,4,1,731,Tyrel,McDermott,1992-12-9
2,10,1,139,Rickey,Hodkiewicz,1968-1-24
10,8,1,1017,Gianni,Morissette,1992-8-23
14,11,2,1342,Uriel,Halvorson,1965-9-9
7,10,2,630,Robert,Johnston,1955-10-27
14,12,2,1366,Rene,Carter,1988-9-23
2,1,1,51,Maggie,O'Kon,1998-5-22
2,11,1,156,Jerod,Walker,1971-8-3
13,2,1,1174,Margarette,Jacobi,1955-8-10
3,12,1,301,Cleo,Hudson,1989-1-28
2,13,1,183,Jaquan,Skiles,1957-9-10
9,13,1,904,Elfrieda,Langworth,1953-10-18
5,7,1,461,Torrey,Gislason,1998-1-23
2,7,1,102,Demond,Herman,1950-7-3
11,3,1,1057,Gracie,Rau,1957-6-9
14,7,1,1290,Ollie,Bogan,1951-10-4
7,8,1,610,Rahul,Spencer,1981-10-3
8,5,2,742,Bell,Orn,1963-8-19
8,6,1,752,Alisa,Corwin,1969-11-4
3,2,1,198,Angelina,Corwin,1969-2-2
3,5,2,243,Rigoberto,Runolfsson,1971-1-21
9,16,1,934,Elise,Hegmann,1964-2-5
9,17,1,935,Richmond,Cormier,1980-1-6
14,5,2,1273,Eino,O'Conner,1969-8-24
13,7,1,1214,Kathryn,Kub,1966-1-6
5,7,2,470,Golden,Reichert,1952-9-10
2,1,1,57,Dejah,Schaefer,1975-2-5
2,7,1,101,Ronny,McCullough,1994-6-2
10,6,2,1001,Nikki,Runolfsson,1961-10-5
8,4,1,730,Wayne,Ward,1997-4-9
7,3,1,569,Vivian,Muller,1969-7-31
5,6,1,450,Juvenal,Schmidt,1973-8-5
14,5,2,1274,Orrin,O'Keefe,1981-12-13
14,9,1,1313,Jazlyn,Walter,1992-4-13
1,4,1,7,Darwin,Collins,1975-4-11
9,13,1,901,Margarita,Spinka,1972-5-4
10,2,1,954,Stephen,Schmitt,1980-2-5
2,7,1,104,Westley,Swift,1989-10-19
3,14,1,321,Emelie,Simonis,1951-5-29
11,11,2,1126,Clement,Lemke,1990-10-20
7,14,2,680,Millie,Haag,1979-8-23
2,1,1,56,Marcelino,Will,1987-10-29
14,7,1,1288,Cheyanne,Labadie,1954-7-20
4,3,2,386,Owen,Turner,1986-2-7
3,1,1,193,Lynn,Huel,1963-8-17
14,11,2,1345,Evangeline,Becker,1995-10-9
6,6,1,535,Bridget,Rath,1959-9-12
3,10,1,277,Rodolfo,O'Kon,1951-3-30
12,2,1,1153,Vincent,Collins,1991-8-17
12,1,2,1145,Coralie,Olson,1956-9-14
9,16,1,933,Annabell,Wehner,1965-5-25
7,12,2,649,Davonte,Kohler,1970-6-30
2,12,1,170,Laury,Muller,1968-12-16
7,9,2,622,Norval,Gusikowski,1967-5-18
2,12,1,173,Jena,Conn,1999-5-23
11,3,1,1056,Kamren,Koch,1960-3-24
14,1,1,1242,Mckenna,Graham,1958-6-20
12,3,1,1155,Raegan,Doyle,1996-12-9
9,17,1,939,Lazaro,Swaniawski,1981-8-16
10,4,2,974,Astrid,Rath,1998-9-19
3,4,1,215,Scarlett,Watsica,1987-5-27
5,1,1,400,Jenifer,Stokes,1969-5-16
12,3,2,1164,Kobe,Wisozk,1958-3-24
8,11,1,788,Margret,Zemlak,1993-8-20
9,15,1,920,Sister,Braun,1960-11-9
5,1,1,402,Daniela,Pollich,1968-11-11
9,4,2,820,Cornell,Robel,1952-4-9
3,4,2,227,Marion,Flatley,1997-11-13
1,4,1,3,Arvel,Runolfsdottir,1954-9-5
6,3,1,498,Jazmyn,Hartmann,1986-9-4
5,3,1,433,Cloyd,Botsford,1995-12-23
9,16,1,932,Jerrell,Moore,1972-12-3
11,4,1,1072,Jefferey,Goldner,1984-5-3
14,5,1,1267,Nicolas,Davis,1988-2-5
14,7,1,1291,Demarco,Rolfson,1980-1-3
6,1,1,483,Katelin,Hintz,1955-3-2
14,11,1,1339,Jennings,Schowalter,1985-8-6
12,3,2,1159,Kamryn,Wyman,1998-8-17
3,1,1,187,Tremayne,Cummings,1998-1-14
3,13,1,316,D'angelo,Morar,1990-10-20
3,16,1,346,Marian,Mante,1955-2-27
7,13,1,664,Audreanne,Schoen,1987-9-16
14,2,1,1245,Rylan,Conroy,1951-6-8
5,3,1,429,Leola,Hansen,1997-5-6
10,6,1,991,Ahmad,Schinner,1966-2-11
6,6,1,536,Irma,Osinski,1988-11-29
13,2,1,1173,Sister,Heller,1984-9-13
10,8,1,1019,Margaret,Stokes,1960-8-8
14,11,2,1347,Noah,Brakus,1983-4-21
9,5,1,827,Christina,Feeney,1972-5-31
2,12,1,172,Elwyn,Upton,1971-11-9
2,10,1,143,Reva,Hand,1955-3-17
7,7,1,605,Evangeline,Schuster,1995-7-20
6,5,2,529,Albina,Koss,1981-2-12
7,16,1,698,Ronaldo,Rutherford,1983-11-22
9,12,2,899,Odessa,McClure,1958-6-5
3,4,2,219,Braulio,Gibson,1960-6-12
13,8,2,1232,Josiah,Reynolds,1963-7-30
3,5,1,229,Vidal,Schuppe,1963-2-12
2,1,1,50,Friedrich,Ortiz,1951-1-30
13,4,2,1199,Orin,Vandervort,1981-4-17
3,2,1,196,Chelsey,Boyer,1995-9-5
14,5,2,1276,Tracy,Leffler,1983-3-31
12,3,2,1161,Edythe,Sauer,1972-2-1
13,3,1,1186,Otilia,O'Reilly,1986-2-20
4,4,2,394,Taylor,Quitzon,1975-8-16
11,10,2,1112,Kayli,Mohr,1961-10-10
10,2,1,950,Tillman,Abshire,1972-11-11
13,3,2,1190,Nya,Klocko,1971-6-10
9,14,1,913,Pedro,D'Amore,1957-10-19
12,3,2,1165,Gunner,Hamill,1986-9-15
7,3,1,566,Bailey,Bayer,1965-2-7
3,15,1,339,Broderick,Hettinger,1998-6-18
14,10,1,1326,Carlos,Von,1978-10-21
3,16,2,349,Webster,Rodriguez,1987-7-2
3,16,1,348,Vallie,Wyman,1995-3-16
14,9,2,1318,Clifford,Leuschke,1959-8-30
7,8,1,612,Otto,Mante,1951-3-26
3,8,1,267,Kaleb,Rice,1963-12-25
9,17,1,942,Dave,Erdman,1968-5-23
14,4,1,1263,Lemuel,Osinski,1966-4-20
14,7,2,1292,Winnifred,Mraz,1964-11-29
11,9,1,1093,Ottilie,Gutmann,1990-11-2
9,3,1,797,Rocio,Fisher,1960-2-21
2,10,1,147,Tobin,Larkin,1987-12-27
11,3,1,1058,Brenden,Bechtelar,1981-8-2
13,8,1,1228,Irma,Bruen,1972-8-21
11,1,1,1043,Francisco,Hartmann,1967-6-4
4,3,1,377,Donato,Hyatt,1955-4-21
5,3,1,428,Alexanne,Parker,1965-1-9
11,1,1,1041,Eleanora,Littel,1985-4-19
7,14,1,674,Curt,Kshlerin,1996-12-2
11,10,2,1107,Yazmin,Williamson,1971-5-25
3,12,1,305,Bailee,Rodriguez,1965-5-14
11,2,1,1052,Mathew,McClure,1960-10-18
14,9,2,1317,Macie,Rath,1971-11-6
9,3,2,805,Joany,Sanford,1972-1-24
1,6,1,20,Manley,Bednar,1973-5-31
4,1,2,369,Twila,Stoltenberg,1981-8-8
1,3,2,1,Maeve,Corwin,1963-4-7
11,5,1,1075,Effie,Dooley,1997-6-28
14,4,1,1261,Whitney,Gibson,1982-8-16
2,8,1,117,Chris,Mann,1992-3-2
10,8,1,1014,Hans,Hauck,1953-10-29
10,5,1,979,Wilfredo,Kub,1993-1-9
5,6,1,446,Gabe,Walter,1954-9-23
3,2,1,197,Maxine,Oberbrunner,1996-8-8
9,12,1,888,Cecile,Adams,1963-7-23
2,11,1,153,Tyree,Lemke,1983-8-7
2,11,1,149,Mathew,Lehner,1967-12-3
1,5,1,11,Hipolito,Collins,1961-12-17
7,1,1,548,Assunta,Murazik,1993-5-27
1,7,1,31,Maye,Torphy,1956-12-11
8,9,1,767,Kamille,Kessler,1995-11-27
4,3,1,380,Derrick,Bergnaum,1996-4-20
11,9,2,1096,Hal,Price,1970-11-29
8,1,1,705,Jailyn,Predovic,1993-6-7
10,2,1,958,Jamaal,Buckridge,1997-4-12
14,1,1,1240,Dovie,Yundt,1995-8-17
8,8,1,760,Marisol,Beahan,1975-6-25
3,6,2,248,Jade,Haag,1950-10-21
13,8,1,1223,Gerry,Ziemann,1976-4-3
14,1,1,1237,Logan,Schneider,1977-5-31
7,16,1,700,Nikki,Daniel,1978-4-28
10,5,2,989,Art,Bernhard,1969-9-28
14,1,1,1234,Cassie,Aufderhar,1990-8-31
9,12,2,898,Kayden,Spinka,1986-1-9
11,3,2,1063,Faustino,Schamberger,1994-11-14
6,3,2,509,April,Williamson,1984-4-21
10,4,1,971,Cristina,DuBuque,1968-9-19
9,14,1,911,Roel,Flatley,1958-10-17
7,5,1,578,Magdalena,Cole,1986-2-23
8,3,1,718,Pat,Dach,1956-2-29
9,15,2,923,Kiana,Jenkins,1994-7-28
11,1,1,1042,Gene,West,1953-5-21
13,1,1,1170,Adelbert,Lockman,1991-10-11
1,4,1,4,Anthony,Armstrong,1957-4-9
1,7,2,35,Ramiro,Kohler,1973-9-6
14,5,2,1275,Reyes,Funk,1960-6-10
14,11,1,1334,Ellis,Roob,1951-9-18
2,9,2,138,Linnea,Blanda,1968-2-29
2,4,2,85,Elinor,Jakubowski,1999-4-29
9,13,2,909,Kristy,Orn,1963-12-9
9,12,1,893,Mandy,Howell,1985-11-9
14,6,1,1281,Marisa,Terry,1991-2-12
12,1,1,1133,Taryn,Predovic,1990-9-19
11,7,2,1083,Otto,Bergstrom,1955-6-8
14,11,2,1343,Howell,Moore,1956-9-27
11,3,2,1061,Sabina,Senger,1968-4-1
1,8,2,45,Gaston,Graham,1963-9-2
9,17,1,941,Brant,Halvorson,1970-12-28
3,11,2,292,Hallie,Schaefer,1974-8-6
9,6,1,832,Kara,Block,1974-2-5
4,4,1,390,Terrence,Effertz,1986-11-29
4,1,1,356,Maye,Bauch,1980-9-16
2,6,2,96,Brock,Rowe,1971-1-17
7,4,1,575,Jessie,Larkin,1977-6-24
14,4,1,1260,Carlie,Gerlach,1995-5-11
1,7,2,33,Kirsten,Reichel,1988-11-6
3,9,1,271,Roselyn,Jakubowski,1970-11-19
14,12,1,1360,Alene,Jacobi,1999-5-26
10,5,2,986,Newton,Volkman,1969-12-13
7,13,1,656,Soledad,Spencer,1993-12-31
3,5,1,230,Lucas,Emmerich,1977-9-29
14,8,2,1307,Lyla,Vandervort,1979-6-16
10,3,1,969,Vena,Conn,1985-3-6
9,11,1,877,Sally,Runolfsdottir,1960-10-6
11,11,1,1121,Caroline,Smitham,1979-8-4
9,1,1,792,Litzy,Tromp,1972-8-22
7,10,2,631,Reynold,Dare,1991-4-14
10,7,2,1011,Beverly,McLaughlin,1999-8-20
1,6,1,18,Jaylen,Cole,1975-10-6
2,13,1,182,Delia,Strosin,1968-11-29
8,9,1,764,Lavina,Koch,1993-9-16
2,4,2,87,Marvin,Lehner,1956-11-1
10,2,1,956,Prince,Schroeder,1979-5-9
2,9,1,129,Emmie,Auer,1969-6-19
8,5,2,743,Antoinette,Legros,1986-5-17
9,7,1,847,Murphy,Jenkins,1955-12-2
11,11,2,1124,Quinton,Romaguera,1973-12-28
12,3,2,1166,Martine,Stanton,1977-3-1
8,4,2,737,Nova,Sporer,1993-4-5
3,13,1,314,Gilberto,Kuhic,1970-4-17
10,1,2,948,Faye,Wisoky,1958-12-11
7,12,2,650,Aleen,O'Connell,1987-9-21
10,5,1,975,Sandrine,Hegmann,1980-6-27
4,3,1,379,Shanna,Mann,1977-7-4
1,5,2,14,Eugenia,Nicolas,1976-5-5
13,2,1,1175,Heaven,Lang,1962-12-29
7,6,1,593,Hans,Fahey,1964-3-30
3,14,2,330,Alexa,Muller,1964-12-20
7,9,2,616,Elwyn,Russel,1966-5-21
10,2,1,953,Adolphus,Koch,1975-7-25
9,10,1,870,Rose,Walker,1950-6-15
3,3,2,212,Jairo,Smith,1980-11-5
8,11,1,781,Haylee,Stiedemann,1980-12-8
2,8,1,118,Alysson,Wisoky,1982-10-2
13,7,1,1217,Jace,Monahan,1995-12-10
14,5,2,1278,Danny,Kautzer,1967-12-30
1,6,2,26,Madyson,Bednar,1972-5-27
11,1,2,1047,Annalise,Lind,1982-7-23
1,5,2,13,Orlo,Wuckert,1991-2-14
9,15,2,924,Nicole,Balistreri,1950-10-5
7,6,2,596,Abigayle,Bogisich,1977-7-4
8,2,1,713,Malcolm,Spencer,1982-11-24
10,1,2,949,Eugene,Barrows,1983-7-30
6,6,2,546,Edward,Crist,1996-4-30
9,8,1,856,Percival,Bogan,1968-2-16
2,7,2,109,Gaetano,Rosenbaum,1979-1-30
14,11,2,1349,Eulalia,Nader,1958-4-18
1,8,1,37,Remington,Ratke,1988-11-15
7,15,1,687,Vada,Hansen,1960-10-13
4,3,2,387,Rogers,Larkin,1988-7-20
5,2,1,419,Arne,Ernser,1971-1-24
4,2,1,372,Orin,Quitzon,1995-1-26
11,9,2,1099,Roscoe,Collier,1990-6-1
13,7,1,1218,Chet,Wyman,1953-7-13
2,3,2,67,Aubree,Marvin,1979-6-6
3,15,1,337,Clare,Runolfsson,1969-9-9
14,11,2,1348,Urban,Hamill,1963-2-12
9,7,2,848,Hulda,Kautzer,1971-7-29
5,2,2,424,Neha,Jenkins,1961-8-22
3,4,1,214,Shawna,Boyle,1991-12-11
10,7,2,1012,Ivory,Davis,1987-5-16
11,8,1,1085,Esteban,Powlowski,1954-12-25
8,9,2,773,Dannie,Bogisich,1949-11-2
8,9,1,768,Aylin,Sporer,1990-12-30
2,2,1,59,Tianna,Kilback,1995-11-19
13,8,1,1226,Verla,Lehner,1969-4-14
2,12,1,169,Tina,Becker,1966-8-5
12,1,2,1142,Cassie,Littel,1956-10-4
5,3,1,430,Naomi,Stiedemann,1953-11-24
10,5,2,987,Deonte,Larson,1980-3-5
2,6,2,95,Odie,Halvorson,1992-11-5
2,13,1,181,Yolanda,Leannon,1996-3-2
10,5,1,980,Rachel,West,1978-3-24
8,10,2,777,Gladys,Lakin,1978-8-23
12,3,1,1156,Eugene,Farrell,1975-2-22
7,13,2,668,Ryley,Berge,1989-11-3
14,10,1,1324,Martine,Becker,1961-4-15
6,1,1,484,Greyson,Welch,1956-3-7
3,3,1,205,Cade,Hessel,1990-2-5
13,3,1,1188,Federico,Bins,1976-6-29
1,4,1,2,Lauren,Keeling,1969-12-27
2,10,1,144,Brandt,Torp,1960-8-1
10,2,1,951,Joey,Abernathy,1973-3-20
3,3,2,209,Alayna,Orn,1988-7-28
4,1,1,359,Jensen,Beier,1956-12-9
3,5,2,238,Victor,Murray,1983-6-3
2,11,2,167,Lauriane,Hodkiewicz,1952-2-25
9,13,1,906,Mayra,Heidenreich,1969-9-8
14,4,1,1257,Ova,Torp,1955-12-26
11,10,2,1111,Verla,Oberbrunner,1959-7-9
3,14,2,327,Brianne,Schoen,1975-7-29
7,1,2,559,Agustin,Pouros,1997-3-25
10,6,2,999,Earline,Becker,1964-1-22
13,2,1,1177,Genevieve,Kutch,1984-3-23
3,7,1,261,Gussie,Emmerich,1971-2-12
9,4,1,818,Louisa,Strosin,1953-8-16
10,5,2,985,Okey,Fisher,1960-12-2
2,13,1,179,Aylin,Kshlerin,1967-10-21
10,3,1,966,Sonya,Hagenes,1977-5-13
14,11,1,1338,Murl,Boehm,1986-2-4
7,5,2,581,Jamie,Aufderhar,1990-12-28
2,12,1,168,Keegan,Collins,1978-8-24
7,13,1,657,Connor,Kessler,1981-7-1
11,8,2,1091,Lauren,Hoeger,1958-1-1
10,4,1,970,Martina,Greenholt,1978-10-20
5,8,1,472,Clinton,Maggio,1968-5-31
9,14,1,917,Esther,Nitzsche,1989-7-24
2,10,1,142,Rashad,Ortiz,1950-3-5
4,3,2,385,Jacky,Crona,1989-6-18
12,2,1,1149,Neoma,Schamberger,1981-3-23
2,7,1,105,Giovanni,Wuckert,1989-4-28
14,4,1,1256,Zackary,Jaskolski,1961-11-14
13,4,1,1197,Sally,Stokes,1975-3-29
5,1,1,404,Terrence,Purdy,1969-6-15
7,14,1,672,Margret,Bradtke,1984-1-24
7,6,2,602,Maureen,Stark,1994-12-16
9,16,1,931,Heather,Goyette,1951-7-10
9,11,1,881,Rosario,Kohler,1969-3-24
2,6,2,98,Gerry,Daugherty,1986-6-22
2,7,2,111,Destany,Jacobs,1981-7-31
9,15,1,922,Caitlyn,Nikolaus,1949-12-16
14,5,1,1265,Fanny,Sawayn,1956-6-30
7,10,1,624,Vincenzo,Kozey,1975-10-20
7,4,1,574,Ciara,Prosacco,1958-11-14
5,7,1,465,Chandler,Borer,1957-3-12
10,6,2,998,Kraig,Ortiz,1953-8-6
5,1,1,405,Waldo,Swaniawski,1951-7-30
7,10,2,635,Jeramy,Kiehn,1981-4-10
2,8,1,119,Dedrick,Yundt,1973-2-26
2,2,1,63,Eugene,Kuhlman,1960-8-11
9,9,2,863,Dedrick,Conn,1965-5-11
14,5,1,1270,Susie,Labadie,1957-10-27
9,3,1,802,Ora,Mohr,1956-1-17
10,5,1,981,Delphine,Lindgren,1980-11-22
3,17,1,352,Chanel,Dare,1980-5-19
2,4,1,78,Shany,Kessler,1983-10-12
2,11,2,162,Loyal,Mertz,1964-11-21
8,10,1,775,Hettie,Kris,1969-1-21
14,1,1,1238,Jon,Pagac,1973-1-16
8,4,1,728,Katrina,Kovacek,1962-5-29
3,12,1,304,Jaime,Barrows,1968-2-25
14,11,1,1337,Casey,Gibson,1988-10-15
12,1,2,1140,Melany,Blanda,1978-1-11
5,2,1,415,Nelda,Hartmann,1973-8-30
10,6,1,990,Nelle,Gislason,1988-2-15
3,12,1,306,Gunnar,Hartmann,1977-7-6
11,6,1,1080,Hoyt,Nikolaus,1987-4-21
...
My instructions/answers:
Eclipse Photon project for Challenge
Requirements:
- Java 8
- Gradle 4.3
- Jackson 2.4
- JUnit 4.12
Build with Gradle:
Gradlew.bat build
Execute main in /src/main/java/org/challenge/Task1.java, possible arguments to control the data sort order and employee sort order are:
[ORIGINAL/ID/LASTNAME/FIRSTNAME/BIRTHDATE] [ASC/DESC]
e.g.
java ... org.challenge.Test1 LASTNAME DESC
java ... org.challenge.Test1 ID
java ... org.challenge.Test1
Run unit tests under /src/test/java/*:
Gradlew.bat test
For Javadoc:
Gradlew.bat javadoc
Questions
- Time complexity is linear or O(n) without sorting, i.e. ORIGINAL, each line of the CSV file is processed once, map insertions and gets are O(1) for unsorted hash maps or O(log n) for sorted maps. Space complexity is O(n).
- Use ID command-line argument to obtain all data sorted by ID, the argument ORIGINAL (default) will use CSV data order.
- Use ID, LASTNAME, FIRSTNAME or BIRTHDATE arguments to set sort order of employees, optionally with ASC or DESC to set sort direction, the default is ascending.
- The program will output some calculated average ages for the company and one sample division, team and manager at the end. I didn't add command-line arguments for this.
Assumptions
Employee birthdate is always in yyyy-M-D format. The CSV file can be read entirely into available heap. A badly formatted CSV row will end the parsing operation. File reading will not be retried. Integer data type can contain all IDs. Long data type can contain the total age in days of all employees during the scope of the average age calculation. Average age of all employees in scope is rounded down to the nearest day.