1
\$\begingroup\$

I had an interview yesterday where I was asked to write a function that would take in a list containing a date and the amount of money spent on that date, and to output the total amount spent per month. The data was for an energy company, so the amount spent corresponds to the amount of energy used that day.

Some conditions were:

  • The list will only contain 1 years worth of data
  • This list is in the order each usage/transaction occurred
  • But the list can start at any point and date within the year (i.e if it started on Feb 2nd 2017, it would only contain data up till Feb 1st 2018)

They left it kind of open after that on how to proceed. I didn't manage to get it done in the interview, although I think I had the right approach.

The sample data looks like this:

(("2017-02-13", 1200.00), ("2017-02-19", 50.00), ("2017-04-10", 100.45), etc..)

My solution would output a table similar to the following:

    Month-Year   |  Usage 
--------------------------------
January-2017     |  2400.0
February-2017    |  1250.0
April-2017       |  100.45
May-2017         |  225.0
December-2017    |  460.9
January-2018     |  1550.0

My code is below. Please let me know if you think there is simpler way to do this. I thought maybe instead of a Map, I could have another class called MonthlyUsage, and make a list of that instead to hold the data, but I wasn't too sure how to implement that with this approach.

DailyUsage.java

package com.energy;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

public class DailyUsage {

    private Date date;
    private double usage;
    private Calendar c = Calendar.getInstance();

    public DailyUsage(String dateStr, double usage) throws ParseException {

        Date formattedDate = new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        this.date = formattedDate;
        this.usage = usage;
    }

    public int getMonth() {

        c.setTime(date);
        return c.get(Calendar.MONTH);
    }

    public String getMonthName() {

        c.setTime(date);
        String month = c.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
        return month;
    }

    public double getUsage() {
        return usage;
    }

    public int getYear() {
        c.setTime(date);
        int year = c.get(Calendar.YEAR);
        return year;        
    }
}

EnergyCalculator.java

package com.energy;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class EnergyCalculator {

    public static void main(String[] args) throws ParseException {

        List<DailyUsage> dailyUsageList = populateList();
        Map<String, Double> monthlyUsage = calculateMonthlyUsage(dailyUsageList);

        System.out.println("Month-Year \t | \tUsage "
                + "\n--------------------------------");
        for (Map.Entry<String, Double> use : monthlyUsage.entrySet()) {
            System.out.println(use.getKey() + "\t | \t" + use.getValue());
        }
    }

    protected static Map<String, Double> calculateMonthlyUsage(
            List<DailyUsage> usageList) {

        // Map to store month and usage
        Map<String, Double> usageMonthMap = new LinkedHashMap<String, Double>();

        for (DailyUsage usage : usageList) {

            // SB for making unique key of month and year
            StringBuilder combinedKey = new StringBuilder();
            combinedKey.append(usage.getMonthName() + "-" + usage.getYear());

            if (!usageMonthMap.containsKey(combinedKey.toString())) {
                usageMonthMap.put(combinedKey.toString(), usage.getUsage());
            } 
            else {
                double currentUsage = usageMonthMap.get(combinedKey.toString());
                currentUsage += usage.getUsage();
                usageMonthMap.put(combinedKey.toString(), currentUsage);
            }
        }

        return usageMonthMap;
    }

    private static List<DailyUsage> populateList() throws ParseException {

        // Test data
        List<DailyUsage> dailyUsageList = new ArrayList<DailyUsage>();
        dailyUsageList.add(new DailyUsage("2017-01-20", 1200.00));
        dailyUsageList.add(new DailyUsage("2017-01-24", 1200.00));
        dailyUsageList.add(new DailyUsage("2017-02-13", 1200.00));
        dailyUsageList.add(new DailyUsage("2017-02-19", 50.00));
        dailyUsageList.add(new DailyUsage("2017-04-10", 100.45));
        dailyUsageList.add(new DailyUsage("2017-05-19", 225.00));
        dailyUsageList.add(new DailyUsage("2017-12-20", 400.50));
        dailyUsageList.add(new DailyUsage("2017-12-29", 60.40));
        dailyUsageList.add(new DailyUsage("2018-01-05", 700));
        dailyUsageList.add(new DailyUsage("2018-01-07", 150));
        dailyUsageList.add(new DailyUsage("2018-01-15", 700));

        return dailyUsageList;
    }
}
\$\endgroup\$
1
\$\begingroup\$

Your code seems fine. Using a Map makes sense.

That said, in Java 8 there are easier ways to implement such aggregation of data, using Streams:

protected static Map<String, Double> calculateMonthlyUsage(List<DailyUsage> usageList)
{
    return
        usageList.stream()
                 .collect(Collectors.groupingBy(usage -> usage.getMonthName() + "-" + usage.getYear(),
                                                LinkedHashMap::new,
                                                Collectors.summingDouble(DailyUsage::getUsage)));
}

Regarding your current implementation, I'd change the following:

StringBuilder combinedKey = new StringBuilder();
combinedKey.append(usage.getMonthName() + "-" + usage.getYear());

to:

String combinedKey = usage.getMonthName() + "-" + usage.getYear();

and replace any combinedKey.toString() with combinedKey.

You can also replace:

double currentUsage = usageMonthMap.get(combinedKey.toString());
currentUsage += usage.getUsage();
usageMonthMap.put(combinedKey.toString(), currentUsage);

with:

usageMonthMap.put(combinedKey, usageMonthMap.get(combinedKey)+usage.getUsage());
\$\endgroup\$
0
\$\begingroup\$

The interviewer assume that your code will works. What he may have expected is more OO designs and other usual choices when coding in Java.

Object Oriented design

The only type that you have introduced is DailyUsage and all the logic is into your main class. This EnergyCalculator contains only static methods with almost no inputs. How can someone reuse this code ?

You can also ask the format of the input, do you receive a java.util.List or a String containing a Csv or a custom format like the one in your sample ?

A ConsumptionReport may be a good choice to introduce some separation of concerns. Separation of concerns is always a good thing to do because it improve the modularity of your code.

An important concept (for your report) is YearAndMonth, you may add such class with a static factory method or a constructor that receive DailyUsage to create it. This one may be used as key in your map and thus you have to carefully implements equals and hashCode because you know that Map rely on them.

Java Internals

By implementing equals and hashCode in YearAndMonth you proof that you know the internals of a map. You can use the Formatter to produce your report (and YearAndMonth can implements Formattable so that you demonstrate that you have some knowledge of the api.

Another choice may be the usage of BigDecimal instead of double which is better for floating point arithmetics.

Testing

Sometimes it is better to start by writing your tests and them implement and reactor your code with the tests as support to proof that "it works". But in interviews with limited time it may be a bad choice because you may loose time by writing the tests than really focusing on the implementation which is more valuable for the interviewer. You can always explain this choice.

But if you have some time at the end, then write them.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.