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

欢迎!请参阅 关于 页面了解有关此功能的更多信息。

+19
Clojure
已关闭

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

建议:添加 map-keysmap-values,它们:映射 HashMap 中的键,并在 HashMap 中映射值。它们返回 HashMap 作为结果。

解决方案:使用函数 reduce-kv 或普通 mapinto 是一个常见的解决方案,但它们令人困惑且类型改变,这使得它变得复杂且繁琐。

讨论: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-valsupdate-keys 而不是 map-valsmap-keys

0

评论者:alexmiller

在这个话题上讨论命名无意义 - Rich 仍然会有他自己的看法。

在补丁中
- 删除了不使用的 :static 元数据
- 需要编写注释字符串,其风格应与其他 Clojure 注释字符串保持一致。map 可能是一个很好的参考点。
- 而不是直接声明,推迟到需要定义这些内容后再进行定义。没有必要为这些内容添加更多的声明。

还有其他潜在的实现方式 - 应该对各种输入大小进行实现和性能比较。除了当前的方法之外,我还将调查

  • 使用 reduce-kv 将其构建到临时映射中。这允许映射自行减少(无需缓存序列)并避免创建只为了再次拆分的条目。
  • 使用 transducers with (into {} (map ... m))

还应考虑
- 是否要构建 k/v 向量并将其转换为映射,还是直接构建映射(前者可能更快,不确定)
- 如果构建映射,如何构建映射条目(向量与直接创建 mapentry 对象)
- 在 map-keys 中,当 map 生成新的重叠键时,是否有什么疑问?
- 在现有核心代码中是否可以找到 map-keys/map-vals 的位置(我非常确信有)

0

评论者:delihiros

感谢评论:)

我提议这些函数称为 update-valsupdate-keys 而不是 map-valsmap-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或"空"的测试用例具有此属性)。

0

评论者:delihiros

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

0

评论者:[email protected]

调用reduce-kv的实现不是懒惰的,因此应在建议的补丁(map-mapper-v3.patch)中对文档进行澄清。此外,最好说"map"(作为名词)而不是指定特定的具体类型"哈希表"。

0

评论者:bronsa

map->map操作也不能以任何方式懒惰。即使某个实现使用懒惰操作遍历原始映射,后面的into {}也会实现它。

0

评论者:nathanmarz

对此表示反对。Clojure旨在保持核心小,将附加功能推入库中。复合转换的问题空间已经被Specter彻底解决,而这个功能只是其中的一小部分。Specter的MAP-VALS和MAP-KEYS导航器在转换时还支持删除键值对,通过将转换到特殊值NON来实现。这大大扩展了其实用性。

还值得注意的是,快速实现需要根据映射的类型采取完全不同的方法。《reduce-kv》与瞬态对于哈希表最优,但对于数组映射则使用底层设施可提供约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 提交)
...