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

时间:2017-06-05 14:36:24

标签: clojure functional-programming reduce

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

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++;
  }
}

然而在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))

考虑到没有特殊情况,这将变得非常难看:

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

麻烦的是我在缩小过程中手动将序列拼接在一起。大多数情况下甚至不关心指数,但必须将其传递给下一个减少步骤。

这个问题是否有更清洁的解决方案?

5 个答案:

答案 0 :(得分:3)

有时使用 dbWB = Application.ActiveWorkbook.FullName dbWS = Application.ActiveSheet.Name dsh = "[" & dbWS & "$]" Set DB = CreateObject("ADODB.Connection") dbPath = "\\Corpaa.aa.com\CampusHome\IOCADHome02\758673\Projects\Global Analysis Tool\MX Analysis DB\Global Line MX Hub Review DB.accdb" scn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath DB.Open scn Dim rs As ADODB.Recordset Dim r As Long Dim y As Long y = TWB.Cells(1, 1).End(xlDown).Row r = TWB.Cells(1, 1).End(xlToRight).Column DB.Execute "DELETE * FROM tblNewSchedule;" Set rs = New ADODB.Recordset rs.Open "tblNewSchedule", DB, adOpenKeyset, adLockOptimistic, adCmdTable For i = 2 To y With rs .AddNew For j = 1 To r fName = TWB.Cells(1, j) fData = TWB.Cells(i, j) .Fields(fName) = fData Next j End With Next i DB.Close 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))))))

答案 1 :(得分:3)

您想要一种纯粹的功能性方法吗?尝试使用Map集合来满足您的临时价值需求。这可以使您的结果保持良好和干净,并在需要时轻松访问这些临时值。

当我们遇到特殊值时,我们还会更新地图中的计数器以及结果列表。通过这种方式,我们可以在处理过程中使用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

答案 2 :(得分:2)

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

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

答案 3 :(得分: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,则可以使用atom。 1.7或者想要以多线程的方式(例如使用pmap)。

答案 4 :(得分:1)

正如亚历杭德罗所说,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)

当一块可变状态是解决问题的最简单方法时,不要害怕使用原子。

我有时使用的另一种技术是使用&#34; context&#34;映射为累加器:

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