Search code examples
vectorclojure

Date periods in clojure


I have a data structure like this:

[{ :2007-08-05 [ { :meat-weight-gain 100} {:meat-weight-loss 80} {:meat-balance 20}]}, 
 { :2007-08-06 [ { :meat-weight-gain 10} {:meat-weight-loss 60} {:meat-balance -30}]},
 { :2007-08-07 [ { :meat-weight-gain 40} {:meat-weight-loss 80} {:meat-balance -70}]}
 { :2007-08-08 [ { :meat-weight-gain 100} {:meat-weight-loss 0} {:meat-balance 30}]}]

How can i iterate through it and return the data period of when the meat balance was negative? A sample data would be something like this:

[ {:end-period-balance -70, :period-start 2007-08-06, :period-end 2007-08-07 } ]

Other than that, can I improve my data structure or it is already ok? If yes, how? Thank you very much.


Solution

  • i would advice you to change your data shape to a list of tuples, each containing date and map of balance data. Just like this:

    (def data [[:2007-08-05 { :meat-weight-gain 100 :meat-weight-loss 80 :meat-balance 20}], 
               [:2007-08-06 { :meat-weight-gain 10 :meat-weight-loss 60 :meat-balance -30}],
               [:2007-08-07 { :meat-weight-gain 40 :meat-weight-loss 80 :meat-balance -70}]
               [:2007-08-08 { :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance 30}]
               [:2007-08-09 { :meat-weight-gain 19 :meat-weight-loss -20 :meat-balance -10}]])
    

    then it would be easy to classify the periods by weight gain/loss (using partition-by) and collect needed info:

    user> (let [parts (partition-by #(-> % second :meat-balance neg?) data)]
            (keep #(let [[p-start _] (first %)
                         [p-end {balance :meat-balance}] (last %)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -70} 
    ;;    {:period-start :2007-08-09, :period-end :2007-08-09, :end-period-balance -10})
    

    or a list of maps including date:

    (def data [{:date :2007-08-05 :meat-weight-gain 100 :meat-weight-loss 80 :meat-balance 20}, 
               {:date :2007-08-06 :meat-weight-gain 10 :meat-weight-loss 60 :meat-balance -30},
               {:date :2007-08-07 :meat-weight-gain 40 :meat-weight-loss 80 :meat-balance -70}
               {:date :2007-08-08 :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance 30}
               {:date :2007-08-09 :meat-weight-gain 100 :meat-weight-loss 0 :meat-balance -10}])
    
    user> (let [parts (partition-by #(-> % :meat-balance neg?) data)]
            (keep #(let [{p-start :date} (first %)
                         {p-end :date balance :meat-balance} (last %)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -70} 
    ;;    {:period-start :2007-08-09, :period-end :2007-08-09, :end-period-balance -10})
    

    UPDATE

    if you really need your initial data format, then you can use the same approach, just redefining values retrieval parts:

    user> (defn meat-balance [rec]
            (some :meat-balance (-> rec first second)))
    
    user> (let [parts (partition-by #(-> % meat-balance neg?) data)]
            (keep #(let [p-start (-> % first ffirst)
                         p-end (-> % last ffirst)
                         balance (-> % first meat-balance)]
                     (when (neg? balance)
                       {:period-start p-start
                        :period-end p-end
                        :end-period-balance balance}))
                  parts))
    ;;=> ({:period-start :2007-08-06, :period-end :2007-08-07, :end-period-balance -30})