请在 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-vals` 和 `update-keys`,而不是 `map-vals` 和 `map-keys`

0

评论者:alexmiller

在这上面讨论名字不值得 - 无论怎样Rich都会有自己的看法。

关于补丁
- 删除了::static元数据,这种用法现在已经不再需要
- 需要文档字符串,应该采用其他Clojure文档字符串的风格。map可能是一个好地方可以借鉴。
- 而不是声明到,推迟定义这些内容,直到它们需要定义的内容完成。没有必要为此添加更多的声明。

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

  • reducekv与构造到临时映射。这允许映射自行减少(无需seq缓存)并避免创建条目又要将其拆分。
  • transducers with (into {} (map ...) m)

还应考虑
- 是否构建k/v向量并将其转换为映射,或者直接构建映射(前者可能更快,不确定)
- 如果构建映射,如何构造映射条目(向量与直接创建映射条目对象)
- 在map-keys中,映射生成新重叠键时是否有任何开放问题?
- 是否在现有核心代码中的某些地方可以使用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(我相当肯定有)
> 如果构建映射,如何构造映射条目(向量与直接创建映射条目对象)
我会尽快检查其中的哪些。我还没有做过。

在map-keys中,映射生成新重叠键时是否有任何开放问题?
我坚信,它应该被后应用的键和值覆盖。

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 或 "empty" 的测试用例具有此属性)。

0

由 delihiros 发表的评论

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

0

评论者:[email protected]

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

0

由 bronsa 发表的评论

map->map 操作也不是惰性的。即使一个实现使用了迭代原始映射的惰性操作,但最终的 into {} 仍会将其实现。

0

评论者:nathanmarz

-1 对此。Clojure 旨在保持小型核心,将附加功能推入库中。复合变换(此功能是其中的一小部分)的问题空间已经完全由 Specter 解决。Specter 的 MAP-VALSMAP-KEYS 导航器还支持在变换期间删除键/值对,通过变换为特殊的 NONE 值来实现,这大大扩展了其用途。

另外值得一提的是,快速实现需要根据映射的类型采取完全不同的方法。对于哈希表,使用瞬时值的 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的方法之外,还有其他有效的方案。

关于快速实现:并不是每个函数都必须追求最优性能实现,特别是在增加了分支和复杂性的情况下。性能成本模型必须考虑复杂性。

这是一个请求,在许多代码库中都是重复出现的一个方便的功能。

我们是否希望保留元数据?许多map操作都会涉及到。
我们是否应该假设IEditableCollection?

0

评论者:nathanmarz

对于像这样的一般数据结构操作函数,性能非常重要。在性能敏感的代码中,操作数据结构是你在语言中做的最基本的事情之一,而且它经常在进行。

map-vals/map-keys是Specter用户在过去几年中学到的一些丰富的问题空间的一部分。Clojure在此问题空间上几乎没有任何涉及,尤其是在处理嵌套或递归数据结构时。添加到Clojure的函数与外部库中现有的函数在语义和性能上都明显不足,这似乎相当愚蠢。仅供参考。

0

由 delihiros 发表的评论

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

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