寻找有关如何在Clojure中编写以下代码的建议。我应该到达州Monad吗?或者core.async会有用吗?
我需要将这些称为Blob的东西组合在一起。关于Blob的重要之处在于它具有最多两个接口表面,可以与另一个Blob紧密配合。当两个Blob一起出现时,它们会成为另一个Blob。有些Blob只有一个表面而有些没有任何表面 - 所以根本不能组合。
我需要编写一个函数来接受一系列Blob并返回一个(通常)较小的序列。此功能将尝试所有可能的组合。困难在于当两个Blob组合在一起形成另一个Blob时组合发生变化 - 两个/三个/四个可用表面减少到零/一个/两个。
我用一种我比较习惯的语言编写代码的方式非常有条理。会有一个Blob池,最初是空的。添加新Blob时,所有现有Blob都会排队(顺序无关紧要),以检查它们与新Blob的兼容性。如果没有兼容的话,Blob会与其他人加入,而且不再有活动。但是,如果一个兼容,则形成新的Blob。当然,使用这个新的Blob,排队过程将重新开始。
只是寻求一些帮助,以便采取相应的方向。我不知道如何在功能上解决这个问题。
答案 0 :(得分:2)
我不明白为什么你需要像monad这样的状态。你的第四段对我来说就像一个正常的功能过程。每次测试新的blob时,都可以返回一个新的blob集合,或者使用一个blob,或者使用一个blob,但是包含一个结合了另外两个blob的新blob。 Blob可以持续存在或者可以重新创建,但每一步都有一个新的集合。
关键是你生成的是包含许多blob集合的单个序列,而不是单个blob集合的一系列状态。
顶级序列可能是一个懒惰的blob集合序列,您可以轻松地将其编码为可能无限的blob集合序列,您可以从中检查所需的blob集合。这意味着您拥有生成所有blob集合序列的代码这一事实并不意味着您必须立即将所有这些集合保存在内存中。您可能只对最后一个感兴趣,在这种情况下,您可以drop
其余的。这种方法可能会使代码更简单,在某些情况下,懒惰可能比序列非惰性更有效。
继续重新创建新集合可能听起来效率低下,但可能并非如此;这就是Clojure的目的(而Clojure有时会将效率换成其他优势)。
函数for
,reduce
和map
可能会提供有用的构建块;这些将自动产生延迟序列。也许更多专用工具如filter
或some
会很有用。
答案 1 :(得分:2)
以下是您如何通过我对您的要求的最佳理解进行递归。避免可变状态的关键是使用累加器将所有相关状态传递给下一个函数调用。
(def some-blobs
[{:surfaces #{1}
:data 1}
{:surfaces #{1}
:data 2}])
(def blob-to-merge
{:surfaces #{1}
:data 3})
(def blob-to-conj
{:surfaces #{2}
:data 4})
(def all-the-blobs (into some-blobs [blob-to-merge blob-to-conj]))
(defn combine-blobs [b1 b2]
(let [s1 (b1 :surfaces)
s2 (b2 :surfaces)
s (first (clojure.set/intersection s1 s2))]
{:surfaces (disj (clojure.set/union s1 s2)
s)
:data (mapv :data [b1 b2])})) ; Some arbitrary way to combine the data
(defn share-surface? [b1 b2]
(not-empty (clojure.set/intersection
(b1 :surfaces)
(b2 :surfaces))))
(defn add-blob [blob blobs]
(loop [acc [] ; An accumulator to store processed blobs
[b & bs] blobs] ; The blobs still to process, destructured into first and rest
(if b ; If there is another blob to process
(if (share-surface? b blob) ; If it shares a surface with the incoming blob
(into acc (conj bs (combine-blobs b blob))) ; Then combine blobs and return with the other processed and unprocessed blobs
(recur (conj acc b) ; Else return to `loop` adding the first blob to `acc`
bs)) ; and attempt to process the rest
(conj blobs blob)))) ; If there was no next blob to process then add the incoming blob to the original collection.
(add-blob blob-to-merge some-blobs)
; => [{:surfaces #{}, :data [1 3]}
; => {:surfaces #{1}, :data 2}]
(add-blob blob-to-conj some-blobs)
; => [{:surfaces #{1}, :data 1}
; => {:surfaces #{1}, :data 2}
; => {:surfaces #{2}, :data 4}]
(defn add-all-blobs
"Add each new blob in turn to some existing blobs"
[existing-blobs [b & bs :as new-blobs]]
(if b ; If there is another blob to process
(recur (add-blob b existing-blobs) ; Then recurse with the blob added
bs) ; and process the rest
existing-blobs)) ; If there was no next blob to process then return the original collection
(add-all-blobs [] all-the-blobs)
; => [{:surfaces #{}, :data [1 2]}
; => {:surfaces #{1}, :data 3}
; => {:surfaces #{2}, :data 4}]
或许add-all-blobs
并不总是产生最小可能的最终列表,因为它们的添加顺序。我不确定这对你来说是否重要。