Clojure 2024 调查问卷 中分享您的想法!

欢迎!有关如何使用本服务的更多信息,请参阅 关于 页面。

+19
Clojure
已关闭

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

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

解决方法:使用 `reduce-kv` 函数或普通的 `map` 和 `into` 函数是一种常见解决方案,但它们可能会令人困惑,并且类型可能会改变,这使得操作变得复杂和繁琐。

讨论:[https://groups.google.com/forum/#!topic/clojure-dev/kkPYIl5qj0o](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 构建到瞬态地图中。这允许地图自我缩减(不需要缓存序列),并避免再次拆分条目。
  • transducer 使用 (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
by

评论者:delihiros

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

0
by

评论者:[email protected]

调用 reduce-kv 的实现不是惰性的,因此应该在提议的补丁中明确文档(map-mapper-v3.patch)。此外,最好说 "map"(作为名词),而不是指定特定的具体类型 "hash map"。

0
by

评论者:bronsa

map->map 操作也不能是惰性的。即使一个实现使用惰性操作遍历原始映射,into {} 也会后来实现它。

0
by

评论者:nathanmarz

-1。Clojure 旨在保持核心小,将附加功能推向库。这种功能是复合转换问题空间的一个小部分,该问题空间已经被 Specter 完全解决。Specter 的 MAP-VALSMAP-KEYS 导航器还支持在转换期间删除键值对,通过将其转换为特殊值 NONE。这大大扩大了其实用性。

此外,值得注意的是,快速实现需要根据映射的类型采用完全不同的方法。对于哈希映射,使用 transients 的 reduce-kv 是最优的,但对于数组映射,使用底层设施可以提供 ~60% 的速度提升。请参阅 Specter 的实现这里

0
by

评论者:gshayban

内森,你在关于复合变换的问题上构建了一个稻草人。这并不是请求一个具有过滤旋钮或条件行为的函数(这通常是Clojure所反对的)。除了Specter的其他方法外,还有许多有效的其他方法。

关于快速实现:并非每个函数都必须追求最有效的实现,尤其是在牺牲分支和总体复杂性的情况下。性能成本模型必须考虑复杂性。

这个工单是请求在许多代码库中重复出现的便捷功能。

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

0

评论者:nathanmarz

对于这类通用数据结构操作函数,性能至关重要。在语言中使用,最重要的是操作数据结构,而且在性能敏感的代码中经常执行。在性能敏感的代码中必须放弃“美好”的函数,转而使用丑陋的特殊化代码,这是一种语言的失败。

map-vals/map-keys 是一个丰富的问题空间的一部分,自从我和Specter的用户过去几年学到了很多。Clojure几乎触及了这个问题空间,尤其是对于嵌套或递归数据结构。在我看来,在Clojure中添加功能,其语义和性能都劣于已有外部库的功能,似乎是相当愚蠢的。只是我个人的看法。

0

评论者:delihiros

我同意内森·马茨的看法,即性能非常重要,但我仍然有一个强烈的观点,即这个函数应以某种方式导入到语言的内核部分。
人们经常使用这种转换,如果核心部分有快速实现,将对我们所有人都有很大益处。

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