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

欢迎!请参阅 关于 页面以获得更多关于此工作的信息。

+19
Clojure
已关闭

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

建议:添加 map-keysmap-values,将 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
by

评论由:alexmiller 发表

在这个问题上没有必要浪费时间讨论名字 - Rich 将会有自己的看法。

关于补丁
- 删除不再使用的 :static 元数据
- 需要文档字符串,应该按照其他 Clojure 文档字符串的风格来编写。map 是可能的参考地点。
- 而不是立即声明,这些定义应推迟到所需的部分被定义。没有必要为这个增加更多的声明。

还有其他的潜在实现 - 这些应该被实现并在一系列输入大小上比较性能。除了当前方法之外,我还会调查

  • 使用 constructing into a transient map 的 reduce-kv。这允许 map 自己减少(不需要 seq 缓存)并避免创建条目然后再次拆散。
  • transducers with (into {} (map ...) m)

还应考虑:
- 是否构建 k/v 向量和转换为映射,还是直接构建映射(前者可能更快,不确定)
- 如果构建映射,如何构建映射条目(向量与否直接创建 mapentry 对象)
- 在 map-keys 中,当 map 生成新的重叠键时是否存在任何未解决的问题?
- 在现有核心代码中,map-keys/map-vals 是否有地方可以使用(我相当确定有)

0
by

评论者:delihiros

感谢评论:)

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

关于潜在实现
我已经尝试了几种实现,并且看起来当前的实现是最快的。
您可以在这里查看: https://github.com/delihiros/performance

关于考虑事项
> 是否构建 k/v 向量和转换为映射,还是直接构建映射(前者可能更快,不确定)
> 在现有核心代码中,map-keys/map-vals 是否有地方可以使用(我相当确定有)
> 如果构建映射,如何构建映射条目(向量与否直接创建 mapentry 对象)
我将尽快检查这些。我还没有做到这一点。

在 map-keys 中,当 map 生成新的重叠键时是否存在任何未解决的问题?
我认为它应该被后来应用的键和值覆盖。

0
by

评论由: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"(作为名词)来说明,而不是指定特定的具体类型 "hash map"。

0

评论者:bronsa

map->map 操作也不能是懒加载的。即使某个实现使用懒加载操作遍历原始地图,但 later 会在 `into {}` 中实现。

0

评论由:nathanmarz 发表

-1。Clojure 希望保持核心小巧,将额外功能推入库中。复合转换的问题空间中,该功能只是其中一小部分,已经彻底解决,例如 Specter。Specter 的 MAP-VALSMAP-KEYS 导航器还支持在转换期间通过转换为特殊的 NONE 值来删除键值对。这极大地扩展了其用途。

还值得注意的是,快速实现需要根据地图的类型采用完全不同的方法。《reduce-kv》与临时对象结合对散列映射是最优的,但对于数组映射,使用更低级别的设施可提供 60% 的速度提升。参见 Specter 的实现此处

0

评论者:gshayban

内森,你关于复合变换提出了一个成见假设。这不是要求一个带有过滤旋钮或条件行为(Clojure历史上一直在反对这种做法)的函数。比Specter的其他方法也有很多有效的方法。

关于快速实现:并非每个函数都必须追求最高效的实现,特别是在以分叉和总体复杂度为代价的情况下。性能成本模型必须考虑复杂性。

此票据是关于许多代码库中重复出现的便利性的请求。

我们想要保留元数据吗?许多映射操作都需要。
我们想要假设IEditableCollection吗?

0

评论由:nathanmarz 发表

对于这类通用数据结构操纵函数,性能至关重要。操纵数据结构是你在语言中最基本的事情之一,而且在性能敏感的代码中经常会这么做。在性能敏感的代码中,必须放弃“优雅”的函数,而是使用丑陋的专用代码,这是语言的失败。

map-valsmap-keys是Specter用户过去几年学到很多的问题空间丰富的部分。Clojure几乎没有触及这个问题空间,尤其是对于嵌套或递归数据结构。添加到Clojure中的函数在语义和性能上都明显劣于已在外部库中存在的函数,这似乎很可笑。仅是我的两点看法。

0

评论者:delihiros

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

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