Search code examples
clojure

Getting date intervals from vector


I have a day ordered vector in clojure that's something like

(def a [{:day #inst "2017-01-01T21:57:14.873-00:00" :balance 100.00},
        {:day #inst "2017-01-05T21:57:14.873-00:00" :balance -50.00},
        {:day #inst "2017-01-10T21:57:14.873-00:00" :balance -100.00},
        {:day #inst "2017-01-14T21:57:14.873-00:00" :balance 50.00},
        {:day #inst "2017-01-17T21:57:14.873-00:00" :balance -200.00}])

I would like to get all date intervals where balance is negative. The period ends when balance gets positive on next position or when balance changes its value but keeps negative, like:

[{:start #inst "2017-01-05T21:57:14.873-00:00"
  :end #inst "2017-01-09T21:57:14.873-00:00"
  :value -50.00},
 {:start "2017-01-10T21:57:14.873-00:00"
  :end "2017-01-13T21:57:14.873-00:00"
  :value -100.00},
 {:start "2017-01-17T21:57:14.873-00:00"
  :value -200.00}]

I've found this and this but I couldn't adapt to my data. How can I do it?


Solution

  • Cheating a little with the dates by using this not yet implemented function, which is supposed to decrement a date:

    (defn dec-date [d] d)
    

    This would be one way to solve the problem:

    (->> a
         (partition-by #(-> % :balance neg?))
         (drop-while #(-> % first :balance pos?))
         (mapcat identity)
         (map (juxt :day :balance))
         (partition 2 1)
         (map (fn [[[date-1 val-1] [date-2 val-2]]]
                (if (neg? val-1)
                  {:start date-1
                   :end   (dec-date date-2)
                   :value val-1}
                  {:start date-2
                   :value val-1}))))) 
    
    ;;=> ({:start "05/01", :end "10/01", :value -50.0} {:start "10/01", :end "14/01", :value -100.0} {:start "17/01", :value 50.0})
    

    If dec-date was properly implemented then the first :end would be "09/01" rather than "10/01" and the second :end would be "13/01" rather than "14/01", which would be the correct answer.

    Now hopefully an improved answer that will work for more edge cases:

    (->> a
         (partition-by #(-> % :balance neg?))
         (drop-while #(-> % first :balance pos?))
         (mapcat identity)
         (map (juxt :day :balance))
         (partition-all 2 1)
         (keep (fn [[[date-1 val-1] [date-2 val-2]]]
                 (cond
                   (neg? val-1) (cond-> {:start date-1
                                         :value val-1}
                                        date-2 (assoc :end (dec-date date-2)))
                   (pos? val-1) nil
                   :else {:start date-2
                          :value val-1}))))