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

欢迎!请查看关于页面以了解更多此工作的信息。

+19
Clojure
关闭

很多人都在编写用于映射HashMap中的值的函数

建议:添加`map-keys`和`map-values`,它们将HashMap中的键和值映射到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 元数据,这不再被使用了。
- 需要 docstrings,应该以其他 Clojure docstrings 的风格编写。map 可能是个不错的参考。
- 而不是声明导入,推迟这些定义直到它们的需要定义。没有必要添加更多声明。

还有其他潜在的实现方式 - 这些应该实现并比较它们在各种输入大小上的性能。除了当前的方法,我还将研究

  • reduce-kv 将构建到临时映射中。这允许映射自我减少(不需要序列缓存)并避免创建再拆分的条目。
  • 使用 (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或"空"的测试用例具有此属性),可以实现输出中保持地图类型的属性。

0

评论者:delihiros

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

0

评论者:[email protected]

调用reduce-kv的实现不是懒加载的,因此请在建议的补丁(map-mapper-v3.patch)中阐明该文档。也许最好说“map”,而不是指定特定的具体类型“哈希表”。

0

评论者:bronsa

无论哪种方式,map->map操作都不能是懒加载的。即使某个实现使用懒操作来遍历原始map,但into {} 会在稍后实现这一点。

0

由:nathanmarz 发表评论

反对这个提议。Clojure旨在拥有一个小型核心,将额外的功能推入库中。复合转换的问题空间已经得到了充分的解决,而这项功能只是其中一小部分。Specter的MAP-VALS和MAP-KEYS导航器还在转换期间支持通过转换为特殊的NONE值来删除键/值对,从而大大扩展了其用途。

还值得注意,根据map的类型,快速实现需要完全不同的方法。对于哈希表,使用transients的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
参考:[Clojure Issue CLJ-1959](https://clojure.atlassian.net/browse/CLJ-1959)(由delihiros报告)
...