如何映射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)