Reputation: 17
Trying to compare two vectors, and storing the differences inside another vector.
;Data Set 1
{{:SKU "Apple" :QTY 10 :Status "In Stock" }
{:SKU "Banana" :QTY 10 :Status "In Stock" }
{:SKU "Mango" :QTY 0 :Status "Out of stock"}
{:SKU "XYZ" :QTY 10 :Status "In Stock" }
{:SKU "Grapes" :QTY 10 :Status "In Stock" }}
;Data Set 2
{{:SKU "Apple" :QTY 5 :Status "In Stock" }
{:SKU "Banana" :QTY 0 :Status "Out of Stock"}
{:SKU "Mango" :QTY 10 :Status "In Stock" }
{:SKU "XYZ" :QTY 10 :Status "In Stock" }
{:SKU "Pineapple" :QTY 10 :Status "In Stock" }}
I'm trying to get an output like
{{:SKU "Apple" :Reason "Stock Change -5" }
{:SKU "Banana" :Reason "In Stock +10" }
{:SKU "Mango" :Reason "Out of stock -10" }
{:SKU "Grapes" :Reason "Missing" }
{:SKU "Pineapple" :Reason "Added" }}
I'm trying to build logic using nested doseq, but I don't know how to write it to a variable within clojure.
(defn compare_two_vectors
[data_set1 data_set2]
(doseq [recent_item data_set1]
(doseq [old_item data_set2]
(if (= (recent_item :SKU) (old_item :SKU))
(let [diffresults (clojure.data/diff recent_item old_item)
old_file (second diffresults)
new_file (first diffresults)
current_sku (recent_item :SKU)
]
;; How do I store results into a persistant variable?
)))))
Then I can do
(println (compare_two_vectors data_set1 data_set2))
Update: Or let me know what is the better alternatives. I'm still a newb in regards to clojure :(.
Upvotes: 0
Views: 158
Reputation: 4806
There are two issues with your data. First, a vector is not a good way to store data which is not positional, but rather associative, which you have. Second, you are storing redundant information which can be derived instead, as you store "Out of stock" for quantity zero, and "In stock" for quantity greater than zero. In a large system with many items it is fine to cache derivable data this way, but in this case it is simply unnecessary redundancy. So, you would do better to define your data this way, IMO:
(def ds1 {"Apple" {:QTY 10}
"Banana" {:QTY 10}
"Mango" {:QTY 0}
"XYZ" {:QTY 10}
"Grapes" {:QTY 10}})
(def ds2 {"Apple" {:QTY 5}
"Banana" {:QTY 0}
"Mango" {:QTY 10}
"XYZ" {:QTY 10}
"Pineapple" {:QTY 10}})
This function will do the comparison you want, with the suggested data structures here. First it does a diff to determine the SKUs which are added and removed. Next, it does a diff on the items which are present in both, and uses a merge function to calculate the difference.
(defn sku-diff [sku-before sku-after]
(let [[removed added _] (d/diff (set (keys sku-before)) (set (keys sku-after)))
removed-map (apply hash-map (concat (interpose {:Status "Missing"} removed)
[{:Status "Missing"}]))
added-map (apply hash-map (concat (interpose {:Status "Added"} added)
[{:Status "Added"}]))
[before after _] (d/diff sku-before sku-after)
before (apply dissoc before removed)
after (apply dissoc after added)
merge-fn (fn [{before-qty :QTY} {after-qty :QTY}]
(let [stock-change (- after-qty before-qty)
text (cond (zero? before-qty) "In Stock +"
(zero? after-qty) "Out of stock "
:default "Stock Change ")]
{:Status (str text stock-change)}))
changed-map (merge-with merge-fn before after)]
(merge removed-map added-map changed-map)))
The result:
(sku-diff ds1 ds2)
=>
{"Pineapple" {:Status "Added"},
"Mango" {:Status "In Stock +10"},
"Grapes" {:Status "Missing"},
"Apple" {:Status "Stock Change -5"},
"Banana" {:Status "Out of stock -10"}}
This isn't using the vector you originally used, but clearly a vector is not the right data structure, and an associative one is more appropriate.
Upvotes: 0
Reputation: 5395
Does the order matter in your vectors? If it doesn't (which is probably the case - you seem to be using SKUs for indexing), it's better to convert them first to maps to avoid the O(n^2) loop comparing each SKUs with each.
(defn dataset->map [dataset]
(into {} (for [rec dataset] [(:SKU rec) (dissoc rec :SKU)])))
You can then get a list of all SKUs that were either in the first dataset, or in the second one, or both, by merging the keys lists of the two maps and applying clojure.core/distinct
.
Using this, you can loop over all SKUs to get what you need:
(defn compare-data [dataset-vec-1 dataset-vec-2]
(let [dataset-1 (dataset->map dataset-vec-1)
dataset-2 (dataset->map dataset-vec-2)
all-SKUs (distinct (concat (keys dataset-1) (keys dataset-2)))]
(for [sku all-SKUs
:let [rec-1 (get dataset-1 sku)
rec-2 (get dataset-2 sku)]]
{:SKU sku
:reason (cond
(nil? rec-1) "Added"
(nil? rec-2) "Missing"
:else ...)}))) ; whatever else you need to generate the reason
The code above should return a list in the format you want.
Upvotes: 0
Reputation: 17859
the thing is doseq
is for side effects. In your case you don't need to put anything into some mutable variable. Instead you can map over collections and return result. One of the ways to do it is to use list comprehension (for
):
(defn compare-data [data1 data2]
(for [recent-item data1
old-item data2
:when (= (:SKU recent-item) (:SKU old-item))
:let [[old-file new-file] (clojure.data/diff recent-item old-item)
current-sku (:SKU recent-item)]]
{:SKU current-sku :reason ...}))
(didn't have any time to test it, but still that should work)
Upvotes: 1