简体   繁体   English

如何映射clojure中很少使用的状态?

[英]How do I map with rarely used state in clojure?

The situation is as follows: I'm transforming a sequence of values. 情况如下:我正在改变一系列价值观。 The transformation of each value breaks down into a number of different cases. 每个值的转换分解为许多不同的情况。 Most 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've encountered so far. 然而,有一个特例需要我跟踪到目前为止我遇到过多少特殊情况。 In imperative programming this is pretty straightforward: 在命令式编程中,这非常简单:

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've come up with is: 然而在Clojure中,我提出的最好的是:

(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))

Which is pretty ugly considering that without the special case this would become: 考虑到没有特殊情况,这会变得非常难看:

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

The trouble is that I'm manually stitching a sequence together during the reduction. 麻烦的是我在缩小过程中手动拼接一个序列。 Also most cases don't even care about the index but must nonetheless pass it along for the next reduction step. 大多数情况下甚至不关心指数,但必须将其传递给下一个减少步骤。

Is there a cleaner solution to this problem? 这个问题有更清洁的解决方案吗?

Sometimes code using loop and recur looks better than the equivalent code using reduce . 有时使用looprecur代码看起来比使用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 produces lazy sequence. 由于似乎有一些需求,这里有一个产生延迟序列的版本。 Customary warnings about premature optimization and keeping it simple 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)))

You want a purely functional approach? 你想要一个纯功能的方法吗? Try using a Map collection for your temporary value needs. 尝试使用Map集合来满足您的临时价值需求。 This keeps your results nice and clean, and an easy way to access those temporary values when needed. 这可以使您的结果保持良好和干净,并在需要时轻松访问这些临时值。

When we encounter a special value, we also update the counter in the map as well as the result list. 当我们遇到特殊值时,我们还会更新地图中的计数器以及结果列表。 This way we can use reduce to store some state as we process, but keep everything purely functional without atom s. 通过这种方式,我们可以在处理时使用reduce来存储某些状态,但是在没有atom的情况下保持一切纯粹的功能。

(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

You can just use an atom to track 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 just do 然后就可以了

(map handle-cases values)

There is nothing wrong in using a volatile! 使用volatile!可以没有错volatile! for this - in your case, it does not escape the context of the expression and does not create any mutability 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)

You can use an atom instead if you are using Clojure < 1.7 or want to do it in a multi-threaded way (eg with pmap). 如果您使用Clojure <1.7或者想要以多线程方式(例如使用pmap)执行此操作,则可以使用atom

As Alejandro said, an atom allows one to easily keep track of mutable state and use it where needed: 正如亚历杭德罗所说, atom允许人们轻松跟踪可变状态并在需要时使用它:

(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 simplest way to solve a problem. 当一块可变状态是解决问题的最简单方法时,永远不要害怕使用原子。


Another technique I sometimes use is to use a "context" map as the 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))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM