请在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 元数据,因为现在不再使用了
- 需要文档字符串,应该参考其他 Clojure 文档字符串的格式。map 可能是一个很好的参考点。
- 不要立即声明,可以将定义延迟到所需的定义已经完成。没有必要添加更多声明。

还有其他潜在的实现方式 - 应该实现这些方式并比较不同输入规模的性能。除了当前的方法之外,我还将调查

  • reduce-kv 与构建到临时映射中。这允许映射本身进行减少(无需缓存 seq),并避免创建仅再次拆分条目的条目。
  • transducers 与 (into {} (map ...) m)

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

0

评论由:delihiros

感谢评论:)

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

关于可能的实现方法
我尝试了几个实现方法,目前看起来当前的实现是最快的。
您可以在以下位置看到它: https://github.com/delihiros/performance

关于考虑事项
是否构建 kv 向量并将其转换为映射,或直接构建映射(前者可能更快,不确定)
核心代码中是否有可以使用 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或“空”的测试用例具有此属性)。

0

评论由:delihiros

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

0

评论者:[email protected]

调用reduce-kv的实现不是懒的,因此应在建议的补丁(map-mapper-v3.patch)中明确说明文档。也可能最好用“map”(作为名词)而不是指定特定的具体类型“哈希映射”。

0

评论由:bronsa

map->map运算操作也不可能为默认提供支持。即使一个实现使用懒操作来迭代原始映射,最后的into {}会实现它。

0

评论者:nathanmarz

-1对此。Clojure旨在保持核心小,将附加功能推入库。复合转换的问题空间,这一功能是其中的一小部分,已经被Specter彻底解决。Specter的MAP-VALS和MAP-KEYS导航器还支持在转换期间通过转换为特殊值NONE来移除键值对,这大大扩展了其用途。

还需要注意的是,快速实现需要根据映射的类型采取完全不同的方法。对于哈希映射,reduce-kv与瞬态是最佳的,但对于使用底层数据结构的数组映射则可以提供大约60%的性能提升。请参见Specter的实现此处

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报告)
...