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将构造到一个临时映射中。这允许映射自身进行减少(无需序列缓存)并避免创建只为了再次拆分条目。
  • 使用(transducer) into {} (map ...) m

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

0

由:delihiros做出的评论

感谢评论:)

我建议这些函数命名为update-valsupdate-keys,而不是map-valsmap-keys
也许吧。但现在我叫它map-**,我们稍后再决定:)

关于潜在实现
我已经尝试了几个实现,似乎是当前实现速度最快。
您可以在这里看到: https://github.com/delihiros/performance

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

在map-keys中,当map生成新的重叠键时,有什么未解决的问题吗?
我相信它应该被后来应用的键和值覆盖。

0

评论人:nathanmarz

我在Specter的构建过程中对这个问题进行了大量调查。以下是一些关于使用Specter实施map-vals的多次基准测试。

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

几个评论
构建和拆解MapEntry的实现性能较差。
对于大映射应使用transient,但不宜用于小映射。
此基准测试表明,在输出中维护映射类型属性可以在不牺牲性能的情况下实现(使用Specter或"empty"的测试案例具有此属性)。

0

由:delihiros做出的评论

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

0

评论者:[email protected]

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

0

由:bronsa做出的评论

map->map操作也不能是懒加载的。即使一个实现使用懒操作遍历原始映射,最终的into {}也会将其实现。

0

评论人:nathanmarz

反对这一点。Clojure旨在保持核心小巧,将附加功能推入库中。复合转换的问题域(其中此功能是一小部分),已经被Specter彻底解决。Specter的MAP-VALSMAP-KEYS导航器还可以在转换期间通过转换为特殊的NONE值来删除键值对,这极大地扩展了其实用性。

还值得注意,根据映射的类型,快速实现需要采用完全不同的方法。对于hash maps,使用transients和reduce-kv是最优的,但对于array maps,使用底层工具提供了~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做出的评论

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

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