Search code examples
vectorclojureupdates

Adding & removing elements in vector of maps in clojure


I have a use case to implement in my hobby project where i would need to add and remove elements in vector of maps, items, based on some criteria like below

Below following are the input data structures used in my code

Items is a LazySeq

[
{
:name “Bananas”
:line-no 0
:quantity 4
}
{
:name “Apple”
:line-no 2
:quantity 3
}
]

return-quantities is a PersistentArrayMap with line-no as the key like below

{
2 1
}

My objective is to obtain a resultant vector with items split based on return-quantities

[
{
:name “Bananas”
:line-no 0
:quantity 4
}
{
:name “Apple”
:line-no 2
:quantity 1
:returned? true
}
{
:name “Apple”
:line-no 2
:quantity 2
}
]

Below is my example code which achieves that

(let [items-to-be-added
      (for [[line-no return-quantity] return-quantities]
        (let [item (seek #(= line-no (:line-no %)) items)
              item-quantity (:quantity item)
              regular-item (when (pos? (- item-quantity return-quantity))
                             (assoc item :quantity (- item-quantity rma-quantity))
                             (assoc item :return-item? true))
              returned-item (assoc item :quantity rma-quantity)
              result [regular-item returned-item]]
          result))
      items-to-be-removed
      (for [[line-no] return-quantities]
        (let [item (seek #(= line-no (:line-no %)) items)]
          item))
      items-updated (filterv #(not (contains? (vec items-to-be-removed) %)) (vec items))]
  (doall (flatten (concat items-updated items-to-be-added))))

The problem with the above code is that filtering (removing set of elements from vector) does not work. Can someone help me fix the issue with vector filtering ? Also can someone help me to achieve this with more efficient code as i feel that the impl is convoluted ?


Solution

  • Use mapcat when processing a collection to produce zero or more output elements for each input element:

    (defn split-item [item quantity]
      (if quantity
        [(assoc item :returned? true :quantity quantity)
         (update item :quantity - quantity)]
        [item]))
    
    (defn split-items [items quantities]
      (vec (mapcat #(split-item % (quantities (:line-no %))) items)))
    
    (def items
      [{:name "Bananas", :line-no 0, :quantity 4}
       {:name "Apple",   :line-no 2, :quantity 3}])
    
    (def return-quantities
      {2 1})
    
    (split-items items return-quantities)
    => [{:name "Bananas", :line-no 0, :quantity 4}
        {:name "Apple",   :line-no 2, :quantity 1, :returned? true}
        {:name "Apple",   :line-no 2, :quantity 2}]