[Challenge] Calculate Max, Min, and Averages 🏃

Code Challenge 20: August 23, 2017

Every week, we feature the type of brain-teasing question that might be asked in a full-stack developer’s job interview at places such as Google and Facebook.

Variations of week’s challenge were reported to have been asked in interviews at Amazon:


The Challenge

You’re training for a marathon and have a list of times in which you’ve completed your training runs. Write a function, averageFinder that will return the mean and mode of your race times. Make sure that you write your functions and find these answers from scratch – don’t use imported tools!

  • Function name: averageFinder
  • Input: an array with race times, each a natural number representing the minutes it took you to finish your run (you can presume that race times are rounded up to the nearest minute so we do not have to deal with seconds)
  • Output: an array, with mean time and mode time (in that order)
  • Example: averageFinder([500, 450, 400, 400, 375, 350, 325, 300]) => [387.5, 400]
    • Please include the above sample input array in your submission as a test.
  • Always remember to explain your code and the thought processes behind it!
  • As always solutions using imports to do all the heavy lifting such as itertools will not be considered for the winner (and are not what interviewers are looking for) – you should write your functions from scratch.
  • What if your interviewer had follow-up questions, for example asking for maximum, median, and minimum times instead of mean and mode? What if your input array did not have duplicate values? Don’t anticipate what exactly those follow-ups or changes may be, but try to write your code so that it is easily read, easily maintained, and can be adapted to potential modifications in the interviewer’s questioning.
Find out more about basic challenges.

Intermediate difficulty

You’re not satisfied with just looking at the historical data for your marathon training – you want your program to reflect your continued progress, returning your best, worst, and average run times. Write a function, timeKeeper that will help you out. timeKeeper will take an input, n a positive integer representing the number of minutes it took you to complete your latest run, and then return an array of the following vital statistics about all of your runs to date:

  • the longest time you’ve taken to date (maxTime)
  • the shortest (best) time you’ve taken to date (minTime)
  • the mean of all of your race times (meanTime)
  • the mode of all of your race times (modeTime)
  • the median of all of your race times (medianTime)
  • function name: timeKeeper
  • Input: a race time n, a natural number in minutes (you can presume that race times are rounded up to the nearest minute so we do not have to deal with seconds). Each time you insert a new time, n, it is added to an array that contains all of your historical race times.
  • Output: an array, with longest time, shortest time, mean time, mode time and median time (in that order)
  • Example: with an existing array of [500, 450, 400, 400, 375, 350, 325, 300] for your historical times and your new time of 320 to insert, timeKeeper(320) => [500, 300, 380, 400, 375]
    • Please include this example array and insertion in your test submission to make it easier for others to assess.
  • Always remember to explain your code and the thought processes behind it!
  • Make sure to write everything from scratch!
  • Interviewers often ask you for extra features or revisions after you’ve made the program, so for bonus points – can you also return whether or not (and by how much) your times are improving over time? You can presume that your array of race times is sorted by the time in which they were logged, with the first time you trained as the first entry in the array and the most recent time as the last entry in the array. You would show the value for improvements (or deterioration) of your times as the last entry in your results array, after the median.
  • If you’d prefer to use a class that is acceptable too
Find out more about intermediate challenges.

Hard Difficulty

Write timeKeeper as efficiently as possible (and try to include the bonus points question).

  • Don’t forget to explain your submission just as you would do in a job interview setting!
Find out more about hard challenges and Big O

Reply to this thread with a link to your code on repl.it and paste your properly formatted code to participate! Don’t just submit your code, remember to explain your solution, too! If you want to be considered as the winner, your function must have the correct name and provide output in the format specified above, you also need to abide by our other simple rules.

As always solutions using imports to do all the heavy lifting such as itertools will not be considered for the winner.

When including your repl.it link, please just give us the link and not the code preview! Just put a space before your URL so that the preview box doesn’t show – too many preview boxes can make the thread hard to read.


The fine print:

Click the links to find out more about:

Basic difficulty in Python. Mean is basic averaging, mode is calculated by implementing a hashmap and then finding the key with the largest value.

