请分享您的想法至 2024 Clojure 状况调查!

欢迎!请参阅 关于 页面以了解更多关于此页面信息的工作方式。

+19
Clojure
已关闭

许多人都在编写一个用于在 HashMap 中映射值的函数

建议:添加 `map-keys` 和 `map-values`,它们可以将 HashMap 中的键和值映射。它们返回 HashMap 作为结果。

解决方案:使用函数 `reduce-kv` 或普通 `map` 和 `into` 是一个常见的解决方案,但它们很让人困惑并且类型发生变化,这让事情变得复杂和繁琐。

讨论: https://groups.google.com/forum/#!topic/clojure-dev/kkPYIl5qj0o

已关闭,附带说明: Clojure 1.11.0-alpha2 增加了 `update-keys` 和 `update-vals` 的支持 - 请参阅 CLJ-1959 和 CLJ-2651 以获取更多信息

13 答案

0

评论人:delihiros

map-keys 和 map-vals 的代码和测试

0

评论人:bronsa

我建议将这些函数命名为 `update-vals` 和 `update-keys` 而不是 `map-vals` 和 `map-keys`。

0

评论人:alexmiller

在这个问题上没有必要像处理自行车棚一样命名,Rich 将有自己的观点。

针对补丁
- 删除 :static 元数据,因为这个不再使用
- 需要 docstrings,其风格应与其他 Clojure docstrings 一致。可以使用 map 作为参考。
- 不要一开始就声明,这些定义可以推迟,直到所需内容定义完成。没有必要添加更多声明。

存在其他潜在实现 - 应当在不同输入规模下实现和比较性能。除了当前方法外,我还将研究

  • 使用 reduce-kv 构造一个临时映射。这允许映射自身进行减少(不需要序列缓存)并避免仅创建条目然后再拆分它们的操作。
  • 使用包含 (into {} (map ...) m) 的转换器

还应考虑
- 是否构建一个 k/v 向量并将其转换为映射,或者直接构建映射(前者可能更快,尚不确定)
- 如果构建映射,如何构建映射条目(向量与直接创建 MapEntry 对象)
- 在 map-keys 中,当 map 生成新的重叠键时,是否有任何开放性问题?
- 在现有核心代码中,是否有地方可以使用 map-keys/map-vals(我相当确定有)

0

评论人:delihiros

感谢您的评论:)

我建议将这些函数命名为 `update-vals` 和 `update-keys` 而不是 `map-vals` 和 `map-keys`。
也许。但为了现在,我将它命名为 map-**,稍后我们可以选择最终的名称 :)

关于潜在实现
我已经尝试了几种实现,似乎当前实现是最快的。
您可以在以下链接查看: https://github.com/delihiros/performance

关于考虑事项
> 是否构建一个 k/v 向量并将其转换为映射,或者直接构建映射(前者可能更快,不确定)
> 在现有核心代码中,是否有地方可以使用 map-keys/map-vals(我相当确定有)
> 如果构建映射,如何构建映射条目(向量与直接创建 MapEntry 对象)
我将尽快检查这些。我还没有做过。

在 map-keys 中,当 map 生成新的重叠键时,是否有什么开放性问题?
我相信它应该被后来的键和值覆盖。

0

评论者:nathanmarz

我已经通过构建 Specter 对这个问题进行了大量研究。以下是一些 map-vals 方法的基准测试,包括使用 Specter 的方法。

代码: https://github.com/nathanmarz/specter/blob/4778500e0370fb211f47ebf4d69ca64366117b6c/scripts/benchmarks.clj#L87
结果: https://gist.github.com/nathanmarz/bf571c9ed86bfad09816e17b9b6e59e3

一些备注
- 构建 MapEntry 并将其拆分的实现表现较差。
- 应当在大型映射中使用临时对象,但不是在小映射中使用。
- 该基准测试表明,在不牺牲性能的情况下,可以在输出中保持映射的类型属性(使用 Specter 或 "empty" 的测试用例具有此属性)。

0

评论人:delihiros

我已经修改了实现。它应该比之前更快。

0

评论者:[email protected]

调用reduce-kv的实现不会是懒加载的,因此应在提出的补丁(map-mapper-v3.patch)中明确文档说明。也许最好用“map”(作为名词)而不是指定特定的具体类型“hash map”。

0

评论人:bronsa

map->map操作也不能是懒加的。即便一个实现使用了迭代原始映射的懒操作,也会在稍后执行into {}

0

评论者:nathanmarz

反对。-1。Clojure旨在保持核心小而精,将额外功能推入库中。复合转换的问题空间,其中此功能仅是一小部分,已经被Specter彻底解决。Specter的MAP-VALSMAP-KEYS导航器还支持在转换期间删除键值对,通过转换为特殊值NONE来实现。这大大扩展了其实用性。

还值得注意,快速实现需要根据映射类型采取完全不同的方法。reduce-kv和transients最适合hash映射,但对于数组映射,使用底层设施可提供约60%的速度提升。请参阅Specter的实现在此:https://github.com/nathanmarz/specter/blob/1.0.4/src/clj/com/rpl/specter/navs.cljc#L243

0

评论者:gshayban

Nathan,你对于一个复杂数据转换的看法太偏激了。这并不是对带过滤按钮或条件行为的函数的请求(Clojure历史上一直反对这种行为)。除了Specter之外,还有其他有效的方法。

关于快速实现:不是每个函数都需要力求最出色的性能实现,特别是在增加分支和通用复杂性的成本下。性能的成本模型必须考虑到复杂性。

这个工单是针对在许多代码库中重复出现的一项便利性请求。

我们是否希望保留元数据?许多映射操作都会这样做。
我们是否想要假设IEditableCollection?

0

评论者:nathanmarz

性能对于这种通用数据结构操作函数来说极其重要。操作数据结构是你在一种语言中最基本的事情之一,这在性能敏感的代码中是时常发生的。在性能敏感的代码中,为了性能而不得不放弃“便利”的功能,转而使用丑陋的专用代码,这是语言的一个失败。

map-vals / map-keys 是一个丰富的问题空间的一部分,我自己和Specter的用户在过去几年中已经学到了很多。Clojure几乎触及了这个空间,尤其是在处理嵌套或递归数据结构时。为Clojure添加一些函数,这些函数在语义和性能上都远不如外部库中已有的函数,这似乎有些愚蠢。仅供参考。

0

评论人:delihiros

我同意Nathan Martz的看法,性能非常重要,但我仍然强烈认为这个函数应该以某种方式引入到语言的核心部分。
人们经常使用这种转换,如果核心中有快速的实现,这将对我们所有人都有很大的好处。

0
参考: https://clojure.atlassian.net/browse/CLJ-1959 (由delihiros报告)
...