I'm working through the challenges on Reddit's /r/DailyProgrammer subreddit to practice using Clojure, a language I've started to learn. Last time I got some user input and wrote it to screen, today I was tasked with building a calculator of some sort, to calculate some value that would be of use in my day to day life.
For this task, I ended up settling on writing a program to calculate the hours left to work in the week given the hours I'm supposed to work, and the hours I already have. The solution I arrived at takes in a seq of vectors, containing information on my start time, end time, and the time I took over lunch, and works out the working hours remaining in the week. Here's my code:
(defn minutes-to-decimal [minute] (float (/ minute 60))) (defn convert-24-hour-time-to-decimal [time] (let [hour (subs time 0 2) minute (subs time 2 4)] (+ (Integer. hour) (minutes-to-decimal (Integer. minute))))) (defn hours-worked-during-day ([[start-time end-time lunch]] (- (convert-24-hour-time-to-decimal end-time) (convert-24-hour-time-to-decimal start-time) lunch))) (defn hours-worked-during-week "Expects a seq of vectors of start and end times" [hours] (reduce (fn [total record] (+ total (hours-worked-during-day record))) 0 hours)) (defn hours-left-to-work-this-week [total worked] (- total worked)) (defn easy-challenge-2  (hours-left-to-work-this-week 37.5 (hours-worked-during-week [["0900" "1730" 1] ["0800" "1700" 0.5] ["0700" "1800" 1]])))
The first function minutes-to-decimal is straightforward in that it simply converts a minute value (0 to 59) to a decimal representation of what that minute means in the context of an hour. For example, the minute value of 15 is the same as the decimal representation 0.25, 30 minutes becomes 0.5, and so on. I need this simple conversion so that I can do my time calculations in base 10.
The second function convert-24-hour-time-to-decimal takes a string in the 24 hour format "HHMM" (so examples like "1725" or "0740" would both work) and returns a decimal representation of that time. It sub-strings out the hour and minute parts of the argument, uses minutes-to-decimal to convert the minutes to a decimal value, and then returns the sum of hour and the converted minute. So the time "0930" would convert into an hours value of 09 and a minutes value of 30, which would be converted into a minutes value of 0.5. The sum of those two would be 9.5, and that's what would be returned.
The third function hours-worked-during-day expects a vector of start-time, end-time, and time taken for lunch, and returns the total hours worked for that day. It does this by destructuring the vector into its component parts (start-time, end-time, and lunch) and then subtracting the decimal version of start-time (computed using the convert-24-hour-time-to-decimal function) and lunch from the also decimalised end-time. It uses the result of that subtraction as the return value.
The fourth function hours-worked-during-week expects a seq of vectors containing a start-time, and end-time, and a lunch-time for the days that I have worked this week, it then returns the sum of all the hours I've so far worked. It does this by using reduce to cycle through the seq of vectors passed in, applying a function to add the running total of hours to the hours-worked-during-day for the vector it's currently looking at.
The function hours-left-to-work-this-week is a very simple function, which simply subtracts one number from another. Those being the total hours I'm due to work that week, and the number of hours I've actually worked that week.
Lastly, the easy-challenge-2 function provides an example use of the calculator, by supplying hours-worked-during-week with some sample values, and passing that into hours-left-to-work-this-week along with the number of hours I'm supposed to be working this week, which is 37.5. Running the easy-challenge-2 function in a REPL will produce the answer 11.5 as the number of hours left to work.
How do I think I did?
It's hard to know how I did on this task. I created some functions that gave me a right answer, but I'm not sure I've managed it as elegantly as I'd have liked. For one thing I've erred on the side of long, explanatory function names, sometimes even for very simple operations. For instance, hours-left-to-work-this-week makes the calling code very clear about what it's doing, but it's not abstracting away any serious functionality, so I'm not clear on whether I should inline that code or not. Also, my code expects the start and end times to be passed in as strings, but the lunch time to be passed in as a number, which isn't very consistent. Hopefully elegance is something that will come with practice and experience.