【问题标题】:Is (first <set>) deterministic?(first <set>) 是确定性的吗?
【发布时间】:2017-06-18 14:54:08
【问题描述】:

给定两个(无序)相同的集合,(first &lt;set&gt;) 是否保证总是返回相同的元素?

如果这被记录在某处,它在哪里?

我为什么关心

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

我不希望对图的节点或边进行排序。这似乎强加了计算开销而没有真正的好处——除了确定性。

我在其他项目中也遇到过同样的需求,但不涉及图表。通常使用遗传算法,我有很多我可以通过的集合(但没有重复!)。程序通过集合的顺序无关紧要,只是它会影响在处理给定元素之前调用随机数生成器的次数。所以,更一般地说,你可以说我的问题是关于 (seq &lt;set&gt;) 是否是确定性的。

重新定义问题?

如果答案是否定的,或者如果答案是肯定的,但对 Clojure 有罪,这里有另一种说明问题的方式:Clojurely 逐项处理集合的方法是什么,你永远不希望重复,你在哪里不关心顺序——除了每次运行程序时都需要项目以相同的顺序出现? (更准确地说,每次调用函数时都使用相同的随机数种子。)

不过,这可能是没有希望的,除非我准备编写另一个图形库。

【问题讨论】:

  • 即使是这样,我也不想在代码中使用它——它掩盖了你的数据结构的意图(即是否被排序)。了解您可能不会问,因为您想使用它。 :)
  • @matt_t_gregg 我绝对想使用它,特别是用于行走图。我会把这个添加到问题中。
  • 我会重新考虑。从集合中取出第一个元素的想法是荒谬的(我的意思是字面意思)。好比说:我要在一个没有顺序的容器中按照容器的顺序最先出现的元素。 Imo,您绝对应该在您的问题中添加行走图部分,因为我真的认为您不应该将first 与一组一起使用。
  • @MasterMastic 你一定是其中之一Axiom of Choice-haters。说真的,我只是需要逐项浏览我不希望重复的集合,其中的顺序无关紧要,但在程序的多次运行中顺序需要相同。也许还有另一种我没有想到的方法。 (我也会将此添加到问题中。)
  • @matt_t_gregg 我不确定,实际上这可能是时候留意 Knuth 反对过早优化的劝告中的智慧了。不过,这个程序非常消耗 CPU,而且我想在不太困难的地方在内部循环中添加不必要的东西。更一般地说,避免排序的另一个原因是排序 模糊了意图。由于顺序没有意义,因此在代码中强制执行特定顺序有点误导。

标签: clojure set deterministic


【解决方案1】:

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

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

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

【讨论】:

  • 好点,即使它碰巧工作,它仍然不能依赖。有没有一种方法可以在 set 集合中获得任意但一致的顺序,该集合不允许重复,而不增加比较函数的额外开销?
【解决方案2】:

稍后可能会有人提供权威答案,同时我进行了快速生成测试。

(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 相同的元素的等效集合。

【讨论】:

  • 显然这种特殊情况会在 Clojure 1.9 中改变,见dev.clojure.org/jira/browse/CLJ-1860
  • 感谢您的反例——以及向我展示如何进行快速生成测试!到目前为止,我几乎没有研究过规范。这是一个很好的学习例子,与我目前正在做的事情直接相关。
【解决方案3】:

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

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

根据The Clojure Toolbox,一个名为linked 的库在Clojure 中提供了链接的哈希集和映射实现。

【讨论】:

  • 谢谢!我刚刚进行了实验,看起来不错,但我的直接应用仍然存在一个障碍:uber/nodesloom/nodes 似乎返回集合。是时候再次更新问题了……
  • 也感谢您提醒我,当我修改程序以在多核上运行时,可能根本没有什么好的方法来保持确定性。 (Ecch!)
猜你喜欢
  • 2011-12-31
  • 2012-05-13
  • 2016-10-20
  • 1970-01-01
  • 1970-01-01
  • 2016-05-12
  • 2021-05-26
  • 1970-01-01
相关资源
最近更新 更多