2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请访问 关于 页面以获取更多关于如何使用本站的信息。

+19
Clojure
已关闭

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

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

workaround:使用函数 `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 元数据,现在不再使用
- 需要编写文档字符串,应采用其他 Clojure 文档字符串的风格。map 似乎是一个很好的参考点。
- 不要立即声明,等到需要的定义已经确定后再进行定义。没有必要为这个添加更多的声明。

还有其他潜在的实现方式 - 这些应该被实现,并且应该在各种输入大小上进行性能比较。除了当前方法,我还将调查

  • 使用 reduce-kv 将构建到临时映射中。这允许映射自身进行归约(不需要seq缓存)并避免创建条目然后再分解。
  • 使用 (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 然后再拆分的实现表现很差。
- 对于大映射应使用 Transients,但对于小映射则不应该。
- 这个基准测试表明,在输出中保持地图类型的属性可以被实现,而不需要牺牲性能(使用 Specter 或 "空" 的测试案例具有此属性)。

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 是最佳的,但对于数组映射,使用更低级别的设施提供 ~60% 的速度提升。请参见 Specter 的实现,见此处 https://github.com/nathanmarz/specter/blob/1.0.4/src/clj/com/rpl/specter/navs.cljc#L243

0

评论者:gshayban

内森,你提出了关于组合变换的稻草人论证。这并不是要求一个带有过滤旋钮或条件行为的函数(这是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报告)
...