Nate wants to see more than data structures in a REPL.
Goal: see a text-based calendar of hours worked by day and week."With the power of the predicate, I summon thee from the bag of data!"If data items share structure, you only need one predicate function, not separate functions per "type".Common pattern: filter then map, then reduceOutput:Looks like a calendar with one line per weekShow daily total for each dayShow weekly totalProblem: how do we split the days into weeks?"I don't remember split-weeks in the Clojure cheat sheet.""What day does your week start on? My week starts on Tuesday.""I always assume that when a programmer says 'most', they mean 'most of the people around me'." "I actually think it just means 'me'.""Anecdata: when you have two anecdotes, it makes data."Create higher-level summaries: entry โ day โ weekSift through data at the level of your question.Problem: A date identifies a "day", but what identifies a week? A "week ID", so to speak.It would be nice if that week ID was ordered, so we could sort on it.Idea: Use the date of the first day of the weekuniq, sortable, spans yearsweeks that start on Tuesday will never have the same ID as weeks that start on SundayMight as well have the starting day of the week in the map too: :sunday, :tuesday, etc.Convenient to have two different views for the same data in the same data structureimmutable data won't change and get inconsistent.it's pre-calculated if you need to use it a lot vs just using a predicate "on the fly".Want to group-by the week:need a predicate from "date ID" โ "week ID": (week-id starting-day-of-week day)need to take a "date" and produce "date of first day in week"Eg. (week-id :sunday {:date "2019-03-08" ...}) => "2019-03-03""You're teaching Clojure core how to describe your data by giving it vocabulary."Write something that converts data to data, not data to println.Principle: move the I/O to the edgesmake the println a trivial step at the endeven making the strings is a data transformeverything prior to the println could be tested, even the formatting!"Test it to the level of detail you can tolerate."Much easier to reason about pure functions.015: Finding the Time016: When 8 - 1 = 6017: Data, at Your Service018: Did I Work Late on Tuesday?filter, map, reducegroup-bysort-by with partition-bytake-while, drop-while, and split-withCode sample from this episode:
(ns time.week-05
(:require
[time.week-04 :as week-04]
[java-time :as jt]
))
; Date helpers
(defn start-of-week
"Get the the starting date of the week containing local-date. The week will
start on the named day. Eg. :sunday"
[starting-day-of-week local-date]
(jt/adjust local-date :previous-or-same-day-of-week (jt/day-of-week starting-day-of-week)))
(defn week-dates
"Create a week's worth of dates starting from the given date."
[starting-date]
(->> (range 0 7)
(map #(jt/plus starting-date (jt/days %)))))
; Aggregates
(defn sum-minutes
"Sums the minutes for all kinds: entry, day, week"
[entries]
(->> entries
(map :minutes)
(reduce +)))
; Conversions
(defn entries->days
"Convert a seq of entries to days."
[entries]
(->> (group-by :date entries)
(map (fn [[date xs]] {:date date :minutes (sum-minutes xs)}))))
(defn days->week
"Convert a seq of days into a week. Week is picked by first date."
[starting-day-of-week days]
(let [lookup (into {} (map (juxt :date identity) days))
starting-date (start-of-week starting-day-of-week (:date (first days)))
all-days (->> (week-dates starting-date)
(map #(or (get lookup %)
{:date %
:minutes 0})))]
{:starting-day-of-week starting-day-of-week
:date starting-date
:days (vec all-days)
:minutes (sum-minutes all-days)}))
(defn partition-weeks
[starting-day-of-week days]
(->> days
(sort-by :date)
(partition-by #(start-of-week starting-day-of-week (:date %)))))
(defn days->weeks
"Convert a seq of days into an ordered seq of weeks."
[starting-day-of-week days]
(->> (partition-weeks starting-day-of-week days)
(map (partial days->week starting-day-of-week))))
(comment
(->> (week-04/log-times "time-log.txt")
(entries->days))
(->> (week-04/log-times "time-log.txt")
(entries->days)
(days->weeks :sunday))
)