Search code examples
clojurefunctional-programming

functional way for balancing two vectors in clojure


I would balance two vector representing needs and availability of a product.

This is a canonical problem in Material Requirement Planning very easy in imperative style. Is possible in functional programming ?

I must pass from A1 to A2 with following priority: assign aval qty first for records with the same :cat , then to needs in early :day.

The format for :day is AAAAMMDD

(def A1
  {:avail [{:day 20190101 :qty  10  :mkey "AAABB" :cat "CO1"}
           {:day 20190101 :qty  20  :mkey "OS100" :cat "CO1"}
           {:day 20190102 :qty  50  :mkey "OS200" :cat "   "}
           {:day 20190103 :qty  50  :mkey "OS300" :cat "   "}
           {:day 20190104 :qty  40  :mkey "OS400" :cat "   "}]

   :needs [{:day 20190107 :qty -100 :mkey "OS200" :cat "   "}
           {:day 20190108 :qty  -50 :mkey "OS300" :cat "   "}
           {:day 20190109 :qty -100 :mkey "OS400" :cat "   "}
           {:day 20190217 :qty -100 :mkey "OS100" :cat "CO1"}]})


(def A2
  {:avail [{:day 20190101 :qty  0   :mkey "AAABB" :cat "CO1"}
           {:day 20190101 :qty  0   :mkey "OS100" :cat "CO1"}
           {:day 20190102 :qty  0   :mkey "OS200" :cat "   "}
           {:day 20190103 :qty  0   :mkey "OS300" :cat "   "}
           {:day 20190104 :qty  0   :mkey "OS400" :cat "   "}]

   :needs [{:day 20190107 :qty 0    :mkey "OS200" :cat "   "}
           {:day 20190108 :qty -10  :mkey "OS300" :cat "   "}
           {:day 20190109 :qty -100 :mkey "OS400" :cat "   "}
           {:day 20190217 :qty -70  :mkey "OS100" :cat "CO1"}]})

a possible java algorithm for obtaining A2 from A1

ArrayList avail = new ArrayList();
ArrayList needs = new ArrayList();
/* first assign based on same :cat */
for (int i=0;i< needs.size();i++) {
    // get needs record of index i..
    for (int j=0;j< avail.size();j++) {
         // get avail record of index j..
         if (need.cat != avail.cat)  // only same same cat ! 
                continue;
         balance the actuals records needs and avail
         updating relative qty, 
         trying to set need qty to zero decrementing avail
    }
}
/* now again without test on cat */
for (int i=0;i< needs.size();i++) {
    // get needs record of index i..
    for (int j=0;j< avail.size();j++) {
         // get avail record of index j..
         balance the actuals records needs and avail
         updating relative qty 
         trying to set need qty to zero decrementing avail
    }
}

Solution

  • I will do this with two functions:

    1. First function is to reduce demands base on one supply
    2. Second function uses above function to consume a list of supplies
    (defn consume
      "Reduce demands base on one supply"
      [can-consume? demands supply]
      (reduce (fn [{:keys [supply] :as ans} d]
                (let [qty (:qty supply)]
                  (if (can-consume? d supply)
                    (let [used (-> d :qty (+ qty))]
                      (-> ans
                          (update :needs conj (assoc d :qty (min 0 used)))
                          (assoc-in [:supply :qty] (max 0 used))))
                    (update ans :needs conj d))))
              ;; initial state - no demands just one supply
              {:needs  []
               :supply supply}
              ;; feed demands into reducing function
              demands))
    
    (defn consume-all
      "Reduce demands with a list of supplies"
      [{:keys [needs avails]}]
      (->> (reduce (fn [ans s]
                     (let [{:keys [supply needs]
                            :as   same-cat}      (consume (fn [d s] (and (<= (:day s) (:day d))
                                                                         (= (:cat s) (:cat d))))
                                                          (:needs ans)
                                                          s)
                           {:keys [supply needs]} (if supply ;; supply of same cat is consumed
                                                    same-cat
                                                    (consume (fn [d s] (<= (:day s) (:day d)))
                                                             (:needs ans)
                                                             s))]
                       (-> ans
                           (assoc :needs needs)
                           (update :avails conj supply))))
                   ;; initial state - no supply only demands
                   {:needs  needs
                    :avails []}
                   ;; feed supplies into reducing function
                   avails)))
    

    Run with your sample

    (consume-all {:needs [{:day 20190107 :qty -100 :mkey "OS200" :cat "   "}
                           {:day 20190108 :qty -50 :mkey "OS300" :cat "   "}
                           {:day 20190109 :qty -100 :mkey "OS400" :cat "   "}
                           {:day 20190217 :qty -100 :mkey "OS100" :cat "CO1"}]
                  :avails  [{:day 20190101 :qty 10 :mkey "AAABB" :cat "CO1"}
                           {:day 20190101 :qty 20 :mkey "OS100" :cat "CO1"}
                           {:day 20190102 :qty 50 :mkey "OS200" :cat "   "}
                           {:day 20190103 :qty 50 :mkey "OS300" :cat "   "}
                           {:day 20190104 :qty 40 :mkey "OS400" :cat "   "}]})
    
    ==>
    {:needs
     [{:day 20190107, :qty 0, :mkey "OS200", :cat "   "}
      {:day 20190108, :qty -10, :mkey "OS300", :cat "   "}
      {:day 20190109, :qty -100, :mkey "OS400", :cat "   "}
      {:day 20190217, :qty -70, :mkey "OS100", :cat "CO1"}],
     :avails
     [{:day 20190101, :qty 0, :mkey "AAABB", :cat "CO1"}
      {:day 20190101, :qty 0, :mkey "OS100", :cat "CO1"}
      {:day 20190102, :qty 0, :mkey "OS200", :cat "   "}
      {:day 20190103, :qty 0, :mkey "OS300", :cat "   "}
      {:day 20190104, :qty 0, :mkey "OS400", :cat "   "}]}