How do I display with rarely used state in clojure?

The situation is this: I transform a sequence of values. The conversion of each value falls into several different cases. Most of the values ​​are completely independent of each other. However, there is one special case that requires me to keep track of how many special cases I have encountered so far. In imperative programming, it's pretty simple:

int i = 0;
List<String> results = new ArrayList<>();
for (String value : values) {
  if (case1(value)) {
    results.add(handleCase1(value));
  } else if (case2(value)) {
  ...
  } else if (special(value)) {
    results.add(handleSpecial(value, i));
    i++;
  }
}

      

However, in Clojure the best I have come up with is:

(first 
 (reduce 
  (fn [[results i] value]
      (cond
       (case-1? value) [(conj results (handle-case-1 value)) i]
       (case-2? value) ...
       (special? value) [(conj results (handle-special value i))
                         (inc i)]))
  [[] 0] values))

      

This is pretty ugly considering that without a special case this would become:

(map #(cond 
       (case-1? %) (handle-case-1 %)
       (case-2? %) ...)
      values)

      

The problem is I am manually stitching the sequence during the cut. Also most of the cases don't even care about the index, but still have to pass it for the next reduction step.

Are there any cleaner solutions to this problem?

+3


source to share


5 answers


Sometimes the code using loop

and recur

looks better than the equivalent code using reduce

.

(loop [[v & more :as vs] values, i 0, res []]
  (if-not (seq vs)
    res
    (cond
      (case-1? v) (recur more i (conj res (handle-case-1 v)))
      (case-2? v) (recur more i (conj res (handle-case-2 v)))
      (special? v) (recur more (inc i) (conj res (handle-special i v))))))

      



Since there seems to be some demand, here is a version that creates a lazy sequence. The usual warnings about premature optimization and their simplicity apply.

(let [handle (fn handle [[v & more :as vs] i]
               (when (seq vs)
                 (let [[ii res] (cond
                                 (case-1? v) [i (handle-case-1 v)]
                                 (case-2? v) [i (handle-case-2 v)]
                                 (special-case? v) [(inc i) (handle-special i v)])]
                   (cons res (lazy-seq (handle more ii))))))]
  (lazy-seq (handle values 0)))

      

+3


source


Do you need a purely functional approach? Try to use a Map for your temporary needs. This provides good results and clean results, as well as an easy way to access these time values ​​when needed.

When we come across a special value, we also update the counter on the map as well as the list of results. This way we can use reduce

to store some state in processing, but keep everything purely functional without atom

s.



(def transformed-values
  (reduce
    (fn [{:keys [special-values-count] :as m} value]
      (cond
        (case-1 value) (update m :results conj (handle-case-1 value))
        (case-2 value) (update m :results conj (handle-case-2 value))
        ...
        (special-case? value) (-> m
                                  (update :results conj (handle-special value special-values-count))
                                  (update :special-values-count inc))
        :else m))
    {:results [] :special-values-count 0}
    your-list-of-string-values))

(:results transformed-values)
;=> ["value1" "Value2" "VALUE3" ...]

(:special-values-count transformed-values)
;=> 2

      

+3


source


You can just use an atom to keep track of it:

(def special-values-handled (atom 0))

(defn handle-cases [value]
  (cond
    (case-1? value) (handle-case-1 value)
    (case-2? value) ...
    (special? value) (do (swap! special-values-handled inc)
                         (handle-special @special-values-handled value))))

      

Then you can simply do

(map handle-cases values)

      

+2


source


There is nothing wrong with using it volatile!

for this - in your case it does not elude the context of the expression and does not create any modification or threading complications:

(let [i (volatile! 0)]
  (map #(cond 
          (case-1? %) (handle-case-1 %)
          (case-2? %) (handle-case-2 %)
          (special? %) (do (handle-special % @i)
                           (vswap! i inc)))
       values)

      

atom

Use Clojure <1.7 instead , or want to do it in a multithreaded way (with pmap for example).

+2


source


As Alejandro said, atom

makes it easy to track volatile state and use it where needed:

(def special-values-handled (atom 0))

(defn handle-case-1 [value]  ...)
(defn handle-case-2 [value]  ...)
...
(defn handle-special [value]
  (let [curr-cnt (swap! special-values-handled inc)]
    ...<use curr-cnt>... )
  ...)

(defn handle-cases [value]
  (cond
    (case-1? value)   (handle-case-1  value)
    (case-2? value)   (handle-case-2  value)
    ...
    (special? value)  (handle-special value)
    :else (throw (IllegalArgumentException. "msg"))))

...
(mapv handle-cases values)

      

Never be afraid to use an atom when a piece of mutable state is the easiest way to solve the problem.


Another method that I sometimes use is to use the "context" map as an accumulator:

(defn handle-case-1 [ctx value] (update ctx :cum-result conj (f1 value)))
(defn handle-case-2 [ctx value] (update ctx :cum-result conj (f2 value)))
(defn handle-special [ctx value]
  (-> ctx
    (update :cum-result conj (f-special value))
    (update :cnt-special inc)))

(def values ...)
(def result-ctx
  (reduce
    (fn [ctx value]
      (cond
        (case-1? value) (handle-case-1 value)
        (case-2? value) (handle-case-2 value)
        (special? value) (handle-special value i)))
    {:cum-result  []
     :cnt-special 0}
    values))

      

+1


source







All Articles