23

...也许使用可变数据的命令式编程已经深入我的大脑,但我发现在 Clojure 中构建数据向量的代码冗长、笨拙且令人费解。肯定有更好的办法!

在 Ruby 中,我可能会编写如下代码:

results = []
a_collection.each do |x|
  x.nested_collection.each do |y|
    next if some_condition_holds
    results << y
  end
end

在 Clojure 中,我不知道有比使用递归函数更好的方法,也许像下面的(可怕的)代码:

; NEWBIE ALERT! NEWBIE ALERT!
(loop [results   []
       remaining a_collection]
  (if (empty? remaining)
      results
      (recur
        (loop [results results
               nested  (nested_collection (first remaining))]
           (if (empty? nested)
               results
               (if (some_condition_holds)
                   (recur results (rest nested))
                   (recur (conj results (first nested)) (rest nested))))) 
        (rest remaining))))

如果没有可变数据和迭代循环,您需要使用递归来构建集合。每个这样的递归函数都需要一个(empty?)保护子句,等等。整个事情是如此重复,让我想尖叫。

在简单的情况下,map就足够了,但我正在考虑存在多个嵌套级别的情况,并且在每个级别上,可能存在需要跳过迭代的条件。

在 Common Lisp 中,我可能会使用loop宏或mapcan. Clojure没有类似的东西mapcan吗?

4

5 回答 5

25

按照我认为选项看起来有多好的降序排列:

(for [x coll,
      y (nested-collection x)
      :when (not (some-condition-holds y))]
  y)

或者,如果您宁愿使用函数构建它,mapmapcat不是使用for语法:

(mapcat (fn [x]
          (remove some-condition-holds
                  (nested-collection x)))
        coll)

如果你真的热衷于它,你也可以用偏函数应用和组合来构建它:

(mapcat (comp (partial remove some-condition-holds)
              nested-collection)
        coll)

这第三种风格在 Clojure 中读起来不太好,尽管在其他一些语言中的等效代码非常好。例如,在 Haskell 中:

coll >>= (filter (not . someConditionHolds) . nestedCollection)
于 2012-07-03T00:01:50.600 回答
7
(mapcat (fn [y] (filter condition y)) x)
于 2012-07-03T00:59:13.853 回答
7

其他人已经提供了有关如何使用 FP 概念(例如使用高阶函数)解决特定问题的答案。如果您分析导致您现有代码的思维过程,并将其与其他人提供的 FP 解决方案进行比较,您会发现每当您想到 - “有一个变量来存储处理后的结果” - 它都会导致命令式 OR一步一步的解决方案,因此您的 Clojure 代码主要是必要的,因为您认为将结果存储为“向量变量”。这种思维不允许你应用基于“表达评估”和“通过组合解决问题”的FP概念

于 2012-07-03T05:20:24.197 回答
4

高阶函数确实可以帮助它变得更漂亮,尽管它确实需要一段时间来适应序列的思考和序列的转换。

有很多方法可以写这个:

user> (into [] a_colletion)
[0 1 2 3 4 5 6 7 8 9]

user> (vec a_colletion)
[0 1 2 3 4 5 6 7 8 9]

user> (for [x a_colletion :when (even? x)] x) 
(0 2 4 6 8)

一个更复杂的例子可能看起来像这样:

(flatten (for [x (map extract-from-nested-collection a_collection)
                 :when (test-conditions? x)]
            x))

做一个嵌套集合

user> (def a_collection (map #(reductions + (range %)) (range 1 5)))
#'user/a_collection 
user> a_collection
((0) (0 1) (0 1 3) (0 1 3 6))

从 a_collection 的每个元素中检索嵌套集合并跳过其中一些:

user> (map #(filter pos? %) a_collection)
(() (1) (1 3) (1 3 6))

将嵌套集合附加在一起

user> (flatten (map #(filter pos? %) a_collection))
(1 1 3 1 3 6)

从展平的集合中过滤一些大于 3 的东西,然后将它们中的每一个平方

user> (for [x (flatten (map #(filter pos? %) a_collection))
              :when (<= x 3)]
           (* x x))
(1 1 9 1 9)
user> 
于 2012-07-02T22:41:46.047 回答
2

如果您想遵循惯用的功能样式并产生惰性序列,amalloy 的答案可能是最好的。

如果您真的对命令式构造向量(而不是惰性序列)感兴趣,我可能会使用 atom 和 doseq 如下所示:

(let [v (atom [])]
  (doseq [x (range 5)
          y (range 5)]
    (if (> y x)
      (swap! v conj (str x y))))
  @v)

=> ["01" "02" "03" "04" "12" "13" "14" "23" "24" "34"]

如您所见,这最终在结构上与 Ruby 代码非常相似。

也可以使用 reduce 来做到这一点,但是当只有一个输入序列时这是最合适的,例如:

(reduce
  (fn [v x] 
    (if (even? x)
      (conj v x)
      v))
  []
  (range 20))

=> [0 2 4 6 8 10 12 14 16 18]
于 2012-07-03T08:01:29.657 回答