Elie
Elie

Reputation: 53

Clojure , increment a counter

I have a collection looking like this:

[({:customer_id "111", :product_id "222"})({:customer_id "333", :product_id "444"}{:customer_id "555", :product_id "666"})...]

And i would like to flag the "position" of the hash in the collection. At the end i would like my hash to look like this:

[({:product_id "222", :number "1"})({:product_id "444", :number "1"}{:product_id "666", :number "2"})...]

I'have try like this:

(->> (pig/load-clj "resources/test0_file")
(pig/map
     (fn [ord]
       (for [{:keys [product_id]} ord]
         (let [nb (swap! (atom 0) inc)]
           {:product_id product_id :number nb})))) 

But in that case nb is not incrementing. Thanks for you help

Upvotes: 2

Views: 2438

Answers (3)

KobbyPemson
KobbyPemson

Reputation: 2539

map-indexed , assoc and dissoc provide a cleaner solution

(def products ['({:customer_id "111", :product_id "222"})
               '({:customer_id "333", :product_id "444"}
                 {:customer_id "555", :product_id "666"})])


    (for [p products] 
      (map-indexed #(dissoc (assoc %2 :number (str (inc %))) :customer_id ) p))
;user=>(({:number 1, :product_id "222"}) ({:number 1, :product_id "444"} {:number 2, :product_id "666"}))

Upvotes: 5

Thumbnail
Thumbnail

Reputation: 13473

Given

(def data [[{:customer_id "111", :product_id "222"}]
           [{:customer_id "333", :product_id "444"}
            {:customer_id "555", :product_id "666"}]])

then

(map
 #(map-indexed
   (fn [n m]
     (assoc
       (select-keys m [:product_id])
       :number
       (str (inc n))))
   %)
 data)

is

(({:number "1", :product_id "222"})
 ({:number "1", :product_id "444"}
  {:number "2", :product_id "666"}))

Upvotes: 1

Konrad Garus
Konrad Garus

Reputation: 54015

Resisting the urge to play too much code golf, here's a working implementation:

(def products ['({:customer_id "111", :product_id "222"})
               '({:customer_id "333", :product_id "444"}
                 {:customer_id "555", :product_id "666"})])

(defn number-in-list [products]
  (loop [products products counter 1 result []]
     (if (empty? products)
       (seq result)
       (let [[{:keys [product_id]} & ps] products
             updated {:product_id product_id :number (str counter)}]
         (recur ps (inc counter) (conj result updated))))))

(vec (map number-in-list products))

Here's another:

(vec
  (for [product-list products
        :let [numbers (iterate inc 1)
              pairs (partition 2 (interleave numbers product-list))]]
      (for [[number {:keys [product_id]}] pairs]
        {:product_id product_id :number (str number)})))

There is some destructuring going on, but it looks like you have that covered.

I assume that the output is what you really want and for some reason care to have a vector of lists and :number as string. If that is not the case you can drop the calls to seq, str and vec.

Note that this implementation is pure and does not use any mutable contructs.

In general, atoms are pretty rare and only used for some kind of (semi) global, mutable state. For such problems as yours it's more idiomatic to use loops, ranges, sequences etc.

To break this down, this returns an infinite sequence of natural numbers:

(iterate inc 1)
; think '(1 (inc 1) (inc (inc 1)) ..)

This bit returns a sequence of numbers and products interleaved (until one of them runs out):

(interleave numbers product-list)
; [first_number first_product second_number second_product ..]

Then we partition it to pairs:

(partition 2 ...)
; [[first_number first_product] [second_number second_product] ...]

... and finally for each of these pairs we construct the record that we wanted.

Upvotes: 2

Related Questions