https://repl.it/KWzm/2

def averageFinder(times):
  mean = sum(times)/float(len(times))
  
  stats = {}
  for x in times:
    if x in stats:
      stats[x] += 1
    else:
      stats[x] = 1
  
  mode = max(stats,key=stats.get)
  
  return mean,mode
  
times = [500, 450, 400, 400, 375, 350, 325, 300]
print averageFinder(times)

Intermediate/hard difficulty in Python. Min/max use Python built-ins, mean and mode are calculated the same as in the basic difficulty version, median creates a temporary sorted array and returns the middle value or the mean of the two middle values, depending on array length.

https://repl.it/KXAm/1

class timeKeeper:
  
  # Initialize with empty array if no arguments passed
  def __init__(self,times = None):
    if times is None:
      self.times = []
    else:
      self.times = times
    
  # Add a new time and return current stats
  def add_time(self,n):
    times.append(n)
    return [self.max_time(), self.min_time(), self.mean_time(), self.mode_time(), self.median_time()]
  
  def max_time(self):
    return max(times)
    
  def min_time(self):
    return min(times)
    
  def mean_time(self):
    return sum(times)/float(len(times))
    
  def mode_time(self):
    stats = {}
    for x in times:
      if x in stats:
        stats[x] += 1
      else:
        stats[x] = 1
    
    return max(stats,key=stats.get)
    
  def median_time(self):
    sorted_times = sorted(times)
    time_len = len(sorted_times)
    if time_len % 2 == 0:
      return sum(sorted_times[time_len/2-1:time_len/2+1])/2.0
    else:
      return sorted_times[time_len/2]

times = [500, 450, 400, 400, 375, 350, 325, 300]
tk = timeKeeper(times)
print tk.add_time(320)

Basic using Ruby : https://repl.it/KXUg/0

Hard + Bonus
repl: https://repl.it/KX5c/1

from typing import *


def timeKeeper(new_race_time: int) -> List:

    historical_runs = list([500, 450, 400, 400, 375, 350, 325, 300])
    historical_runs.append(new_race_time)

    # initialization of values
    max_time: int = 0
    min_time: int = 10**6
    mode_time: int = None

    mode_count = dict()
    sum_of_all = 0

    # doing all the heavy lifting in a single pass over the run times
    for run_time in historical_runs:

        # MAX TIME CALCULATION
        if run_time > max_time:
            max_time = run_time

        # MIN TIME CALCULATION
        if run_time < min_time:
            min_time = run_time

        # accumulation of the sum of all numbers to be used for calculating the mean time
        sum_of_all += run_time

        # counting occurances of run times in a dict
        mode_count[run_time] = mode_count.get(run_time, 0) + 1

    # MEAN TIME CALCULATION
    mean_time = float(sum_of_all / len(historical_runs))

    # MODE TIME CALCULATION
    occurrence = 0

    for key, value in mode_count.items():

        # searching for highest occurrence value in the dict. They key for this value will be the mode time
        if value > occurrence:
            occurrence = value
            mode_time = key

    # returning -1 in case of no duplicate values (occurrence <= 1)
    if occurrence <= 1:
        mode_time = -1

    # IMPROVEMENT CALCULATION
    improvements = RunImprovements.calculate_improvements(historical_runs)

    # MEDIAN TIME CALCULATION
    median_index = int(len(historical_runs) / 2)

    # sorting the list of runs for the sake of median value calculation
    historical_runs = list(sorted(historical_runs))
    # I decided to sort it inplace to not use extra memory. In a real world
    # scenario it might be worth to keep the original order of the list for clarity

    # calculating median time in the cases of even and odd length of historical_runs list
    if len(historical_runs) % 2 == 0:
        median_time = float((historical_runs[median_index - 1] + historical_runs[median_index]) / 2)
    else:
        median_time = historical_runs[median_index]

    return [max_time, min_time, mean_time, mode_time, median_time, improvements]


