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

欢迎!请查看关于页面了解有关此功能的一些更多信息。

+19
Clojure
已关闭

很多人都在编写一个映射 HashMap 中值的函数

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

解决方案:使用函数 reduce-kv 或普通的 mapinto 是一种常见解决方案,但它们很复杂,类型发生变化,这使得它很复杂且繁琐。

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

0

评论由:alexmiller 提出

在这个问题上,没有必要争论名字 - Rich 无论如何都会有他自己的看法。

关于补丁
- 移除 :static 元数据,因为不再使用它
- 需要文档字符串,应遵循其他 Clojure 文档字符串的风格。map 是获取灵感的良好地方。
- 而不是立即声明这些项,推迟它们的定义,直到需要这些项。没有必要为了这项功能添加更多的声明。

还有其他可能的实现 - 这些应该在一系列输入大小上进行性能对比。除了当前的方法,我还将调查

  • 使用 reduce-kv 将信息结构化到临时映射中。这允许映射自己进行缩减(无需缓存序列),并且避免了只创建条目然后再拆分开来的情况。
  • 使用 transducers 与 (into {} (map ...) m)

还应考虑
- 是否构建一个键/值向量并将其转换为映射,或者直接构建映射(前者可能更快,但不确定)
- 如果构建映射,如何构造映射条目(向量或直接创建 MapEntry 对象)
- 在 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(我非常确定有)
> 如果构建映射,如何构造映射条目(向量或直接创建 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导航器还支持在转换过程中删除键值对,通过转换到特殊的NONE值。这极大地扩大了实用性。

还值得注意的是,快速实现需要根据映射类型采取完全不同的方法。使用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
by

评论由:nathanmarz 提出

对于这种类似的数据结构操作函数来说,性能至关重要。在性能敏感的代码中,操作数据结构是语言中最基本的事情之一,而且经常这样做。在性能敏感的代码中必须放弃“好的”函数去使用丑陋的专用代码,这是语言的失败。

map-vals/map-keys是丰富的问题空间的一部分,作者和Specter的用户在过去几年中对其有了很多了解。Clojure对这个问题空间几乎不触及,尤其是涉及到嵌套或递归数据结构时。添加到Clojure的函数在语义和性能上远不如已经存在于外部库中的函数,这在作者看来似乎很愚蠢。仅代表个人观点。

0
by

评论者:delihiros

我同意Nathan Martz的观点,性能非常重要,但我仍然认为这个函数应该以某种方式导入到语言的核心部分。
人们经常使用这种转换,如果在核心部分实现一个快速版本,将对所有人都有极大的好处。

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