(第一个<set>)是确定性的吗?

时间:2017-06-18 14:54:08

标签: clojure set deterministic

鉴于两个(无序)相同的集合,(first <set>)是否保证始终返回相同的元素?

如果在某处记录了这一点,它在哪里?

为什么我关心

我现在想要这个的主要原因是搜索由随机过程产生的图形,产生应用更多随机过程的结果。 uber/nodesloom/nodes似乎会返回集合。 遍历顺序的一些细节并不重要,但有些情况确实如此。重要的是,每次我使用相同的随机数种子运行程序时,我都应该得到相同的结果。

我不希望对图表的节点或边缘强加排序。这似乎会带来计算开销,除了确定性之外没有真正的好处。

我在其他项目中遇到了同样的需求,而不涉及图表。通常使用遗传算法,我已经获得了很多套装(但没有重复!)。程序通过集合的顺序并不重要,除了它影响在处理给定元素之前调用随机数生成器的次数。所以,更一般地说,你可以说我的问题是关于(seq <set>)是否具有确定性。

重新定义问题?

如果答案是否定的,或者如果答案是肯定的,但是对Clojure犯了罪,这是解决问题的另一种方式:Clojurely通过收集项目的方式是什么 - 按项目,你永远不需要重复,你不关心订单 - 除了你每次运行程序时需要以相同的顺序进入项目? (更确切地说,每次使用相同的随机数种子调用函数时。)

但是,这可能毫无希望,除非我想写另一个图库。

3 个答案:

答案 0 :(得分:3)

在我知道的任何地方都无法保证。实际上,如果实现和/或哈希函数发生变化,很可能会针对不同版本的Clojure进行更改。

如果您想按特定顺序使用,请使用sorted-setsorted-set-by例如:

user> (sorted-set-by > 3 5 8 2 1)
#{8 5 3 2 1}

答案 1 :(得分:3)

有人可能会在稍后提供权威答案,与此同时我做了一个快速的生成测试。

(require '[clojure.spec.alpha :as s]
         '[clojure.spec.test.alpha :as stest])

(defn rebuild-set [set]
  (into #{} (shuffle (vec set))))

(s/fdef rebuild-set
  :args (s/cat :set set?)
  :ret set?
  :fn #(= (-> % :ret first)
          (-> % :args :set first)))

(stest/check `rebuild-set
             {:clojure.spec.test.check/opts {:num-tests 100}})

令我惊讶的是,这似乎最初成功了。

然后是一个边缘案例,一个反例出现了:

(= #{0 -0.0} #{-0.0 0})  ; => true
(first #{0 -0.0})  ; => 0
(first #{-0.0 0})  ; => -0.0

因此,我们可以非常一般地说明,有些等效的集合不会使用first返回相同的元素。

答案 2 :(得分:3)

还有另一种选择可能值得一提 - 链接哈希集。链接的哈希集保留了插入顺序。首先插入的元素是迭代集合中的值时的第一个元素。

如果相同的随机种子保证在应用程序的不同运行中以相同顺序将元素添加到集合中,则(first my-linked-set)是确定性的。但是,在许多情况下,相同的随机种子不能保证相同的插入顺序。例如,如果多个线程通过原子更新同一组,那就是这样。

根据The Clojure Toolbox,名为linked的库在Clojure中提供链接的哈希集和地图实现。