class RunImprovements:

    def __init__(self, run_time, improved):

        self.run_time: int = run_time
        self.improved: bool = improved
        self.improvement_rate: float = None

    @staticmethod
    def calculate_improvements(historical_runs):

        runs: List['RunImprovements'] = list()

        for index, run_time in enumerate(historical_runs):

            if index == 0:
                run = RunImprovements(run_time=run_time, improved=False)

            else:
                run = RunImprovements(run_time=run_time, improved=historical_runs[index] < historical_runs[index - 1])
                if run.improved:
                    run.improvement_rate = float((historical_runs[index-1] / historical_runs[index]) - 1) * 100

            runs.append(run)

        return runs

    def __repr__(self):

        if self.improved:
            return "Run time: %s  Time improved: %s  Improvement Rate %s %%" % (self.run_time, self.improved, round(self.improvement_rate, 1))
        else:
            return "Run time: %s  Time improved: %s" % (self.run_time, self.improved)


result = timeKeeper(320)
print(result[:-1])

for improvement in result[-1]:
    print(improvement)

Output

[500, 300, 380.0, 400, 375]
Run time: 500  Time improved: False
Run time: 450  Time improved: True  Improvement Rate 11.1 %
Run time: 400  Time improved: True  Improvement Rate 12.5 %
Run time: 400  Time improved: False
Run time: 375  Time improved: True  Improvement Rate 6.7 %
Run time: 350  Time improved: True  Improvement Rate 7.1 %
Run time: 325  Time improved: True  Improvement Rate 7.7 %
Run time: 300  Time improved: True  Improvement Rate 8.3 %
Run time: 320  Time improved: False
2 Likes

Hard Difficulty : Python 3.6 -

This challenge doesn’t need detailed explanation, though included in comments.
Time complexity of averageFinder() is O(n) and of creating a timeKeeper() object is O(n*log(n)) [due to sorting] whereas addition of new time in timeKeeper() is effectively O(n)

def averageFinder(raceTimes):             # O(N+K), simply O(N)
    mean = 0
    counts = {}                           # keeps count of race times
    maxCount = 0                          # maximum occurance of a race time
    for time in raceTimes:                # O(N)
        mean += time
        counts[time] = counts[time]+1 if time in counts else 1
        if counts[time] > maxCount:
            mode = time
            maxCount = counts[time]
        elif maxCount == counts[time] and time < mode:
            mode = time
    mean /= len(raceTimes)
    
    return [mean, mode]
    

