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

如何映射clojure中很少使用的状态?,clojure,functional-programming,reduce,Clojure,Functional Programming,Reduce,情况如下:我正在转换一系列的值。每个值的转换分解为许多不同的情况。大多数值彼此完全独立。然而,有一个特殊情况需要我记录到目前为止我遇到了多少特殊情况。在命令式编程中,这非常简单: int i = 0; List<String> results = new ArrayList<>(); for (String value : values) { if (case1(value)) { results.add(handleCase1(value)); } el

情况如下:我正在转换一系列的值。每个值的转换分解为许多不同的情况。大多数值彼此完全独立。然而,有一个特殊情况需要我记录到目前为止我遇到了多少特殊情况。在命令式编程中,这非常简单:

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++;
  }
}
考虑到没有特殊情况,这会变成:

(map #(cond 
       (case-1? %) (handle-case-1 %)
       (case-2? %) ...)
      values)
问题是我在还原过程中手动将序列缝合在一起。此外,大多数情况下甚至不关心索引,但必须将其传递给下一个缩减步骤


这个问题有更干净的解决方案吗?

您可以使用原子来跟踪它:

(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))))
那你就做吧

(map handle-cases values)

正如Alejandro所说,
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)

有时使用
loop
recur
的代码看起来比使用
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))))))
由于似乎有一些需求,这里有一个生成惰性序列的版本。关于过早优化和保持简单的习惯警告适用

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

你想要一个纯功能的方法?尝试使用地图集合满足您的临时价值需求。这将使您的结果保持良好和干净,并且在需要时可以方便地访问这些临时值

当我们遇到一个特殊值时,我们还会更新映射中的计数器以及结果列表。通过这种方式,我们可以在处理过程中使用
reduce
来存储一些状态,但在没有
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

使用
volatile没有什么错对此-在您的情况下,它不会脱离表达式的上下文,也不会产生任何易变性或线程复杂性:

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

如果您正在使用Clojure<1.7或希望以多线程方式(例如,使用pmap),则可以使用atom

我忘记了handle special还需要知道Clojure变体中的索引。我已经相应地编辑了这篇文章。所以我肯定不只是计算特殊情况,而是映射索引增加每个值的索引。我只想在遇到一个特殊的值时增加它。我认为只有一个原子才是最好的选择。更新。索引的地图在这里似乎是多余的。atom处理的特殊值已经在为特殊值编制索引了。也许我误解了你关于需要索引的其他评论。在这种情况下,只需坚持使用
map
mapv
。更新以显示计数器在
handle special
中的使用情况良好,但是手动使用lazy seq的递归函数更好(因为懒惰比生成向量更有效)。我非常喜欢这个解决方案。虽然我的良心仍然迫使我暂时离开。还有,你对@amalloy?@SebastianOberhoff有什么想法?这正是编辑中添加的内容。尽管我想说这根本不是优化,更不用说过早了:相反,它放弃了少量吞吐量,让调用方在如何生成函数的输入和使用函数的输出方面具有灵活性。
(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
(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)