Clojure 什么';嵌套测试的功能版本是什么?

Clojure 什么';嵌套测试的功能版本是什么?,clojure,functional-programming,Clojure,Functional Programming,我把一些C++代码转换成Culjure,我想要 返回添加了一组边的图形g。 我传入顶点数、图形和 测试谓词(例如,一个可能依赖于i、j、随机性等的函数)如下所示: (defn addSomeEdges [v g test-p] (doseq [i (range v)] (doseq [j (range (dec i))] (if test-p (add-edges g [i j] ) ))) g) 当然,问题是(添加边)返

我把一些C++代码转换成Culjure,我想要 返回添加了一组边的图形
g
。 我传入顶点数、图形和 测试谓词(例如,一个可能依赖于i、j、随机性等的函数)如下所示:

(defn addSomeEdges [v g test-p]
  (doseq [i (range v)]
    (doseq [j (range (dec i))] 
      (if test-p 
          (add-edges g [i j] )
          )))
  g)

当然,问题是
(添加边)
返回一个新的
g
。请问,如何使用最佳实践Clojure捕获此更新的图表?在C++中看起来很简单自然。

< P>有不止一个解决方案。但是,有时候,当您遇到一个基本的可变/命令式问题时,您应该只使用可变/命令式解决方案:

; simplest version using mutation
(defn addSomeEdges [v g test-p]
  (let [g-local (atom g)]
    (doseq [i (range v)]
      (doseq [j (range (dec i))]
        (when (test-p i j ...)  ; what other args does this need?
          (swap! g-local add-edges [i j]))))
    @g-local))
我对
test-p
的符号有点不确定,因此该部分可能需要改进

注意
交换
将调用
添加边
如下所示:

(add-edges <curr val of g-local> [i j])
(添加边[i j])

有关更多信息,请参见(&p)。

如果将信息分为两部分,则迭代累积信息看起来就像一个缩减函数:

  • 生成一组要考虑的边,包括
  • 测试每条边,如果它通过,则将其包括在内。否则,将结果以不变的方式传递
可以使用
reduce

user> (defn add-edge [g i j]
        (assoc g i j))
#'user/add-edge

user> (add-edge {1 2} 2 1)
{1 2, 2 1}

user> (defn addSomeEdges [v g test-p]
        (reduce (fn [graph [i j]]        ;; this takes the current graph, the points,
                  (if (test-p graph i j) ;; decides if the edge should be created.
                    (add-edge graph i j) ;; and returns the next graph
                    graph))              ;; or returns the graph unchanged.
                g  ;; This is the initial graph
                (for [i (range v)    
                      j (range (dec i))]
                  [i j])))  ;; this generates the candidate edges to check.
#'user/addSomeEdges
让我们开始吧

user> (addSomeEdges 4 {1 2} (fn [g i j] (rand-nth [true false])))
{1 2, 2 0}
user> (addSomeEdges 4 {1 2} (fn [g i j] (rand-nth [true false])))
{1 2, 3 0}
user> (addSomeEdges 4 {1 2} (fn [g i j] (rand-nth [true false])))
{1 2, 2 0, 3 1} 
当您想到其他测试时,您可以将这些调用线程连接在一起:

user> (as-> {1 2} g
        (addSomeEdges 4 g (fn [g i j] (rand-nth [true false])))
        (addSomeEdges 7 g (fn [g i j] (< i j)))
        (addSomeEdges 9 g (fn [g i j] (contains? (set (keys g)) j))))
{1 2, 3 1, 4 1, 5 3, 6 4, 7 5, 8 6}
user>(作为->{12}g
(附加边4g(fn[gij](rand nth[true-false]))
(增加7克(fn[gij](
您也可以在
for
中使用
:when
进行筛选并简化
reduce
。只有在边缘检测仪独立于reduceing函数先前迭代添加的边缘时,when in a for循环才起作用。如果第一条边受影响,应延迟添加哪些边。一些常见的情况是循环消除、生成生成树和最小连通图。如果您的测试符合该标准,那么您可以使用Justin three for loop解决此问题,无需减少。非常感谢@Arthurlfeldt!使用过滤器对生成器进行缩减是一种非常有用的模式,生成树等也是我所考虑的例子。但是我不理解你的评论:“想想你可以把这些调用连接在一起的其他测试?”?在您的评论中:只要测试没有被以前的添加更改,那么在生成for循环时也可以只使用a:when?我还没有发现有人以这种方式使用ad hoc atom,而不是代码味道。也感谢这个解决方案。我同意有时只是非常易变/不纯净是好的,但现在我正试图记住如何从功能上思考。而真正的克隆人似乎也对替代方案的“气味”感到不安。你可以使用
循环/recur
在没有原子的情况下实现同样的事情,但是使用依赖嵌套循环,需要更多的手动索引,并且有更多的机会通过一个错误或类似错误获得成功。还要注意, >减少使用内部的突变,以更复杂的代码排列为代价。C++代码可能会很有用。