class timeKeeper:
    
    @staticmethod
    def insertIntoSortedList(sortedList, element):  # Best case O(1) worst case O(N+log(N)), i.e. O(N)
        elementInserted = False
        if len(sortedList) == 0:
            sortedList.append(element)              # O(1)
            elementInserted = True
        elif sortedList[0] > element:
            sortedList.insert(0, element)           # O(N)
            elementInserted = True
        elif sortedList[-1] < element:
            sortedList.append(element)              # O(1)
            elementInserted = True
        if not elementInserted:
            start = 0
            end = len(sortedList)
            while start <= end:                     # Binary search used to find place in sorted array, O(log(N))
                mid = (start + end)//2
                if sortedList[mid] == element:
                    sortedList.insert(mid, element)        # O(N)
                    elementInserted = True
                    break
                elif sortedList[mid] > element:
                    end = mid - 1
                else:
                    start = mid + 1
            if not elementInserted:
                if sortedList[mid] > element:
                    sortedList.insert(mid, element)       # O(N)
                else:
                    sortedList.insert(mid + 1, element)   # O(N)
                elementInserted = True
        

    def __init__(self, timeList=None):             # Initialize all the values, worst case O(N*log(N)+N) i.e. O(N*log(N))
        self.timeList = timeList if (not timeList is None) else []
        if type(self.timeList) == int:
            self.timeList = [self.timeList]
        self.sortedTimeList = sorted(self.timeList)    # O(N*log(N))
        self.__meanTime = None
        self.__modeTime = None
        self.__medianTime = None
        self.__minTime = None
        self.__maxTime = None
        self.previous = None
        self.counts = {}                     # keeps count of race times
        mean = 0
        maxCount = 0
        for time in self.timeList:           # O(N), used to keep counts and calculate mean
            mean += time
            self.counts[time] = self.counts[time]+1 if time in self.counts else 1
            if self.counts[time] > maxCount:
                mode = time
                maxCount = self.counts[time]
            elif maxCount == self.counts[time] and time < mode:
                mode = time
        if (not timeList is None):           # Calculate max, min, mean, mode and median if race time/s as input is/are given
            self.calculateAll(mean/len(self.timeList), mode)      # Mean, Mode already calculated

    def record(self, newTime):               # add race time for new trial, O(N)
        self.timeList.append(newTime)        # Adding new race time to the list
        timeKeeper.insertIntoSortedList(self.sortedTimeList, newTime)
        # Sorting the array is a must to find the median(atleast to my knowleadge), so instead of sorting race time list each time new value is added, I sort the list once during initialization and add new times in correct places in the sorted array
        # In the earlier case i.e. sorting is O(N*log(N)) where inserting into sorted list is O(N)
        self.counts[newTime] = self.counts[newTime]+1 if newTime in self.counts else 1      # increment its count by one
        if self.__meanTime is None:
            self.__maxTime = self.__minTime = self.__meanTime = self.__modeTime = self.__medianTime = newTime
            return self
        else:
            self.previous = [self.__maxTime, self.__minTime, self.__meanTime, self.__modeTime, self.__medianTime]   # Store previous stats
        trialsNo = len(self.timeList)
        mean = newTime if self.previous[2] == newTime else (self.previous[2]*(trialsNo - 1) + newTime)/trialsNo       # O(1)
        # Mean remains same if new time is equal to the mean
        # Otherwise instead of getting the sum of all race times which is O(N), used previous sum and added new time to get the current sum which is O(1) and divided the sum by total race trials to get mean(average)
        mode = newTime if ((self.counts[self.previous[3]] == self.counts[newTime] and newTime < self.previous[3]) or (self.counts[self.previous[3]] < self.counts[newTime])) else self.previous[3]           # O(1)
        # the newly added race time becomes Mode if its count is greater than all other or if its count is maximum(maybe equal to others) and it is lowest of them all
        # Otherwise it remains same as before
        median = newTime if self.previous[4] == newTime else None                    # O(1)
        # Mean, Mode and Median are calculated here for efficiency(reduce calculation, thus time)
        self.calculateAll(mean, mode, median)        
        return self                          # print the stats
        
    def examine(self):                       # inspect changes, included as BONUS
        if self.previous == None:
            print('Not enough evidence')
            return
        if self.previous[0] < self.__maxTime or self.previous[1] > self.__minTime:
            print(f'New record for {"best" if self.previous[1] > self.__minTime else "worst"} time of {self.__minTime if self.previous[1] > self.__minTime else self.__maxTime} seconds recently added.')
        if self.previous[2] > self.__meanTime:
            print(f'Average race time is improved by {round(self.previous[2] - self.__meanTime, 2)} seconds and {round((100 - (self.__meanTime*100/self.previous[2])), 2)}% from last time')
        else:
            print(f'No recent improvement in race time.')
        
    def history(self, getRaceTimes=False):
        print('The following data shows improvement in positive seconds and negative for descent for all the race trials in seconds :-')
        trialsNo = len(self.timeList)
        print([(self.timeList[i] - self.timeList[i+1]) for i in range(trialsNo) if i < (trialsNo - 1)])
        if getRaceTimes:
            print(f'History of all race times in order => {self.timeList}')
        return

    def calculateAll(self, mean, mode, median=None):     # O(1)
        trialsNo = len(self.timeList)                  # number of running trials
        self.__meanTime = mean
        self.__modeTime = mode
        # Already calculated before
        
        if not(median is None):
            self.__medianTime = median
        else:
            if trialsNo > 2:                               # find median from middle of sorted list of race times
                self.__medianTime = self.sortedTimeList[trialsNo//2] if trialsNo%2 else (self.sortedTimeList[trialsNo//2]+self.sortedTimeList[trialsNo//2 - 1])/2    # O(1)
            else:
                self.__medianTime = self.__meanTime        # if total recorded race times are less than 3, median and mean are equal

        self.__maxTime = self.sortedTimeList[-1]           # O(1)
        self.__minTime = self.sortedTimeList[0]            # O(1)

    def __repr__(self):                           # return max, min, mean, mode and median of all recorded run times in same order
        return str([self.__maxTime, self.__minTime, round(self.__meanTime, 3), self.__modeTime, self.__medianTime])


https://repl.it/K0y1/13

I thought inserting an element in a list at particular index would be O(1) but it is actually O(n).

1 Like

Solution to basic difficulty in Java
https://repl.it/KX1l/27

/*The method mean_mode first calculates the mode of the array, i.e the number with the
maximum number of occurrences. If there are more than one such values, the number with
higher value will be the mode. If none of the values repeat, the largest number will be the
mode. Then it calculates the mean of the elements in the array.*/
class Main{
public static void mean_mode(int[] time) {
    int mode = time[0];
    int maxCount = 0;
    int sum=0;
    for (int i = 0; i < time.length; i++) {
        int value = time[i];
        int count = 1;
        for (int j = 0; j < time.length; j++) {
            if (time[j] == value) count++;
            if (count > maxCount) {
                mode = value;
                maxCount = count;
            }
        }
    }
    for(int i=0;i<time.length;i++){
      sum=sum+time[i];
    }
    float mean=(float)sum/(time.length);
    System.out.print("["+mean+", "+mode+"]");
}
public static void main(String[] args) {
    int[] time = new int[] {500, 450, 400, 400, 375, 350, 325, 300};
    mean_mode(time);
}
}

Intermediate Solution
https://repl.it/K0IY

It uses ES6 JavaScript, and is broken down into tiny functions for easy maintainability/additions. The portion that makes it maintainable, is storing functions in a reusable manner as methods in an object named helpers. If there is any additional functionality required, more helper functions can be added to support anything else in the app.

Performance wise, I know the reduce calls are not so hot, but this is probably all the time I can contribute to this as of right now. I am sure it will also fail for other cases as well, but this is my final submission. Godspeed.

let historical = [500, 450, 400, 400, 375, 350, 325, 300]

const timeKeeper = (n, arr) => {
  historical.push(n)
  
  return [
    helpers.longestTime(arr),
    helpers.shortestTime(arr),
    helpers.mean(arr), 
    helpers.mode(arr),
    helpers.median(arr)
  ]
} 

const helpers = {
  sum: function(total, current) { return total + current },
  total: function(arr) { return arr.reduce(this.sum) },
  divide: function(a, b) { return a / b },
  mean: function(arr) { return this.divide(this.total(arr), arr.length) },
  median: function(arr) {
    let sorted = arr.sort()
    let i = Math.floor(sorted.length / 2)
    return sorted[i]
  },
  mode: function(arr) {
    return arr.reduce(
      (a,b,i,arr) => (arr.filter(c=> c === a).length >= arr.filter(c => c === b).length) ? a : b,  
      null
    )
  },
  longestTime: function(arr) {
    return arr.reduce((prev,cur) => prev > cur ? prev : cur)
  },
  shortestTime: function(arr) {
    return arr.reduce((prev,cur) => prev < cur ? prev : cur)
  }
}

// Run the program
timeKeeper(320, historical)

https://repl.it/KY0V/1#Basic Challenge

https://repl.it/KY2r/0 timeKeeper Intermediate difficulty

Remember, entries that do not include preformatted code samples and explanation will not be considered. A REPL link alone will not make the cut.

4 Likes

Aren’t the comments in the code enough?

We want to see your code posted right here for easy readability. In the challenge prompt it says:

4 Likes

timeKeeper Intermediate difficulty with bonus
I had to create a redirect link because it says I cannot post links to that host
http://bit.ly/Daniel_timeKeeperBonus

const timeKeeperList = [500, 450, 400, 400, 375, 350, 325, 300];
function timeKeeper(num) {
  timeKeeperList.push(num);

  const
    s    = timeKeeperList.map(n => n).sort(), // s as in sorted
    min  = s[0],                  // first element of a sorted list is the min
    max  = s[s.length - 1],       // last  element of a sorted list is the max
    mean = s.reduce((a, b) => a + b) / s.length, // sums all values and divides result by 2, mean

    // the module of odd numbers by 2 always returns 1, which is evaluated as true on the ternary
    // in that case, the median will be the middle element of the sorted list
    // the module of odd numbers by 2 is always 0, which is evaluated as false on the ternary
    // in that case it calculates the mean of the two middle elements
    median = s.length % 2 ? s[~~(s.length / 2)] : (s[s.length / 2 - 1] + s[s.length / 2]) / 2,
    temp = {};

  // The numbers in the list will become unique keys in the hashTable temp
  // the values of each key will be the numbers of occurrences of each key
  s.forEach(n => temp[n]++ || (temp[n] = 1));
  const
    // sorts a list of keys by their values in a descending order
    modeList = Object.keys(temp).sort((a, b) => temp[b] - temp[a]),
    // The mode only exists if the top value is greater that the previous one
    mode = modeList[0] > modeList[1] ? +modeList[0] : undefined;

  // declaring res outside just because I want to use a single line
  // arrow function for to map the array
  let res;
  
  const report = timeKeeperList
    // maps the array with the differences between the current value and the next
    // if the diference is not 0 it will return the negative value of that difference
    // that will show when the times are increasing or decreasing
    .map((v, i, list) => (res = v - list[i + 1]) && -res || res)

  // the last element of the  report is always NaN, hence the removal of it
  return [max, min, mean, mode, median, report.slice(0, report.length - 1)];
}

timeKeeper(320); // [ 500, 300, 380, 400, 375, [ -50, -50, 0, -25, -25, -25, -25, 20 ] ]
1 Like

timeKeeper Hard difficulty with bonus
This is basically some improvement techniques applied to the previous post
The performance is around 30% when compared to the pretty version of it.

Performatic code is usually not pretty

The optimizations are done basically by replacing good looking helpers and syntaxes
by old approaches.
E.g:

  • array.map replaced by a a for loop
  • let replaced by var

When the comments are removed the optimizer might work better and improve
it a little further.

I had to create a redirect link because it says I cannot post links to that host
timeKeeperBonusHard - Replit

const timeKeeperList = [500, 450, 400, 400, 375, 350, 325, 300];
function timeKeeper(num) {
  timeKeeperList.push(num);
  // cloning timeKeeperList
  const
    // pre-alocation with arrays is faster than .push
    // for performance I use that aproach since I know
    // beforehand the final size of my array
    s = new Array(timeKeeperList.length),
    temp = {},
    // this variable will save a call to
    // Object.keys(temp) later on
    uniqueKeys = [];

  var
    i   = 0,
    sum = 0;
  // array.map is slow
  // older versions of node.js let is slower than var
  for (; i < s.length; i++) {
    // shallow cloning and sum at once
    sum += (s[i] = timeKeeperList[i]);

    // The numbers in the list will become unique keys in the hash table temp
    // the values of each key will be the numbers of occurrences of each key
    // this line creates or increases the value of a given key
    if (!temp[s[i]]++) {
      temp[s[i]] = 1;
      uniqueKeys.push(s[i]);
    }
  }

  // sorts a list of keys by their values in a descending order
  uniqueKeys.sort((a, b) => temp[b] - temp[a]);

  // sorts the shallow cloned array
  s.sort();

  const
    min  = s[0],            // first element of a sorted list is the min
    max  = s[s.length - 1], // last  element of a sorted list is the max
    mean = sum / s.length,  // sums all values and divides result by 2, mean

    // the module of odd numbers by 2 always returns 1, which is evaluated as true on the ternary
    // in that case, the median will be the middle element of the sorted list
    // the module of odd numbers by 2 is always 0, which is evaluated as false on the ternary
    // in that case it calculates the mean of the two middle elements
    median = s.length % 2 ? s[~~(s.length / 2)] : (s[s.length / 2 - 1] + s[s.length / 2]) / 2,

    // The mode only exists if the top value is greater that the previous one
    mode = temp[uniqueKeys[0]] > temp[uniqueKeys[1]] ? +uniqueKeys[0] : undefined,

    // creates the report array with size 1 less than the
    // list of all recorded times for obvious reasons
    report = new Array(timeKeeperList.length - 1);

  // calculates the distances in minutes between a recorded time and its next
  // if the diference is not 0 it will return the negative value of that difference
  for (var ix = 0; ix < report.length; ix++) {
    report[ix] = -(timeKeeperList[ix] - timeKeeperList[ix + 1]) || 0;
  }

  return [max, min, mean, mode, median, report];
}

timeKeeper(320); // [ 500, 300, 380, 400, 375, [ -50, -50, 0, -25, -25, -25, -25, 20 ] ];

Refactored my code into a class type thing . .
Python 3.6 btw

from typing import *
from collections import defaultdict


class TimeKeeper:

    def __init__(self, new_race_time: int):

        # Data Structures
        self.historical_race_data = list([500, 450, 400, 400, 375, 350, 325, 300])
        self.historical_race_data.append(new_race_time)
        self.sorted_race_data = list(sorted(self.historical_race_data))

        # Caching of values that will be produced by the pre processor and later
        # accessed by the getter properties
        self.__max__: int = 0
        self.__min__: int = 10 ** 6
        self.__mode__: int = None
        self.__mode_count__: DefaultDict = defaultdict(int)
        self.__sum__: int = 0

        # Doing all the heavy lifting in a single pass over the race times
        self.pre_process()

    def pre_process(self):

        for race_time in self.historical_race_data:

            # MAX TIME CALCULATION
            if race_time > self.__max__:
                self.__max__ = race_time

            # MIN TIME CALCULATION
            if race_time < self.__min__:
                self.__min__ = race_time

            # accumulation of the sum of all numbers to be used for calculating the mean time
            self.__sum__ += race_time

            # counting occurances of race times in a dict
            self.__mode_count__[race_time] += 1

    def add_race_time(self, new_race_time: int, update=True):
        self.historical_race_data.append(new_race_time)

        if update:
            self.sorted_race_data = list(sorted(self.historical_race_data))
            self.pre_process()

    @property
    def min(self) -> int:
        return self.__min__

    @property
    def max(self) -> int:
        return self.__max__

    @property
    def mean(self) -> float:
        return float(self.__sum__ / len(self.historical_race_data))

    @property
    def mode(self) -> int:
        occurrence = 0

        for key, value in self.__mode_count__.items():

            # searching for highest occurrence value in the dict. They key for this value will be the mode time
            if value > occurrence:
                occurrence = value
                self.__mode__ = key

        # returning -1 in case of no duplicate values (occurrence <= 1)
        if occurrence <= 1:
            self.__mode__ = -1

        return self.__mode__

    @property
    def improvements(self) -> List['Improvements']:
        return Improvements.calculate_improvements(self.historical_race_data)

    @property
    def median(self) -> int:
        median_index = int(len(self.sorted_race_data) / 2)

        # calculating median time in the cases of even and odd length of sorted_race_data list
        if len(self.sorted_race_data) % 2 == 0:
            median_time = float((self.sorted_race_data[median_index - 1] + self.sorted_race_data[median_index]) / 2)
        else:
            median_time = self.sorted_race_data[median_index]

        return median_time

    @property
    def result(self) -> List:
        return list([self.max, self.min, self.mean, self.mode, self.median, self.improvements])
class Improvements:

    def __init__(self, race_time, improved):

        self.race_time: int = race_time
        self.improved: bool = improved
        self.improvement_rate: float = None

    @staticmethod
    def calculate_improvements(historical_race_data: List[int]) -> List['Improvements']:

        races: List['Improvements'] = list()

        for index, race_time in enumerate(historical_race_data):

            # catching index 0 because we will not be able to look at the previous index in this case
            if index == 0:
                race = Improvements(race_time=race_time, improved=False)

            # comparing current index to previous index in order to determine if we have improved and by how much
            else:
                race = Improvements(race_time=race_time, improved=historical_race_data[index] < historical_race_data[index - 1])
                if race.improved:
                    race.improvement_rate = float((historical_race_data[index - 1] / historical_race_data[index]) - 1) * 100

            races.append(race)

        return races

    def __repr__(self):

        if self.improved:
            return "Race time: %s  Time improved: %s  Improvement Rate %s %%" % (self.race_time, self.improved, round(self.improvement_rate, 1))
        else:
            return "Race time: %s  Time improved: %s" % (self.race_time, self.improved)

Execution

time_keeper = TimeKeeper(320)
result = time_keeper.result

print(result[:-1])

for improvement in result[-1]:
    print(improvement)

Output

[500, 300, 380.0, 400, 375]
Race time: 500  Time improved: False
Race time: 450  Time improved: True  Improvement Rate 11.1 %
Race time: 400  Time improved: True  Improvement Rate 12.5 %
Race time: 400  Time improved: False
Race time: 375  Time improved: True  Improvement Rate 6.7 %
Race time: 350  Time improved: True  Improvement Rate 7.1 %
Race time: 325  Time improved: True  Improvement Rate 7.7 %
Race time: 300  Time improved: True  Improvement Rate 8.3 %
Race time: 320  Time improved: False

@danieloduffy You have made a mistake in description of basic challenge. In example you have used timeKeeper() as input whereas the basic challenge only deals with averageFinder(). Please see to that. :slight_smile:

2 Likes

Basic difficulty in Python 3.6:

  • Mean is calculated by dividing the sum by the number of race times.
  • Mode is calculated by finding the number of occurrences for each time.
    https://repl.it/KZqY/0
def averageFinder(times):
    mean = sum(times) / len(times)  # averaging
    set_times = sorted(set(times))  # eliminate duplication
    num_occur = [times.count(t) for t in set_times]  # list comprehension
    mode_index = num_occur.index(max(num_occur))
    mode = set_times[mode_index]
    
    return [mean, mode]

race_times = [500, 450, 400, 400, 375, 350, 325, 300]
print(averageFinder(race_times))

The Basic difficulty in JavaScript:
https://repl.it/KZtm/1

function averageFinder(times){
  var prom;
  var mode = times[0];
  var max = 0;
  var sum=0;
  for(var i = 0; i<times.length; i++){
    sum += times[i];
    var value = times[i];
    var cont = 1;
    // to find the mode
    for (var j = 0; j<times.length; j++) {
      if (times[j] == value) cont++;
      if (cont > max) {
          mode = value;
          max = cont;
      }
    }
  }
  prom = sum/times.length;
  return [prom, mode];
}

var times = [500, 450, 400, 400, 375, 350, 325, 300];

console.log(averageFinder(times));

Basic Challenge. The comments explain the logic
http://bit.ly/2gcotDW

function averageFinder(s) {
  const mean = s.reduce((a, b) => a + b) / s.length;
  const temp = {};
  // The numbers in the list will become unique keys in the hashTable temp
  // the values of each key will be the numbers of occurrences of each key
  s.forEach(n => temp[n]++ || (temp[n] = 1));
  // sorts a list of keys by their values in a descending order
  const modeList = Object.keys(temp).sort((a, b) => temp[b] - temp[a]);
  // The mode only exists if the top value is greater that the previous one
  const mode = modeList[0] > modeList[1] ? +modeList[0] : undefined;

  return [mean, mode];
}

averageFinder([500, 450, 400, 400, 375, 350, 325, 300]); // [ 387.5, 400 ]

Same code with no comments, concise and minimal

function averageFinder(s) {
  const mean = s.reduce((a, b) => a + b) / s.length;
  const temp = {};

  s.forEach(n => temp[n]++ || (temp[n] = 1));
  const modeList = Object.keys(temp).sort((a, b) => temp[b] - temp[a]);
  const mode = modeList[0] > modeList[1] ? +modeList[0] : undefined;

  return [mean, mode];
}
1 Like

Whoops, thanks for pointing that out (sorry I made this at like 2am under the influence of coffee, just like all good startup things)

6 Likes