【问题标题】:Why are there so many map construction functions in clojure?为什么clojure中有这么多地图构造函数?
【发布时间】:2011-03-13 07:55:26
【问题描述】:

新手问题,但我不太明白为什么clojure中构造地图的操作这么多。

您有conjassocmerge,但它们似乎或多或少都在做同样的事情?

(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})

真正的区别是什么?为什么所有这些方法在做或多或少相同的事情时都需要?

【问题讨论】:

  • 还有(into {:a 1 :b 2} {:c 3})

标签: clojure


【解决方案1】:

assocconj 对于其他数据结构的行为非常不同:

user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]

如果您正在编写一个可以处理多种集合的函数,那么您的选择将会产生很大的不同。

merge 视为仅限地图的函数(它类似于conj 用于其他集合)。

我的看法:

  • assoc - 在“更改”现有键/值对时使用
  • conj - 在“添加”新的键/值对时使用
  • merge - 在合并两个或多个地图时使用

【讨论】:

  • merge 获取任意数量的地图并将它们合并,而不仅仅是两个。
  • mergeconj 在处理 nil (转换为 {})方面不同,无论其他参数的类型如何。另外,对于assocconj 问题,我有不同的方法,但是由于我已经发布了单独的答案,所以我不会在这里详细说明。 :-)
  • conj 也适用于任意数量的地图。用户=> (conj {:a 1 :b 2} {:c 3} {:d 4}) {:d 4, :c 3, :a 1, :b 2}
  • into 如何适应这个?
【解决方案2】:

实际上,这些函数在与地图一起使用时的行为完全不同。

  1. conj:

    首先,问题文本中的 (conj {:a 1 :b 2} :c 3) 示例根本不起作用(无论是 1.1 还是 1.2;IllegalArgumentException 都被抛出)。只有少数几种类型可以conjed 到地图上,即二元素向量、clojure.lang.MapEntrys(基本上相当于二元素向量)和地图。

    请注意,地图的seq 包含一堆MapEntrys。因此,您可以这样做,例如

    (into a-map (filter a-predicate another-map))
    

    (注意into 使用conj -- 或conj!,如果可能 -- 在内部使用)。 mergeassoc 都不允许你这样做。

  2. merge:

    这几乎完全等同于conj,但它用{} 替换了它的nil 参数——空哈希映射——因此当链中的第一个“映射”恰好是时将返回一个映射nil.

    (apply conj [nil {:a 1} {:b 2}])
    ; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList
    (apply merge [nil {:a 1} {:b 2}])
    ; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
    

    请注意,没有什么(除了文档字符串...)可以阻止程序员将merge 与其他集合类型一起使用。如果这样做,就会出现怪异现象。不推荐。

  3. assoc:

    同样,问题文本中的示例 -- (assoc {:a 1 :b 2} {:c 3}) -- 不起作用;相反,它会抛出一个IllegalArgumentExceptionassoc 接受一个 map 参数,后跟偶数个参数——奇数位置(假设地图位于位置 0)是键,偶数位置是值。我发现我assoc 的东西比我conj 更频繁地出现在地图上,尽管当我conjassoc 时会觉得很麻烦。 ;-)

  4. merge-with:

    为了完整起见,这是处理地图的最终基本功能。我觉得它非常有用。它按照文档字符串的指示工作;这是一个例子:

    (merge-with + {:a 1} {:a 3} {:a 5})
    ; => {:a 9}
    

    请注意,如果地图包含一个“新”键,而该键在其左侧的任何地图中都没有出现,则不会调用合并函数。这有时令人沮丧,但在 1.2 中,聪明的 reify 可以提供具有非nil“默认值”的地图。

【讨论】:

  • 为了说明merge-with 的最后一点,我准备了以下要点:gist.github.com/468332
  • 感谢您的解释。如您所见,我输入错误并在 conj 和 assoc 之间切换了第二个参数。
【解决方案3】:

由于地图是 Clojure 中如此普遍的数据结构,因此拥有多种工具来操作它们是有意义的。在略有不同的情况下,各种不同的函数在语法上都很方便。

我个人对你提到的具体功能的看法:

  • 我使用 assoc 将单个值添加到给定键和值的地图中
  • 我使用 merge 来合并两张地图或一次添加多个新条目
  • 我通常根本不将 conj 与地图一起使用,因为我将它与列表联系起来

【讨论】:

  • 我认为惯用的方法是使用与要添加到地图的新项目最初可用的形式相匹配的任何函数;如果它是映射,使用merge,如果作为一组键和值没有组装在一个集合中,使用assoc等等。不过,我真正想指出的是conj 通用 Clojure 数据结构构建函数——你不能真正将它保存在列表抽屉中。
  • @Michal 也许你是对的 - 但出于某种原因,我不信任在不同输入类型上执行语义上非常不同的事情而没有警告的函数:-)
  • 我认为conj 确实对不同的输入类型进行语义上等效的操作。 ;)
猜你喜欢
  • 1970-01-01
  • 2012-02-10
  • 2020-04-17
  • 1970-01-01
  • 1970-01-01
  • 2015-02-10
  • 1970-01-01
  • 2015-12-17
  • 1970-01-01
相关资源
最近更新 更多