在解构Set in Loop标题

时间:2016-01-01 10:31:46

标签: clojure set destructuring

Clojure noob在这里。

我想拉开前面,然后休息一下。执行(front #{1})(rest #{1})分别产生1(),这主要是我所期望的。

但是在下面的代码中,我在循环中使用解构[current-node & open-nodes] #{start}从集合中取出一些东西(此时我并不关心它是第一个还是最后一个项目。我只是希望这个表格正常工作)并且它会中断。

这是我的功能,半实现网格搜索:

(defn navigate-to [grid start dest]
  "provides route from start to dest, not including start"
  (loop [[current-node & open-nodes] #{start} ;; << throws exception
         closed-nodes #{}]
    (if (= dest current-node)
      [] ;; todo: return route
      (let [all-current-neighbours (neighbours-of grid current-node) ;; << returns a set
            open-neighbours (set/difference all-current-neighbours closed-nodes)]
        (recur (set/union open-nodes open-neighbours)
               (conj closed-nodes current-node))))))

当单步执行时(使用Cider),在第一个循环开始时,它会抛出此异常:

UnsupportedOperationException nth not supported on this type: PersistentHashSet  clojure.lang.RT.nthFrom (RT.java:933)

我可以使用嵌套的let表单,它首先/手动执行,但这看起来很浪费。有没有办法让循环形式的destructured集合像这样工作?是不是套装不支持?

2 个答案:

答案 0 :(得分:3)

集合是无序的,因此位置解构没有多大意义。

根据同样处理解构的Special Forms文档,指定顺序(向量)绑定使用nthnthnext来查找要绑定的元素。

  

Vector binding-exprs允许您将名称绑定到顺序事物(不仅仅是矢量)的部分,例如矢量,列表,序列,字符串,数组以及支持第n个的任何内容。

Clojure哈希集(是java.util.Set的实例)不支持按索引查找。

我不知道您的示例代码的上下文,但无论如何将设置内容倾注到有序集合中,例如(vec #{start}),都会使解构工作。

答案 1 :(得分:2)

正如其他人所提到的,您无法将集合绑定到矢量文字,因为集合不是顺序的。因此即使这个简单的let也会失败, nth不受支持

(let [[x] #{1}])

您可以通过使用firstdisj“解构”该集来解决此问题:

(loop [remaining-nodes #{start}
       closed-nodes #{}]
    (let [current-node (first remaining-nodes)
          open-nodes   (disj  remaining-nodes current-node)]
        ;; rest of your code ...
    ))

使用(rest remaining-nodes)代替(disj remaining-nodes current-node)是可能的,但由于集合是无序的,理论上rest没有义务取出与{{1}提取的元素相同的元素}。无论如何first将完成这项工作。

注意:请务必检测剩余节点 nil ,这可能导致无限循环。

返回路径的算法

为了实现算法中的缺失部分(返回路线),您可以维护 路径图。每个被访问节点都有一条路径:一个向量,节点从起始节点通向该节点,由该节点键入。

您可以在访问新节点时使用disj来维护该路径图。使用与reduce一起使用的新函数和添加的 nil 测试,程序可能如下所示:

reduce

虽然我将原始(defn add-path [[path paths] node] "adds a node to a given path, which is added to a map of paths, keyed by that node" [path (assoc paths node (conj path node))]) (defn navigate-to [grid start dest] "provides route from start to dest, including both" (loop [remaining-nodes #{start} closed-nodes #{} paths (hash-map start [start])] (let [current-node (first remaining-nodes) current-path (get paths current-node) all-current-neighbours (neighbours-of grid current-node) open-neighbours (set/difference all-current-neighbours closed-nodes)] (if (contains? #{dest nil} current-node) current-path ;; search complete (recur (set/union (disj remaining-nodes current-node) open-neighbours) (conj closed-nodes current-node) (second (reduce add-path [current-path paths] open-neighbours))))))) 与解构节点所需的原始let合并,但算法的本质仍然相同。这不是绝对必要的,但它可能使代码更具可读性。

测试

我使用 grid neighbors-of 的穷人定义对此进行了测试,基于此图表(数字是节点,条形表示链接节点:

0--1  2
|  |  |
3--4--5
|
6--7--8

这个图表似乎是测试的一个很好的候选者,因为它有一个循环,一个死角,并且是连接的。

图形使用 grid 作为向量进行编码,其中每个元素代表一个节点。该向量中的元素索引是节点的标识符。每个元素的内容都是一组邻居,使邻居 - 的功能变得微不足道(你的实现会有所不同):

(def grid [#{1 3}   #{0 4}   #{5}
           #{0 4 6} #{1 3 5} #{2 4}
           #{3 7}   #{6 8}   #{7}  ])

(defn neighbours-of [grid node]
    (get grid node))

然后测试是找到从节点0到节点8的路由:

(println (navigate-to grid 0 8))

输出是:

[0 1 4 3 6 7 8]

这个结果表明算法不保证最短的路线,只有路线才能找到。我想在不同引擎上的结果可能会有所不同,具体取决于Conjure内部如何决定从first的集合中采用哪个元素。

删除其中一个必需的节点链接(如节点7和8之间的节点链接)后,输出为 nil

注意:我发现这是一个有趣的问题,在我的回答中可能有点太过分了。