You’ve started a project to build a new weather data app with a “Google Finance”-style UI that will show time-series charts of numeric temperature data. Specifically, you’ll be working on the logic to fetch the correct “resolution” of data based on the time range over which the user is viewing temperatures.
When viewing data over large time ranges (i.e. multiple weeks) we want to show hourly data, but when the user drills down to a smaller interval, we want to show more detail. The temperature data comes from an external service you don’t control, and can be slow to load, but we want to give the user a high- performance zooming function even if the correct data has not loaded yet. Therefore, your controller will locally cache data and reuse it as much as possible. Your job will be to implement the controller used that communicates with the UI to render the final graph and handle client actions. You will not be responsible for rendering a chart or doing any direct UI work - just the logic to decide which temperature data to show when, and to fetch new data from the backend.
You’ve been asked to render a time-series temperature chart from 1pm to 2pm. This requires one-minute data, and your local data cache is empty (the application has just started). You should:
Expected Call Pattern (see API docs below):
You get called with:
// Jan 1st, 2000 1pm - 2pm GMT
new Controller(ui, backend, 946731600, 946735200)
Your code should call:
// Fetch one minute data from the backend
backend.requestTemperatureData(946731600, 946735200, 60)
// Render a blank chart - we have nothing to show yet
ui.setChartData(null x 60)
Sometime later, you get called with:
// Note - the start and end times might not match your initial call! This could
// be because you have multiple requests in flight, or because the server only
// had partial data available
controller.receiveTemperatureData(946731600, 946735200, 60, data)
Your code should call:
// Pass the one-minute data straight through to the UI
ui.setChartData(data)
Continuing from Example 1, you’ve already rendered the chart from 1pm to 2pm and cached the data. The user changes the start time of the chart to 12:30pm. You should:
Expected Call Pattern (see API docs below):
You get called with:
// Jan 1st, 2000 12:30pm GMT
controller.setStartTime(946729800)
Your code should call:
// Jan 1st, 2000 12:30pm - 1pm GMT
backend.requestTemperatureData(946729800, 946731600, 60)
// Array with 30 nulls followed by cached data from previous call
ui.setChartData(null x 30 + cachedData)
Sometime later, you get called with:
controller.receiveTemperatureData(946729800, 946731600, 60, data)
Your code should call:
// Combine cached and new data to return complete result
ui.setChartData(data + cachedData)
Continuing from Example 2, the user now changes the end time of the chart to 3pm. According to the business rules (see below), this means we should now start showing five-minute-resolution datapoints (instead of one-minute datapoints). You should:
Expected Call Pattern (see API docs below):
You get called with:
// Jan 1st, 2000 3:00pm GMT
controller.setEndTime(946738800)
Your code should call:
// Jan 1st, 2000 12:30pm - 3pm GMT at 5 minute granularity
backend.requestTemperatureData(946729800, 946738800, 300)
// Take a five-minute average of the data already cached
rolledUpCachedData = ...
// Array with 18 averaged data points from cache and 12 nulls
ui.setChartData(rolledUpCachedData + (null x 12))
Sometime later, you get called with:
controller.receiveTemperatureData(946729800, 946738800, 300, data)
Your code should call:
// Combine cached and new data to return complete result
ui.setChartData(data)
The data resolution to use depends on the chart range (difference between end time and start time):
| Range | Resolution |
|---|---|
| [0, 2 hours) | 1 minute |
| [2 hours, 1 week) | 5 minutes |
| [1 week, ∞) | 1 hour |
The rules for caching and making backend calls are:
Your solution will be judged on these criteria:
Once you receive this coding question, we ask that you return a response within one week. Your code may be written in the language of your choice, using your tools, IDEs, books, StackOverflow, etc. - anything you would use in your normal course of work. However, you should not ask other people for help with this problem and you should not incorporate third-party code by cutting and pasting - please make this work your own.
Please submit your final code, along with any relevant supporting material, to jobs@getcensus.com. You may also contact us at any time if you need a clarification on the question, or if you believe you have found an error.
Your code must implement this API (described here in TypeScript - translate it
to your language of choice if you are not using TypeScript for this problem).
Your final product must, at minimum, implement the Controller constructor
and the methods setStartTime, setEndTime, and receiveTemperatureData.
type Resolution = 60 | 300 | 3600
type Datapoint = number | null
type Datapoints = Datapoint[]
interface Backend {
/**
* Issues a call to a remote service to fetch some data. The call is
* asynchronous - you will be called back via the receiveTemperatureData
* method to get the result. Service calls are guaranteed to succeed.
*
* You do not need to implement this method. A fully functional version of
* this function will be available in production. However, the production
* implementation is being developed in parallel by another team and is not
* available to you at this time. Utilize as necessary in your development.
*
* @param startTime - The epoch time of the first datapoint to fetch,
* inclusive
* @param endTime - The epoch time of the last datapoint to fetch, exclusive
* @param resolution - The period, or granularity, of the data to fetch, in
* seconds.
*/
requestTemperatureData(startTime: number,
endTime: number,
resolution: Resolution): void
}
interface UI {
/**
* Renders a chart on screen with the given datapoints, which are simply an
* array of floating point values. Each time this method is called the chart
* is cleared and re-rendered. This is the output of your algorithm.
*
* You do not need to implement this method. A fully functional version of
* this function will be available in production. However, the production
* implementation is being developed in parallel by another team and is not
* available to you at this time. Utilize as necessary in your development.
*
* @param datapoints - An array of values to show on screen. A null in this
* array means is not yet data available for the given point.
*/
setChartData(datapoints: Datapoints): void
}
class Controller {
/**
* Initializes your object with the starting chart range. You should perform
* any service calls needed to render the chart as quickly as possible. The
* startTime and endTime are guaranteed to be aligned with the chart period;
* i.e. if the chart covers a four week span, startTime and endTime will be
* aligned on hourly boundaries; if the chart covers a 36 minute span,
* startTime and endTime will be aligned on one-minute boundaries.
*
* @param startTime - The first datapoint to be rendered, inclusive, in
* seconds since the epoch.
* @param endTime - The last datapoint to be rendered, exclusive, in seconds
* since the epoch.
*/
constructor(ui: UI, backend: Backend, startTime: number, endTime: number) {
// TODO: implement this method
}
/**
* Changes the startTime of the chart to a new value. As in the constructor,
* the startTime is guaranteed to be aligned on a boundary appropriate to
* the chart period.
*
* This method will be called by external user actions and drives your
* algorithm.
*
* @param startTime - The new first datapoint to be rendered, inclusive, in
* seconds since the epoch.
*/
setStartTime(startTime: number): void {
// TODO: implement this method
}
/**
* Changes the endTime of the chart to a new value. As in the constructor,
* the endTime is guaranteed to be aligned on a boundary appropriate to the
* chart period.
*
* This method will be called by external user actions and drives your
* algorithm.
*
* @param endTime - The new last datapoint to be rendered, exclusive, in
* seconds since the epoch.
*
*/
setEndTime(endTime: number): void {
// TODO: implement this method
}
/**
* Callback method to finish asynchronous service calls.
*
* This method will be called exactly once, asynchronously, after you call
* {@link Backend.requestTemperatureData}
*
* @param startTime - The epoch time of the first datapoint returned,
* inclusive
* @param endTime - The epoch time of the last datapoint returned,
* exclusive
* @param resolution - The period, or granularity, of the data returned,
* in seconds.
* @param datapoints - An array of datapoints for the requested range
*/
receiveTemperatureData(startTime: number,
endTime: number,
resolution: Resolution,
datapoints: Datapoints): void {
// TODO: implement this method
}
}