为什么在大多数情况下,group-by函数的结果键按顺序排列,但在使用范围时却没有?

时间:2013-03-19 11:53:06

标签: clojure

我正在研究this problem

我的解决方案是:

(fn [s]
  (map #(first %) (group-by identity s)))

前三次测试通过,最后一次测试失败。

由于

(group-by identity (range 50)

给出无序的结果。但我的解决方案强烈依赖于分组功能的有序功能。也就是说,必须保持结果映射中每个键的顺序。即使the Doc不能保证这一点,这几乎也是如此。

真奇怪的是:

enter image description here

你看,当参数超过32时,分组功能会给出错误的顺序。结果不是随机的,而是溢出的元素在第一个元素之后。

为什么?

如何保留分组功能的有序功能还是有更好的解决方案?

3 个答案:

答案 0 :(得分:6)

通用地图的任何排序都是实施细节。

使用哈希表实现较大的映射,这通常不会保留顺序。对于小地图,散列的开销高于线性查找的开销。因此,优化是针对小地图以数组地图开始生命,这确实保留了顺序。随着添加更多元素,地图将转换为哈希映射。

(class (group-by identity (range 8)))
;=> clojure.lang.PersistentArrayMap

(class (group-by identity (range 32)))
;=> clojure.lang.PersistentHashMap

这种转换发生在32个元素之前,但是没有挖掘内部结构,我怀疑初始哈希表有32个插槽,因此在哈希冲突策略启动之前不会发生混乱。

4Clojure implement distinct问题而言,您可以使用原始集合中sort-by上的.indexOf来挽救您的解决方案。

扰流:

  

(fn [s]       (排序方式#(。indexOf s%)         (map#(first%)(group-by identity s))))

答案 1 :(得分:0)

听起来你想要sorted-map

=> (apply sorted-map (flatten (seq (group-by identity (range 50)))))
{0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9 9, 10 10, 11 11, 12 12, 13 13, 14 14, 15 15, 16 16, 17 17, 18 18, 19 19, 20 20, 21 21, 22 22, 23 23, 24 24, 25 25, 26 26, 27 27, 28 28, 29 29, 30 30, 31 31, 32 32, 33 33, 34 34, 35 35, 36 36, 37 37, 38 38, 39 39, 40 40, 41 41, 42 42, 43 43, 44 44, 45 45, 46 46, 47 47, 48 48, 49 49}

如您所见,当您处理小地图时,clojure可能会选择已排序的实现。但是,这是一个实现细节,不能保证。 sorted-map返回一个映射,其中键的迭代顺序保证被排序。

答案 2 :(得分:0)

将值添加到地图中时,将返回相应类型的集合。在PersistentArrayMaps的情况下,当大小超过16个项目时(参见source第177行),它返回一个PersistentHashMap,而不是维持顺序。

虽然我无法找到切换第33个元素的行为的直接原因,但我知道处理Vector的方式是32个块,因此更新一个元素不需要全新的向量 - 只需要更换块。它可能与此有关,或者与其他一些优化行为有关。